F # deserialize JSON for string or node

I have some JSON data that I am trying to deserialize that looks like this:

{[{ "node":"xyz", "type":"string" }, { "node":{ "moredata":"values", "otherdata":"values2" }, "type":"node" }]} 

I want to be able to serialize this to an F # class, but it currently fails because the node field can change the type. Does anyone know a good way to handle this? I assume that I want the node field to be something like a JsonNode type? I am currently using Newtonsoft, but opening other libraries to use if one of them has a better solution.

EDIT: Unfortunately, I cannot use type providers, because I want this to be possible to use with C #.

+5
source share
4 answers

Here is the current solution I'm using:

 type Object = { node : Newtonsoft.Json.Linq.JToken ``type`` : string } let data = JsonConvert.DeserializeObject<Object[]>(jsonStr) 

This, at least, allows me to load an object and have some type safety, without losing any data.

But my ideal solution would allow me something like node: ICommonInterface And then the base type will be the type for each data type.

0
source

You can use the JSON Type Provider . It understands the heterogeneous types of node. Here's the JSON from OP (fixed, so this is a valid JSON) used as a pattern to determine the type:

 open FSharp.Data type MyJson = JsonProvider<"""{ "arr": [ { "node": "xyz", "type": "string" }, { "node": { "moredata": "values", "otherdata": "values2" }, "type": "node" } ] }"""> 

Now you can create a value of this type by calling MyJson.Parse , but if you want to look only at the JSON used as an example from which the type is derived, you can also use GetSample :

 let json = MyJson.GetSample() 

Now you can start exploring the data. Here's the FSI session:

 > json.Arr;; val it : JsonProvider<...>.Arr [] = [|{ "node": "xyz", "type": "string" }; { "node": { "moredata": "values", "otherdata": "values2" }, "type": "node" }|] > json.Arr |> Array.map (fun x -> x.Node);; val it : JsonProvider<...>.StringOrNode [] = [|"xyz"; { "moredata": "values", "otherdata": "values2" }|] 

As you can see, each Node represents a StringOrNode value, which can be accessed as follows:

 > json.Arr |> Array.map (fun x -> x.Node.Record) |> Array.choose id;; val it : JsonProvider<...>.Node [] = [|{ "moredata": "values", "otherdata": "values2" }|] 

Since x.Node.Record is an option , you can only select Some with Array.choose .

You can get the lines in the same way:

 > json.Arr |> Array.map (fun x -> x.Node.String) |> Array.choose id;; val it : string [] = [|"xyz"|] 
+4
source

Perhaps you can fit something like this:

 open Newtonsoft.Json open Newtonsoft.Json.Linq open System.Linq let json = """{ "arr": [ { "node": "xyz", "type": "string" }, { "node": { "moredata": "values", "otherdata": "values2" }, "type": "node" } ] }""" type Simple = { Node : string Type : string } JObject.Parse(json).["arr"] .Children() .Select(fun (x:JToken) -> {Node = string (x.ElementAt 0) ; Type = string (x.ElementAt 1)}) .ToList() .ForEach(fun x -> printfn "Type: %s\nNode:\n%s" x.Type x.Node) 

Print

 Type: "type": "string" Node: "node": "xyz" Type: "type": "node" Node: "node": { "moredata": "values", "otherdata": "values2" } 

Link: https://dotnetfiddle.net/3G1hXl

But it is in C # style. Not suitable for F #

+2
source

Have you looked at the FSharp.Data JSON parser (not a type provider, but an analyzer)? If you yourself define a class, you can use the parser to repeat the properties and populate the class.

http://fsharp.imtqy.com/FSharp.Data/library/JsonValue.html

Here is a working example.

 open System open FSharp.Data open FSharp.Data.JsonExtensions let json = """{ "arr": [ { "node": "xyz", "type": "string" }, { "node": { "moredata": "values", "otherdata": "values2" }, "type": "node" } ] }""" type Node = | String of string | Complex of string*string with static member Parse (json:JsonValue) = match json with | JsonValue.String (s) -> String(s) | JsonValue.Record (r) -> r |> Map.ofArray // Map<string,JsonValue> |> fun m -> Complex(m.["moredata"].AsString(),m.["otherdata"].AsString()) | _ -> raise (new Exception("Can't parse")) type Simple = { Node:Node; Type:string} with static member Parse (json:JsonValue) = // ideally we'd use json?node and json?type to access the properties, but since type is a reserved word. {Node=Node.Parse(json?node); Type=json.GetProperty("type").AsString()} [<EntryPoint>] let main argv = let s= json |> JsonValue.Parse |> fun j -> seq { for v in j?arr do yield Simple.Parse v} |> Array.ofSeq printfn "%A" s 0 

Output:

 [|{Node = String "xyz"; Type = "string";}; {Node = Complex ("values","values2"); Type = "node";}|] 
+2
source

All Articles