F#将JSON反序列化为字符串或节点

时间:2015-08-26 12:32:26

标签: json f# json.net

我有一些JSON数据,我试图反序列化,看起来像这样:

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

我希望能够将其序列化为F#类,但它当前失败,因为节点字段可以更改类型。有谁知道处理这个的好方法?我假设我希望节点字段类似于JsonNode类型? 我目前正在使用Newtonsoft,但如果在其中一个中有更好的解决方案,则可以使用其他库。

编辑:不幸的是,我不能使用类型提供程序,因为我希望它可以在C#中使用。

4 个答案:

答案 0 :(得分:5)

您可以使用JSON Type Provider。它了解异构节点类型。这是来自OP的JSON(已更正,因此它是有效的JSON),用作定义类型的示例:

open FSharp.Data

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

您现在可以通过调用MyJson.Parse来创建该类型的值,但如果您只想查看用作推断类型的示例的JSON,您还可以使用GetSample

let json = MyJson.GetSample()

现在您可以开始探索数据了。这是一场FSI会议:

> 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"
}|]

如您所见,每个Node都是StringOrNode值,您可以像这样访问:

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

由于x.Node.Recordoption,您只能选择Some Array.choose的那些。

你可以用同样的方式获得字符串:

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

答案 1 :(得分:2)

你看过FSharp.Data JSON解析器(不是类型提供者而是解析器)吗?如果您自己定义类,则可以使用解析器迭代属性并填充类。

http://fsharp.github.io/FSharp.Data/library/JsonValue.html

这是一个有效的例子。

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 

输出:

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

答案 2 :(得分:1)

也许你可以适合这样的事情:

if (!EmailValidator.getInstance().isValid(email)) {
    // ...
}

打印:

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)

链接:https://dotnetfiddle.net/3G1hXl

但这是C#的风格。不适合F#

答案 3 :(得分:0)

以下是我目前使用的解决方案:

type Object = {
    node : Newtonsoft.Json.Linq.JToken
    ``type`` : string
}

let data = JsonConvert.DeserializeObject<Object[]>(jsonStr)

这至少允许我加载对象并具有一些类型安全性,同时不会丢失任何数据。

但是我理想的解决方案可以让我拥有类似node:ICommonInterface的东西 然后底层类型将是每种数据类型的类型。