Make Json.Decode case insensitive in elm - json

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.

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"
)

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 ;)

F#: appending the value of an option to list option

I have a basic append function
let append item list = item :: list
And i have a' list option and option Some("something")
let listOption = Some []
I want to add the value "something" to listOption. How can I do it without using pattern matching and Option.get but by lifting append function?
Any help would be appreciated
You can use maybe computation expression
type MaybeBuilder() =
member this.Bind(m, f) = Option.bind f m
member this.Return(x) = Some x
let maybe = new MaybeBuilder()
let append item list = item :: list
let appendLifted item list =
maybe {
let! l = list
let! i = item
return append i l
}
[<EntryPoint>]
let main argv =
appendLifted (Some "abc") (Some [])
0
It looks like a home work...
If you want to add a value (not an option) at the head of a list option, you can simply do this which will return None if the list option is None:
let liftedAppend item optList =
optList |> Option.bind (fun list -> Some (item :: list))
liftedAppend signature is:
'a -> 'a list option -> 'a list option
But talking about lifting stricto sensu, as the signature of your append function is:
'a -> 'a list -> 'a list
the signature of the lifted function should be:
'a option -> 'a list option -> 'a list option
That means the first argument have to be an option and I guess you want to check if it's Some or None. If so attentively read other's replies.
You can use something like this, which the de-sugared Lanayx's computation expression.
let liftedAppend optItem optList =
optList |> Option.bind (fun list ->
optItem |> Option.bind (fun item -> Some (item :: list)))
This works:
listOption
|> Option.map (append 11)
|> printfn "%A" // Some [11]
but to create a lifted append:
let liftedAppend v = Option.map (append v)
listOption
|> liftedAppend 11
|> printfn "%A" // Some [11]
The signature of the functions are:
val append : 'a -> 'a list > 'a list
val liftedAppend: 'a -> 'a list option -> 'a list option
To pass both parameters as options you can use Option.map2:
let liftedAppend2 vO = vO |> Option.map2 append
listOption
|> liftedAppend2 (Some 11)
|> printfn "%A" // Some [11]
Which has signature:
val liftedAppend2: a option -> 'a list option -> 'a list option

Decode a JSON tuple to Elm tuple

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)

Why pattern matching does not throw exception in Maybe monad

My question is simple. Why wrong pattern matching does not throw exception in Maybe monad. For clarity :
data Task = HTTPTask {
getParams :: [B.ByteString],
postParams :: [B.ByteString],
rawPostData :: B.ByteString
} deriving (Show)
tryConstuctHTTPTask :: B.ByteString -> Maybe Task
tryConstuctHTTPTask str = do
case decode str of
Left _ -> fail ""
Right (Object trie) -> do
Object getP <- DT.lookup (pack "getParams") trie
Object postP <- DT.lookup (pack "postParams") trie
String rawData <- DT.lookup (pack "rawPostData") trie
return $ HTTPTask [] [] rawData
Look at tryConstuctHTTPTask function. I think that when the pattern does not match (for example "Object getP") we must get something like "Prelude.Exception", instead i get the "Nothing". I like this behavior but i am not understand why.
Thanks.
Doing pattern <- expression in a do-block, will call fail when the pattern does not match. So it is equivalent to doing
expression >>= \x ->
case x of
pattern -> ...
_ -> fail
Since fail is defined as Nothing in the Maybe monad, you get Nothing for failed pattern matches using <-.