jsonf#treemultiway-tree

Decode JSON Multiway Tree into an F# Multiway Tree Discriminated Union


I have the following JSON data in a documentdb and I would like to parse this into an F# multiway tree discriminated union

"commentTree": {
    "commentModel": {
        "commentId": "",
        "userId": "",
        "message": ""
      },
      "forest": []
    }

F# multiway discriminated union

type public CommentMultiTreeDatabaseModel = 
| CommentDatabaseModelNode of CommentDatabaseModel * list<CommentMultiTreeDatabaseModel>

where CommentMultiTreeDatabaseModel is defined as

type public CommentDatabaseModel =
{ commentId : string
  userId : string
  message : string
}

I am referencing Fold / Recursion over Multiway Tree in f# extensively. I am not sure where to begin to parse such a JSON structure into an F# multiway tree. Any suggestions will be much appreciated. Thanks


Solution

  • One way to think about this is by looking at what data you need in order to construct a CommentMultiTreeDatabaseModel. It needs a CommentDatabaseModel and a list of CommentMultiTreeDatabaseModel. So we need to write the following two functions:

    let parseComment (input : JSON) : CommentDatabaseModel =
        ...
    
    let parseTree (input : JSON) : CommentMultiTreeDatabaseModel =
        ...
    

    But wait, the parseTree function is the one we're trying to write right now! So instead of writing a new function, we just mark our current function with the rec keyword and have it call itself where needed.

    Below is a rough example of how it could be done. The key thing to look at is parseTree which builds up the data by recursively calling itself. I've represented the JSON input data with a simple DU. A library like Chiron can produce something like this.

    Note that this code parses all of the JSON in one go. Also, it's not tail-recursive, so you'll have to be careful with how deep your tree structure is.

    [<RequireQualifiedAccess>]
    type JSON =
        | String of string
        | Object of (string * JSON) list
        | Array of JSON list
    
    type public CommentDatabaseModel = {
        commentId : string
        userId : string
        message : string
    }
    
    type public CommentMultiTreeDatabaseModel = 
        | CommentDatabaseModelNode of CommentDatabaseModel * list<CommentMultiTreeDatabaseModel>
    
    
    let parseComment = function
        | JSON.Object [ "commentId", JSON.String commentId; "userId", JSON.String userId; "message", JSON.String message ] ->
            {
                commentId = commentId
                userId = userId
                message = message
            }
        | _ -> failwith "Bad data"
    
    let rec parseTree (input : JSON) : CommentMultiTreeDatabaseModel =
        match input with
        | JSON.Object [ "commentModel", commentModel; "forest", JSON.Array forest ] ->
            CommentDatabaseModelNode (parseComment commentModel, List.map parseTree forest)
        | _ -> failwith "Bad data"
    
    let parse (input : JSON) : CommentMultiTreeDatabaseModel =
        match input with
        | JSON.Object [ "commentTree", commentTree ] ->
            parseTree commentTree
        | _ -> failwith "Bad data"
    
    
    let comment text =    
        JSON.Object [
            "commentId", JSON.String ""
            "userId", JSON.String ""
            "message", JSON.String text
        ]
    
    let sampleData =
        JSON.Object [
            "commentTree", JSON.Object [
                "commentModel", comment "one"
                "forest", JSON.Array [
                    JSON.Object [
                        "commentModel", comment "two"
                        "forest", JSON.Array []
                    ]
    
                    JSON.Object [
                        "commentModel", comment "three"
                        "forest", JSON.Array []
                    ]
                ]
            ]
        ]
    
    parse sampleData
    
    (*
    val it : CommentMultiTreeDatabaseModel =
      CommentDatabaseModelNode
        ({commentId = "";
          userId = "";
          message = "one";},
         [CommentDatabaseModelNode ({commentId = "";
                                     userId = "";
                                     message = "two";},[]);
          CommentDatabaseModelNode ({commentId = "";
                                     userId = "";
                                     message = "three";},[])])
    *)