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))
Related
supposing I have to read some data from some json files(i18n), every json file may look like:
{
"foo": "1",
"bar": "2",
...
}
I don't know how many fields this json have(it can be expanded), but it's fields look like
{
[prop: string]: string
}
besides, all the json files share the same fields.
when I try to read a value from this json via:
//a can be expanded, I'm not sure how many fileds does it have
let a = {
name: "dd",
addr: "ee",
}
//I'm confident a has a field "name"
let b = "name";
console.log(a[b]);
the error message is:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
how could I fix it?
The error you're encountering is because the keys in a is not just any string (in fact, it can only be "name" or "add"), but b can be a string of any arbitrary value. If you are very sure that b represents a key found in the object a, you can hint TypeScript as such:
let b: keyof typeof a = "name";
Attempting to assign any arbitrary string value to b will lead to an error:
// This will cause an error
let b: key typeof a = "foobar";
See proof-of-concept on TypeScript Playground.
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
]
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.
had a look for something like this, but can't find the exact issue.
I have a JSON back from server side validation that looks like:
{
"field": ["field-name"],
"messages":["message","message"]
}
What I would like to do is decode it into an elm record like
{ field: String, messages: List String }
However, I'm having trouble with the err, field field. I'm having trouble turning a single element JSON array into just a string of that element.
Is it even possible with Decode, or am I better of Decoding it into a List and then just grabbing the head from the list.
This is what I have for the decode:
valErrorDecoder : Decode.Decoder ValError
valErrorDecoder =
decode ValError
|> required "field" (Decode.list Decode.string)
|> required "messages" (Decode.list Decode.string)
Thanks for any help!
Try Decode.index, that should do the trick.
valErrorDecoder : Decode.Decoder ValError
valErrorDecoder =
decode ValError
|> required "field" (Decode.index 0 Decode.string)
|> required "messages" (Decode.list Decode.string)
You mention in a comment that a colleague suggested Decode.map. In case you're curious, here is what that (more complex) solution might look like:
firstElementDecoder : Decode.Decoder a -> Decode.Decoder a
firstElementDecoder baseDecoder = Decode.list baseDecoder
|> Decode.map List.head
|> Decode.andThen (Maybe.map Decode.succeed >> Maybe.withDefault (Decode.fail "Empty list"))
What's happening here? We begin by decoding a list of strings, then map the List.head function onto that list, giving a Decoder (Maybe String). The function
Maybe.map Decode.succeed
>> Maybe.withDefault (Decode.fail "Empty list")
takes in a Maybe and turns it into a decoder, which either succeeds (with the value of the maybe) or fails (with an "Empty list" error message). We use this function as the argument to Decode.andThen, which:
Passes the Maybe from the list decoder into the function above, getting either a Decode.succeed or a Decode.fail
Runs the returned decoder, succeeding or failing with the appropriate value.
So, yes, Decode.index 0 is easier! But it may be of interest to see the longer solution, too :-)
I have the following Map Object :
val ad = node.asInstanceOf[Map[String, Any]]
and a printed example of ad object is:
ListMap(userId -> 1234, userName -> Jason, location -> ListMap(longitude -> -79.234264, latitude -> 37.2395), email -> Some(jason#yahoo.com))
I am trying to access the nested fields of the location field and cast it as a double. I have the following:
ad.get("location") match {
case Some(i) => i match {
case j: Map[Any, Any] => j("longitude").asInstanceOf[Double]
}
My question is there another more graceful/cleaner syntax way to get the nested objects of the location field?
It depends on the concept of "graceful/cleaner" because that kind of data structure doesn't smell good.
Anyway, the inner pattern match can be merged with the outer one, like this:
ad.get("location") match {
case Some(i: Map[Any,Any]) => i("longitude").asInstanceOf[Double]
case _ => // do nothing
}
You can't really do anything graceful with a type like Map[String, Any]. I would consider using a more type-safe Json library in the first place, such as Circe.
Otherwise, it depends on what you know statically about your Map. If you know for certain that "location" exists in the Map, and that it always contains a nested Map with the "longitude" key, and that "longitude" is indeed always a Double, then it's OK to return a Double. Otherwise, you should consider returning an Option[Double] or something like Either[String, Double]. For example:
ad.get("location").flatMap {
case location: Map[String, _] => Try(location("longitude").asInstanceOf[Double]).toOption
case _ => None
}
This will return a None if the desired value cannot be obtained for whatever reason: "location" doesn't exist, "location" is not a map, the "location" map does not contain "longitude", etc.