Elm decode json in different format - json

I try to decode some json in elm.
The object I´m receiving could be in two different shapes.
First case:
{
...
"ChipId": "NotSet"
...
}
Second Case:
{
...
"ChipId": {
"Item": "0000000000"
},
...
}
So the first one can easily be decoded with field "ChipId" string but in case it´s the complex object it fails.
I already tried it with Decode.andThen but I couldn't solve it.
Thank you for your help!
Update 1 - decoder that fails
The way I tried was by using a Maybe.
chipIdDecoder : Decoder String
chipIdDecoder =
let
chipIdIdDecoder : Decoder String
chipIdIdDecoder =
field "ChipId" (field "Fields" (firstElementDecoder string))
chooseChipId : Maybe String -> Decoder String
chooseChipId c =
case c of
Just id ->
case id of
"ChipId" ->
chipIdIdDecoder
_ ->
succeed ""
Nothing ->
fail "Chip Id is invalid"
in
nullable (field "ChipId" string)
|> andThen chooseChipId
I suppose the problem here is that Maybe expects something or null and not something or something else. ^^

tl;dr: use oneOf
A good approach to writing json decoders in Elm is to start from the smallest parts, write decoders that decode each of those parts independently, then move up to the next level and write a decoder for that by putting together the smaller pieces you've already made.
Here, for example, I would start by writing a decoder to handle the two possible forms of "ChipId" separately. The first is just a string, which of course comes out of the box with elm/json, so that's easy enough. The other is an object with a single field, which we'll just decode into a simple String:
chipIdObjectDecoder : Decoder String
chipIdObjectDecoder =
field "Item" string
Then we need to put them together, which seems like the part you're struggling most with. Here the oneOf function comes to our rescue, whose description says:
Try a bunch of different decoders. This can be useful if the JSON may come in a couple different formats.
Sounds like exactly what we need! To try both the string decoder and our chipIdObjectDecoder we can write:
eitherStringOrChipIdObject : Decoder String
eitherStringOrChipIdObject =
oneOf [ string, chipIdObjectDecoder ]
And then finally we need to decode the "ChipId" field itself:
field "ChipId" eitherStringOrChipIdObject
All this put together in a single function:
chipIdDecoder : Decoder String
chipIdDecoder =
let
chipIdObjectDecoder : Decoder String
chipIdObjectDecoder =
field "Item" string
eitherStringOrChipIdObject : Decoder String
eitherStringOrChipIdObject =
oneOf [ string, chipIdObjectDecoder ]
in
field "ChipId" eitherStringOrChipIdObject
Or to simplify it a little bit, since the above is rather verbose:
chipIdDecoder : Decoder String
chipIdDecoder =
let
chipIdObjectDecoder =
field "Item" string
in
field "ChipId" (oneOf [ string, chipIdObjectDecoder ])
As a last note, since it's not clear whether your code is overly simplified. If the "ChipId" object cannot be reduced to a simple string, you'll have to use a common type that can hold both a String and a ChipIdObject, and map the decoded values to that common type. eitherStringOrChipIdObject could then be something like this:
type alias ChipIdObject = { ... }
type ChipId
= ChipIdString String
| ChipIdObject ChipIdObject
eitherStringOrChipIdObject : Decoder ChipId
eitherStringOrChipIdObject =
oneOf
[ string |> map ChipIdString
, chipIdObjectDecoder |> map ChipIdObject
]

Related

How do I decode custom types from two json fields?

