UnluckyPaladin
UnluckyPaladin

Reputation: 347

Decoding a thing or a list of things in json

I'm trying to parse JSON-LD, and one of the possible constructs is

"John" : { 
  "type": "person",
  "friend": [ "Bob", "Jane" ],
}

I would like to decode into records of type

type alias Triple = 
  { subject: String, predicate: String, object: String }

so the example above becomes:

Triple "John" "type" "person"
Triple "John" "friend" "Bob"
Triple "John" "friend" "Jane"

But "friend" in the JSON object could also be just a string:

"friend": "Mary"

in which case the corresponding triple would be

Triple "John" "friend" "Mary"

Any idea?

Upvotes: 3

Views: 569

Answers (1)

Chad Gilbert
Chad Gilbert

Reputation: 36375

First, you'll need a way to list all key/value pairs from a JSON object. Elm offers the Json.Decode.keyValuePairs function for this purpose. It gives you a list of key names which you'll use for the predicate field, but you'll also have to describe a decoder for it to use for the values.

Since your values are either a string or a list of strings, you can use Json.Decode.oneOf to help. In this example, we'll just convert a string to a singleton list (e.g. "foo" becomes ["foo"]), just because it makes it easier to map over later.

stringListOrSingletonDecoder : Decoder (List String)
stringListOrSingletonDecoder =
    JD.oneOf
        [ JD.string |> JD.map (\s -> [ s ])
        , JD.list JD.string
        ]

Since the output of keyValuePairs will be a list of (String, List String) values, we'll need a way to flatten those into a List (String, String) value. We can define that function like this:

flattenSnd : ( a, List b ) -> List ( a, b )
flattenSnd ( key, vals ) =
    List.map (\val -> ( key, val )) vals

Now you can use these two functions to split up an object into a triple. This accepts a string argument which is the key to look up in your calling function (e.g. we need to look up the wrapping "John" key).

itemDecoder : String -> Decoder (List Triple)
itemDecoder key =
    JD.field key (JD.keyValuePairs stringListOrSingletonDecoder)
        |> JD.map
            (List.map flattenSnd
                >> List.concat
                >> List.map (\( a, b ) -> Triple key a b)
            )

See a working example here on Ellie.

Note that the order of keys may not match how you listed them in the input JSON, but that is just how JSON works. It's a lookup table, not an ordered list

Upvotes: 5

Related Questions