Decode a JSON tuple to Elm tuple - json

My JSON looks like the following
{ "resp":
[ [1, "things"]
, [2, "more things"]
, [3, "even more things"]
]
}
the problem is that I can't parse the JSON tuples into Elm tuples:
decodeThings : Decoder (List (Int, String))
decodeThings = field "resp" <| list <| map2 (,) int string
It compiles, but when ran, it throws
BadPayload "Expecting an Int at _.resp[2] but instead got [3, \"even more things\"]
For some reason it reads [3, "even more things"] as only one thing and not as tuple in JSON format.
How can I parse my JSON into a List (Int, String)?

The accepted answer is more complicated than it needs to be. Try:
import Json.Decode as Decode
decodeTuple : Decode.Decoder (Int, String)
decodeTuple =
Decode.map2 Tuple.pair
(Decode.index 0 Decode.int)
(Decode.index 1 Decode.string)
and then, as you note, for the list
Decode.list decodeTuple

You need a decoder which turns a javascript array of size two into an Elm tuple of size two. Here is an example decoder:
arrayAsTuple2 : Decoder a -> Decoder b -> Decoder (a, b)
arrayAsTuple2 a b =
index 0 a
|> andThen (\aVal -> index 1 b
|> andThen (\bVal -> Json.Decode.succeed (aVal, bVal)))
You can then amend your original example as follows:
decodeThings : Decoder (List (Int, String))
decodeThings = field "resp" <| list <| arrayAsTuple2 int string
(Note that my example decoder does not fail if there are more than two elements, but it should get you pointed in the right direction)

I could not get either Chad Gilbert's or Simon H's solution to work with Elm 0.19. I am quite new to Elm, but this is what I could get to work:
import Json.Decode as Decode
import Json.Decode.Extra as Decode
{-| Decodes two fields into a tuple.
-}
decodeAsTuple2 : String -> Decode.Decoder a -> String -> Decode.Decoder b -> Decode.Decoder (a, b)
decodeAsTuple2 fieldA decoderA fieldB decoderB =
let
result : a -> b -> (a, b)
result valueA valueB =
(valueA, valueB)
in
Decode.succeed result
|> Decode.andMap (Decode.field fieldA decoderA)
|> Decode.andMap (Decode.field fieldB decoderB)

Related

How to Encode and Decode Simple Custom Types in Elm?

In Elm, there's no native way to encode/decode a custom type. This makes send and receive custom-typed values to JS difficult. I was confused for a while and wonder how to handle simple custom types like the one below?
type MyCustomType
= A
| B
| C
Here's a quick and simple demo of how to encode and decode any custom types.
Say you have a custom type like this:
type MyCustomType
= A
| B
| C
You can encode MyCustomType directly as a string:
encodeMyCustomType : MyCustomType -> Encode.Value
encodeMyCustomType myCustomType =
Encode.string <|
case myCustomType of
A -> "A"
B -> "B"
C -> "C"
Decoding MyCustomType is slightly more involved. You need to use Decode.andThen to check which variant is found and use Decode.fail in case no valid variant is found:
Decode.string |>
Decode.andThen
(\str ->
case str of
"A" -> Decode.succeed A
"B" -> Decode.succeed B
"C" -> Decode.succeed C
_ -> Decode.fail "Invalid MyCustomType"
)

Decoding object with multiple constuctors with separated tags