I'm building an application with postgrest (backend) and elm (frontend) and right now I'm stuck writing a decoder.
I don't quite understand how to decode to my specific type instead of the base type decoders like int and string.
When I take a look at how string is implemented (line 73 to 75) it's just a call to Elm.Kernel.Json.decodeString which in turn is in the js base of elm.
My JSON looks something like this:
{ "id" : 1
, "parent_id" : 1
, "body" : "text of my body"
}
OR
{ "id" : 1
, "parent_id" : 1
, "sub_parent_id" : 2
}
Is it possible to decode something like that into a single record type (Step) which contains a custom type with multiple constructors to match the two different fields (sub_parent_id and body)
My decoder looks like this but doesn't compile:
import Api.Parent.Step.Types exposing ( Step, StepContent )
import Json.Decode exposing (..)
import Json.Decode.Pipeline exposing (..)
decoder : Decoder Step
decoder =
succeed Step
|> required "id" int
|> oneOf
[ field "stepContent" stepBodyDecoder
, field "stepContent" subStepDecoder
]
stepBodyDecoder : Decoder StepContent
stepBodyDecoder =
succeed StepContent
|> required "body" string
subStepDecoder : Decoder StepContent
subStepDecoder =
succeed StepContent
|> required "sub_parent_id" decoder
Any my Types:
module Api.Parent.Step.Types exposing ( Step, StepContent )
type StepContent
= StepBody String
| SubStep Step
type alias Step =
{ id : Int
, stepContent : StepContent
}
JSON decode pipelines expects succeed to be passed a function, and StepContent is not a function, but a type. Variant constructors are functions however, and if you look at the compiler error it suggests the right fix (although that's somewhat coincidental since it just suggest based on similar names):
I cannot find a `StepContent` variant:
28| succeed StepContent
^^^^^^^^^^^
These names seem close though:
StepBody
Step
OneOf
SubStep
StepBody and SubStep are the ones you should use instead. stepBodyDecoder will work with just that change, and will get you at least one step further with subStepDecoder, but the type and decoder otherwise don't match the JSON. sub_parent_id is a number, not an object, so it seems like SubStep should take an Int instead of a Step. You can then possibly construct a separate hierarchical data structure in a subsequent step.
The solution is written in the types (as often)
oneOf : List (Decoder a) -> Decoder a
So we have to supply a list of Decoder StepContent, which are the decoder functions.
And as per the relevant comment, to build a StepContent value, we need to form either constructor : a StepBody or a SubStep.
import Api.Parent.Step.Types exposing ( Step, StepContent )
import Json.Decode exposing (..)
import Json.Decode.Pipeline exposing (..)
decoder : Decoder Step
decoder =
succeed Step
|> required "id" int
|> required "stepContent" (oneOf [ stepBodyDecoder, subStepDecoder ] )
stepBodyDecoder : Decoder StepContent
stepBodyDecoder =
succeed StepBody
|> required "body" string
subStepDecoder : Decoder StepContent
subStepDecoder =
succeed SubStep
|> required "sub_parent_id" decoder

How do I unpack a JSON value into a tagged union type?

I've got some JSON coming from firebase that looks like this
{
"type": "added",
"doc": {
"id": "asda98j1234jknkj3n",
"data": {
"title": "Foo",
"subtitle": "Baz"
}
}
}
Type can be one of "added", "modified" or "removed". Doc contains an id and a data field. The data field can be any shape and I am able to decode that properly.
I want to use union types to represent these values like so,
type alias Doc data =
(String, data)
type DocChange doc
= Added doc
| Modified doc
| Removed doc
Here the Doc type alias represents the value contained in the doc field in the JSON above. DocChange represents the whole thing. If the type is say "added", then the JSON must decode into Added doc and so on. I don't understand how to decode union types.
I think the andThen function from Json.Decode looks like what I need, but I am unable to use it correctly.
First of all, it seems like you want to constrain the doc parameter of DocChange to a Doc, so you should probably define it like this instead:
type DocChange data
= Added (Doc data)
| Modified (Doc data)
| Removed (Doc data)
Otherwise you'll have to repeatedly specify DocChange (Doc data) in your functions type annotations which quickly gets annoying, and worse the more you nest it. In any case, I've continued using the types as you defined them:
decodeDocData : Decoder DocData
decodeDocData =
map2 DocData
(field "title" string)
(field "subtitle" string)
decodeDoc : Decoder data -> Decoder (Doc data)
decodeDoc dataDecoder =
map2 Tuple.pair
(field "id" string)
(field "data" dataDecoder)
decodeDocChange : Decoder data -> Decoder (DocChange (Doc data))
decodeDocChange dataDecoder =
field "type" string
|> andThen
(\typ ->
case typ of
"added" ->
map Added
(field "doc" (decodeDoc dataDecoder))
"modified" ->
map Modified
(field "doc" (decodeDoc dataDecoder))
"removed" ->
map Removed
(field "doc" (decodeDoc dataDecoder))
_ ->
fail ("Unknown DocChange type: " ++ typ)
)
The trick here is to decode "type" first, then use andThen to switch on it and choose the appropriate decoder. In this case the shape is identical across "types", but it may not be and this pattern gives the flexibility to handle diverging shapes as well. It could be simplified to just selecting the constructor and keeping the rest of the decoding common if you're absolutely sure they won't diverge.

Elm decode json object with array

Im totally new in ELM. Im trying to get data from get response.
Now im trying to make decoder for that. I have json like this:
{
data: [
{
"price" = 300.5
},
{
"price" = 1005
}
]
}
All i need is get lowest price and return it. (At least return each price).
Now i stacked on Decode.index.
priceDecoder : Decode.Decoder String
priceDecoder =
Decode.field "data" Decode.list
What should i do next?
There are several problems with the question itself. First, you're not posting the error you get. The error I get is this:
The 2nd argument to `field` is not what I expect:
25| Decode.field "data" Decode.list
^^^^^^^^^^^
This `list` value is a:
Decode.Decoder a -> Decode.Decoder (List a)
But `field` needs the 2nd argument to be:
Decode.Decoder a
Second, the JSON you've posted is not valid, data should be enclosed in quotes and properties and values should be separated by :, not =.
Thirdly, the type of priceDecoder seems wrong since the JSON contains no String data, or you're not specifying that you also want to convert the Float to a String. I will assume the type is just wrong.
So, the error you (or at least I) get says that list is a function Decoder a -> Decoder (List a), while it expects just a Decoder a value. This is because list expects a Decoder a to be passed to it, which it will use to decode each item in the list.
The decoder we'll use is Decode.field "price" Decode.float, which will decode the "price" field of the object as a Float.
I'll also change the type of priceDecoder from Decoder String to Decoder (List Float), since the price is a Float and we're decoding a List of them, not just getting the first or last value or something like that. I assume that's what you want since you say "at least return each price'.
The priceDecoder we get then is:
priceDecoder : Decode.Decoder (List Float)
priceDecoder =
Decode.field "data" (Decode.list (Decode.field "price" Decode.float))

Elm: decode local JSON file into dict

I'm working on my first Elm app.
I want to use a local JSON file as a lookup table. The file matches weather conditions (a string) to suggested clothing (a list).
EDIT (clarifying the question): Based on some earlier SO questions from 2015 I've tried using Http.get to get the contents and then Decode.dict to create the Dict. It seems strange to use an Http module to ingest a local file. Is this methodology, including the Http.send I use below correct in Elm?
I am also having a hard time finding an example of a decoder that would work for a JSON file like the one I have. Any pointers would be appreciated.
JSON
{
"male,10,overcast,light wind...": ["Winter Cap", "Light Jacket"...],
"female,30,partly cloudy...": ["Winter Cap", "Sunglasses", ...]
}
CODE
type alias ClothingDict =
Dict String List
clothingUrl: String
clothingUrl =
"./clothing.json"
getClothingDict : Cmd Msg
getClothingDict =
let
url = clothingUrl
in
Http.send SetClothing (Http.get url decodeClothingResponse)
type alias ClothingResponse =
{ clothingOptions: ClothingDict }
decodeClothingResponse : Decoder ClothingResponse
decodeClothingResponse =
Decode.dict ClothingResponse
Decode.dict or dict takes a Decoder in order to handle decoding the keys of the Dict. dict automatically extracts the keys as strings.
I converted the code to:
decodeClothingResponse : Decoder (Dict String (List String))
decodeClothingResponse =
dict (list string)
(list string) is a decoder that will decoder a list of string from json in to a List String

Parsing out a portion of a JSON file and seeing the data

I found this F# JSON parsing - How to get property using complex path (consisting of several propery names) (I shortened the file string). I am wondering how to show the actual data from the json file in the FSI window?
let soilTest = JsonValue.Load("c:\\,,,soiltest.json")
let query soilTest=
let node = JObject.Parse soilTest
node.SelectToken "person[0].name" |> string
Here is the beginning of the json file:
{
"person": [
{
"name": "John Doe",
"date": "December 1, 2015",
.....
I looked at the link that Mark Seemann provided to see how to get the data out of the json file and be shown in the console.
However when putting
Console.WriteLine(name)
I get this warning:
warning FS0020: This expression should have type 'unit', but has type 'string'. Use 'ignore' to discard the result of the expression, or 'let' to bind the result to a name.
val query : soilTest:string -> unit
Tried |>ignore at the end of console.writeline and I get val query : soulTest:string -> unit
How do you do a let statement from this point to bind the data to it?
soilTest is a of type JsonValue but you're trying to parse it as string, hence the error. Please adjust the path for your environment, otherwise this works for me:
let soilTest = JsonValue.Load(#"c:\tmp\test.json")
let qry (soilTest:JsonValue) =
let data = JObject.Parse (soilTest.ToString())
data.SelectToken "person[0].name" |> string
qry soilTest
//val it : string = "John Doe"
There might be better ways to work with. Json and xml are the bane of my existence...
Edit: e.g. you can access the properties directly, like this:
let soilTest = JsonValue.Load(#"c:\tmp\test.json")
soilTest.["person"].[0].["name"].AsString()
//val it : string = "John Doe"