Search code examples
jsonparsingdecodeelmreddit

ELM parse nested json


I have a json array with multiple comments which can be nested.

exemple:

[
  {
    "author": "john",
    "comment" : ".....",
    "reply": "",
  },
  {
    "author": "Paul",
    "comment" : ".....",
    "reply": [  
      {
        "author": "john",
        "comment" : "nested comment",
        "reply": [
          {
            "author": "Paul",
            "comment": "second nested comment"
          }
        ]
      },
      {
        "author": "john",
        "comment" : "another nested comment",
        "reply": ""
      }
    ]
  },
  {
    "author": "Dave",
    "comment" : ".....",
    "reply": ""
  },
]

So it's a list of comment, which every comment can have a reply with an infinite number of reply. With Json.Decode.list I can decode the first level of comment, but how do I checked if there is some reply and then parse again ?

This is a simplify version of what I'm try to do. I'm actually trying to decode reddit comments. exemple


Solution

  • Elm won't let you create a recursive record type alias, so you'll have to use a union type for Customer. You may also want a convenience function for creating a user so you can use Json.map3.

    Your example json has an oddity: Sometimes reply is an empty string and sometimes it's a list. You'll need a special decoder to turn that string into an empty list (assuming an empty list is synonymous with an empty list in this context).

    Since you have a recursive type, you need to use lazy for decoding the child comments to avoid a runtime error.

    import Html exposing (Html, text)
    import Json.Decode as Json exposing (..)
    
    
    main : Html msg
    main =
        text <| toString <| decodeString (list commentDecoder) s
    
    
    type Comment
        = Comment
            { author : String
            , comment : String
            , reply : List Comment
            }
    
    
    newComment : String -> String -> List Comment -> Comment
    newComment author comment reply =
        Comment
            { author = author
            , comment = comment
            , reply = reply
            }
    
    
    emptyStringToListDecoder : Decoder (List a)
    emptyStringToListDecoder =
        string
            |> andThen
                (\s ->
                    case s of
                        "" ->
                            succeed []
    
                        _ ->
                            fail "Expected an empty string"
                )
    
    
    commentDecoder : Decoder Comment
    commentDecoder =
        map3 newComment
            (field "author" string)
            (field "comment" string)
            (field "reply" <|
                oneOf
                    [ emptyStringToListDecoder
                    , list (lazy (\_ -> commentDecoder))
                    ]
            )
    
    
    s =
        """
    [{
      "author": "john",
      "comment": ".....",
      "reply": ""
    }, {
      "author": "Dave",
      "comment": ".....",
      "reply": ""
    }, {
      "author": "Paul",
      "comment": ".....",
      "reply": [{
        "author": "john",
        "comment": "nested comment",
        "reply": [{
          "author": "Paul",
          "comment": "second nested comment",
          "reply": ""
        }]
      }, {
        "author": "john",
        "comment": "another nested comment",
        "reply": ""
      }]
    }]
    """
    

    (Your json is also a little off in other ways: There are a few extra commas after the last parts of list and one of the reply fields is missing)