I have data in form of pairs of two strings, where first is the key identifying shape of the JSON delivered as the second one.
fooooo
{"a": 123}
barrrr
{"a": 123, "b": 123}
fooooo
{"a": 123}
I would like to parse it to same data type, based on the fopooo, baasdasda1, etc :
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
deriving (Show, Generic)
In the Aeson there is a tag feature, but it seems to require presence of the tag inside the object.
Is there some way to handle it like (with tag outside)
magicDecode :: String -> Test
magicDecode "fooooo" = decodeSpecyfic Foo
magicDecode "barrrr" = decodeSpecyfic Bar
Or something similar to it?
edit1
I've currenlty mananged to handle types with records by this
test :: [(String, Text)]
test =
[ ("foooo", "Foo")
, ("barrr", "Bar")
, ("aaaa", "Lel")
]
dec :: String -> ByteString -> Test
dec t x =
fromJust $
parseMaybe
(genericParseJSON defaultOptions {sumEncoding = ObjectWithSingleField})
(object [fromJust (lookup t test) .= fromJust (decode x :: Maybe Value)])
(Issue is if thee are no constructor params :( )
You can use the UntaggedValue option:
{-# LANGUAGE DeriveGeneric #-}
module Q59916344 where
import Data.Aeson
import GHC.Generics
myOptions = defaultOptions { sumEncoding = UntaggedValue }
data Test = Foo { a :: Int, b :: Int } | Bar { a :: Int } deriving (Show, Generic)
instance FromJSON Test where
parseJSON = genericParseJSON myOptions
instance ToJSON Test where
toJSON = genericToJSON myOptions
toEncoding = genericToEncoding myOptions
As the documentation explains about UntaggedValue:
When decoding, constructors are tried in the order of definition. If some encodings overlap, the first one defined will succeed.
Demo:
*Q59916344 Q59916344> decode "{\"a\": 123}" :: Maybe Test
Just (Bar {a = 123})
*Q59916344 Q59916344> decode "{\"a\": 123, \"b\": 123}" :: Maybe Test
Just (Foo {a = 123, b = 123})
That said, you shouldn't model sum types with records, because the record accessors are partial:
*Q59916344 Q59916344> b (Foo 42 1337)
1337
*Q59916344 Q59916344> b (Bar 42)
*** Exception: No match in record selector b

How do I parse a String to a Float?

I need to consume a json source that represents floats as strings* and I can't figure out how.
It is almost easy:
Json.Decode.map String.toFloat Json.Decode.string
However, that produces a Maybe Float and I'd prefer it fail altogether if it cant decode the string.
(*) The reason for this is that the real datatype is Decimal, so "1.5" != "1.50". My application doesn't have to care though.
You can either install elm-community/json-extra and use Json.Decode.Extra.parseFloat
or just copy its implementation
fromMaybe : String -> Maybe a -> Decode.Decoder a
fromMaybe error val =
case val of
Just v ->
Decode.succeed v
Nothing ->
Decode.fail error
parseFloat : Decode.Decoder Float
parseFloat =
Decode.string |> Decode.andThen (String.toFloat >> fromMaybe "failed to parse as float")
Another option which makes fromMaybe unnecessary:
floatDecoder : Json.Decoder Float
floatDecoder =
Json.string |> Json.andThen (String.toFloat >> Maybe.withDefault 0.0 >> Json.succeed)
Just in case it helps someone else ;)

Make Json.Decode case insensitive in elm

Is there an easy way to make Json.Decode case insensitive in elm (0.18)?
decodeDepartmentDate : Json.Decode.Decoder DepartmentDate
decodeDepartmentDate =
Json.Decode.map6 DepartmentDate
(field "nameOfDay" Json.Decode.string)
(field "orderDate" Convert.datePart)
(field "mealTimeID" Json.Decode.string)
(field "mealTime" Json.Decode.string)
(field "departmentID" Json.Decode.string)
(field "department" Json.Decode.string)
I want to be able to use the same elm SPA against multiple back ends and avoid issues like this by default:
BadPayload "Expecting an object with a field named `nameOfDay` at _[11]
but instead got: {\"NameOfDay\":\"Wednesday\",\"OrderDate\":\"2018-09-05T00:00:00\",
\"MealTimeID\":\"546ccee0-e070-403e-a15b-63f4e1366054\",\"MealTime\":\"All Day\",
\"StartTime\":\"2018/06/05 05:04:38\",\"DepartmentID\":\"066a1c9f-97da-487e-b82f-f933b159c042\",
\"Department\":\"Side walk\"}"
Thanks
As far as I'm aware, there's no ready-made solution for doing so. But you can make your own!
The easiest way is probably to just generate the different casings and make your own field decoder using oneOf:
myField name decoder =
Decode.oneOf
[ Decode.field name decoder
, Decode.field (String.toLower) decoder
]
Another approach would be to decode the object as key/value pairs without decoding the values, transforming the keys and then re-encoding it to be able to use the existing JSON decoders on it:
lowerCaseKeys =
Decode.keyValuePairs Decode.value
|> Decode.map (List.map (\(key, value) -> (String.toLower key, value)))
|> Decode.map (Encode.object)
But since the value is now wrapped in a Decoder you'd have to use decodeValue on that and ultimately end up with a double-wrapped Result, which isn't very nice. I might be missing some elegant way of making this work though.
Instead it seems better to not re-encode it, but just make your own field decoder to work on the dict. This will also allow you to ignore casing on the keys you specify.
lowerCaseKeys : Decode.Decoder (Dict.Dict String Decode.Value)
lowerCaseKeys =
Decode.keyValuePairs Decode.value
|> Decode.map (List.map (\( key, value ) -> ( String.toLower key, value )))
|> Decode.map Dict.fromList
myField : String -> Decode.Decoder a -> Dict.Dict String Decode.Value -> Decode.Decoder a
myField name decode dict =
case Dict.get (String.toLower name) dict of
Just value ->
case Decode.decodeValue decode value of
Ok v ->
Decode.succeed v
Err e ->
e |> Decode.errorToString |> Decode.fail
Nothing ->
Decode.fail "missing key"
result =
Decode.decodeString (lowerCaseKeys |> Decode.andThen (myField "fOO" Decode.int)) """{ "Foo": 42 }"""
You can define a variant of field that disregards case.
fieldInsensitive : String -> Decode.Decoder a -> Decode.Decoder a
fieldInsensitive f d =
let
flow = String.toLower f
in
Decode.keyValuePairs Decode.value |> Decode.andThen
(\ l -> l |> List.filter (\(k, v) -> String.toLower k == flow)
|> List.map (\(k, v) -> v)
|> List.head
|> Maybe.map Decode.succeed
|> Maybe.withDefault (Decode.fail "field not found")
) |> Decode.andThen
(\ v -> case Decode.decodeValue d v of
Ok w -> Decode.succeed w
Err e -> Decode.fail (Decode.errorToString e)
)
This is more or less the same code as #glennsl's answer, but wrapped up in a self-contained function. The advantage is a simpler interface, the disadvantage is that if you lookup multiple fields in the same object you will be repeating work.
Note that this code makes a rather arbitrary decision if there are multiple fields with the same key up to case! For more reliable code, it might be a better idea to fail if a key exists more than once.

Defining a type alias for Hash Table in OCaml

I am trying to use atdgen and it requires that you define the types of the OCaml objects you're trying to convert to JSON in what it calls an "atd file"
So for Hashtbl.t atdgen generates code which looks like:
type ('a, 'b) bucketlist =
| Empty
| Cons of 'a * 'b * ('a, 'b) bucketlist
type ('a, 'b) tbl = ('a, 'b) Hashtbl.t = {
mutable size: int;
mutable data: ('a, 'b) bucketlist array;
mutable seed: int;
initial_size: int;
}
and the compiler throws up a:
Error: This variant or record definition does not match that of
type ('a, 'b) Hashtbl.t. Their kinds differ.
And I have no clue how to define the alias in a way atdgen would generate code that'd help me serialize the Hashtbl into a JSON. Because I verified in stdlib/Hashtbl.ml and the types defs appear like for like.
I came across this question, which looked like it could help me, but I couldn't figure out any differences in what was suggested versus what is being generated by atdgen.
Here's how my atd def looks like:
type ('a, 'b) bucketlist = [
| Empty
| Cons of ('a * 'b * ('a, 'b) bucketlist)
] <ocaml repr="classic">
type ('a, 'b) tbl <ocaml predef module="Hashtbl" t="t"> = {
size <ocaml mutable>: int;
data <ocaml mutable>: ('a, 'b) bucketlist list <ocaml repr="array">;
seed <ocaml mutable>: int;
initial_size: int;
}
If the keys of the hash table are strings or maybe ints, I recommend sticking to a JSON object and use a wrapper on the OCaml side.
If you need to support keys of arbitrary types, you probably should use an array of arrays of 2 elements because JSON doesn't offer anything better.
Here is a complete example illustrating both cases:
File table.atd:
(*
If the keys of the hash table are strings (or maybe ints),
use the standard JSON representation as an object:
*)
type 'v table_as_object =
(string * 'v) list <json repr="object">
wrap <ocaml t="(string, 'v) Table.t"
module="Table">
(*
If you need to support keys of arbitrary types,
you probably should use an array of arrays of 2 elements because JSON
doesn't offer anything better:
*)
type ('k, 'v) table_as_array =
('k * 'v) list
wrap <ocaml t="('k, 'v) Table.t"
module="Table">
type stuff = {
x: int;
}
type table_ar = (string, stuff) table_as_array
type table_obj = stuff table_as_object
File table.ml:
type ('k, 'v) t = ('k, 'v) Hashtbl.t
let of_list l =
let tbl = Hashtbl.create (2 * List.length l) in
List.iter (fun (k, v) -> Hashtbl.add tbl k v) l;
tbl
let to_list tbl =
Hashtbl.fold (fun k v l -> (k, v) :: l) tbl []
let wrap = of_list
let unwrap = to_list
File test_table.ml:
open Table_t
let main () =
let tbl = Hashtbl.create 10 in
Hashtbl.add tbl "abc" { x = 123 };
Hashtbl.add tbl "def" { x = 456 };
let json_ar = Table_j.string_of_table_ar tbl in
let json_obj = Table_j.string_of_table_obj tbl in
print_endline (Yojson.Basic.prettify json_ar);
print_endline (Yojson.Basic.prettify json_obj)
let () = main ()
Build commands:
atdgen -t table.atd
atdgen -j -j-std table.atd
ocamlfind ocamlopt -o test_table \
table.ml table_t.mli table_t.ml table_j.mli table_j.ml test_table.ml \
-package atdgen -linkpkg
Output:
$ ./test_table
[ [ "abc", { "x": 123 } ], [ "def", { "x": 456 } ] ]
{ "abc": { "x": 123 }, "def": { "x": 456 } }
I know nothing about atdgen, but it seems to me that Hashtbl.t is an abstract type. You can't define it to be the same type as a concrete record type. This is probably what the compiler means when it says the kinds are different.
# module A : sig type 'a t end =
struct type 'a t = { l : 'a list } end;;
module A : sig type 'a t end
# type 'a myt = 'a A.t = { l : 'a list };;
Error: This variant or record definition does not match that of type 'a A.t
Their kinds differ.
(To put this another way, you can't package the internal structure of a hash table into JSON without violating the abstraction layer of the Hashtbl module.)