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

200 views Asked by At

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

1

There are 1 answers

2
Dave On BEST ANSWER

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";},[])])
*)