JSON decoding a list of objects, just get the keys

Hi Elm gang! :deciduous_tree: Super enjoying the language, finally hit my first serious roadblock, wondering if anyone can help.

I’m trying to decode this JSON file: https://recipes.coopcloud.tech/recipes.json

Everything is working fine, except for the versions key for each “recipe”, which has a structure like this:

{
  "adapt_authoring": {
        ...
        "versions": [
            {
                "0.1.0+0.10.5": {
                    "app": {
                        "digest": "f6d5407",
                        "image": "3wordchant/adaptauthoring",
                        "tag": "0.10.5"
                    },
                    "db": {
                        "digest": "9a5e0d0",
                        "image": "mongo",
                        "tag": "3.7"
                    }
                }
            },
            {
                "0.2.0+0.11.1": {
                    "app": {
                        "digest": "unknown",
                        "image": "coop-cloud-chaos-patchs/adapt_authoring",
                        "tag": "0.11.1"
                    },
                    "db": {
                        "digest": "9a5e0d0",
                        "image": "mongo",
                        "tag": "3.7"
                    }
                }
            }
        ],
    }
  }
  ...
}

I’d like to parse this into the versions property of my custom App type:

type alias App =
    { name : String
    , category : String
    , repository : Maybe String
    , versions : List String
    , icon : Maybe String
    , status : Int
    , slug : String
    , default_branch : String
    , website : Maybe String
    , description : Maybe String
    }

So, discarding the contents of each JSON object, and just looking at the keys to build the List String, which in this example would look like [ "0.1.0+0.10.5", "0.2.0+0.11.1" ].

I’m trying to use Json.Decode.Value to ignore the contents of the objects in versions, but I get an error from the decoder:

versionsDecoder =
    Decode.keyValuePairs Decode.value
    |> Decode.map buildVersions


buildVersions : List ( String, Decode.Value ) -> List String
buildVersions versions =
    List.map (\( version, _ ) -> version) versions

appDecoder : Decode.Decoder App
appDecoder =
    Decode.succeed App
        |> andMap (Decode.field "name" Decode.string)
        |> andMap (Decode.field "category" Decode.string)
        |> andMap (Decode.maybe (Decode.field "repository" Decode.string))
        |> andMap (Decode.at [ "versions" ] versionsDecoder)
        |> andMap (Decode.maybe (Decode.field "icon" Decode.string))
        |> andMap (Decode.at [ "features" ] featuresDecoder)
        |> andMap (Decode.succeed "")
        |> andMap (Decode.field "default_branch" Decode.string)
        |> andMap (Decode.maybe (Decode.field "website" Decode.string))
        |> andMap (Decode.maybe (Decode.field "description" Decode.string))

appListDecoder : Decode.Decoder (List App)
appListDecoder =
    Decode.keyValuePairs appDecoder
        |> Decode.map buildApp


buildApp : List ( String, App ) -> List App
buildApp apps =
    List.map (\( slug, app ) -> { app | slug = slug }) apps

I can’t see details of the error, and if I add Debug.log inside buildVersions to try and verify the contents of versions then it doesn’t output anything to console.

When I’ve hit this kind of error before, it’s been because the JSON didn’t have the expected structure. But here, I can’t work out what’s wrong.

Any advice on either debugging the decode error, or what I should be doing to decode in this case instead? :pray:

I pushed this complete non-working example to the recipe-versions branch of my repo; NB you’ll need to use e.g. CORS Everywhere to test locally, otherwise you’ll hit a different error because of resources failing to load.

This looks approximately right to me, so I’m not sure what’s wrong from reading the code.

It would be easier to help you if you set this up in Ellie. You could inline the json in a string and use decodeString.

1 Like

I’ll give that a go, thanks @dta

Could you try changing versionsDecoder to:

versionsDecoder =
    Decode.list (Decode.keyValuePairs Decode.value)
        |> Decode.map (List.concatMap identity)
        |> Decode.map buildVersions

It feels like it should work.

It’s better to ask this kind of question on slack even though it’s a pretty long question. :slight_smile:
(discourse rules)

2 Likes

The versions field is not an object, but a list of objects, so I think the versionsDecoder should be more like

versionsDecoder : Decode.Decoder (List String)
versionsDecoder =
    Decode.list (Decode.keyValuePairs Decode.value)
        |> Decode.map buildVersions


buildVersions : List (List ( String, Decode.Value )) -> List String
buildVersions versions =
    List.concatMap (List.map (\( version, _ ) -> version)) versions

Edit: Sorry, haven’t looked at Charlon’s version in detail. I think his should work, too :+1:

1 Like

@Charlon @pit both of your suggestions worked, thanks a million! :pray:

(& roger that suggestion about Slack)

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.