如何将嵌套的 Json 数组反序列化为字典?

How can I deserialize a nested Json array to a dictionary?

我目前正在尝试使用 F# JsonProvider 反序列化我从 REST API.

接收到的一组 Json 对象

这已经适用于大部分内容,但对象包含可以具有不同内容的嵌套项。

一旦它可以是一个普通的Json对象,如

{
    "dataType": "int",
    "constraints": {
        "min": 0,
        "max": 650,
        "scaling": -10,
        "steps": 1
    },
    "defaultValue": "string",
}

但它也可以是一个多维数组,如

{
    "dataType": "enum",
    "constraints": {
        "names": [["n.a.", 1],
        ["OK", 4],
        ["High Warn", 6],
        ["Too Low", 7],
        ["Too High", 8],
        ["Low Warn", 9]]
    },
    "defaultValue": "4",
}

在我提供的类型中,我想像这样公开约束

type Description (descriptionJsonIn: string) =
    let parsedInfo = DescriptionProvider.Parse(descriptionJsonIn)

    let parsedConstraints = 
        match parsedInfo.Constraints.JsonValue.Properties().[0].ToString() with
        //| "names" -> 
            //parsedInfo.Constraints.JsonValue.Properties
            //|> Array.map (fun x -> x.ToValueTuple)
            //|> dict<string,string>
        | "min" -> 
            parsedInfo.Constraints.JsonValue.Properties()
            |> Seq.map (fun (k,v) -> k,v.AsString())
            |> dict
        | "maxLength" -> 
            parsedInfo.Constraints.JsonValue.Properties()
            |> Seq.map (fun (k,v) -> k,v.AsString())
            |> dict
        | _ -> dict["",""]

    member __.DataType = parsedInfo.DataType
    member __.DefaultValue = parsedInfo.DefaultValue

    member __.Constraints = parsedConstraints

我感觉解决方案应该类似于(无效)

| "names" -> 
    parsedInfo.Constraints.JsonValue.Properties()
    |> Seq.map (fun (x) -> fst(x.ToValueTuple()).ToString(), snd(x.ToValueTuple()).ToString() )
    |> dict<string,string>        

但我对 F# 语法了解不够,无法继续搜索。我一直得到相同的结果:(

现在的问题:如何从 parsedInfo.Constraints.JsonValue.Properties() 得到我想要 return 的词典?

[接受答案后更新]

接受的答案过去和现在都是正确的。 但是,由于要求不断变化,我不得不稍微调整一下,因为有不止一种约束类型用多个属性表示。

我最终得到了

let objectConstraints =
    let c = parsedVariableDescription.Constraints
    if c.ToString().Contains("\"min\":") 
    then
        [
            "Min", c.Min
            "Max", c.Max
            "Scaling", c.Scaling
            "Steps", c.Steps
        ]
        |> Seq.choose (fun (n, v) -> v |> Option.map (fun v -> n, v.ToString()))
    else if c.ToString().Contains("\"maxLen\":") 
    then
        [
            "RegExpr", c.RegExpr
            "MaxLen", c.MaxLen
        ]
        |> Seq.choose (fun (n, v) -> v |> Option.map (fun v -> n, v.ToString()))
    else
        Seq.empty


let namedConstraints =
    parsedVariableDescription.Constraints.Names
    |> Seq.map (fun arr ->
        match arr.JsonValue.AsArray() with
        | [| n; v |] -> n.AsString(), v.AsString()
        | _ -> failwith "Unexpected `names` structure")

目前我同意 return 将所有内容都转换为字符串,因为无论如何,使用结果的部分必须处理数据转换。

在处理这类问题时,我发现尽可能长时间地呆在强类型的世界中更容易。如果我们从一开始就使用 JsonValue.Properties,那么类型提供程序就没有多大价值。如果数据过于动态,我宁愿使用另一个 JSON 库,例如Newtonsoft.Json.

首先,让我们定义一些常量:

open FSharp.Data

let [<Literal>] Object = """{
        "dataType": "int",
        "constraints": {
            "min": 0,
            "max": 650,
            "scaling": -10,
            "steps": 1
        },
        "defaultValue": "string"
    }"""

let [<Literal>] MultiDimArray = """{
        "dataType": "enum",
        "constraints": {
            "names": [
                ["n.a.", 1],
                ["OK", 4],
                ["High Warn", 6],
                ["Too Low", 7],
                ["Too High", 8],
                ["Low Warn", 9]]
        },
        "defaultValue": "4"
    }"""

let [<Literal>] Sample = "["+Object+","+MultiDimArray+"]"

然后我们可以使用它来创建提供的类型:

type RawDescription = JsonProvider<Sample, SampleIsList = true>

并使用它来提取我们想要的值:

type Description(description) =
    let description = RawDescription.Parse(description)

    let objectConstraints =
        let c = description.Constraints
        [
            "Min", c.Min
            "Max", c.Max
            "Scaling", c.Scaling
            "Steps", c.Steps
        ]
        // Convert (name, value option) -> (name, value) option
        // and filter for `Some`s (i.e. pairs having a value)
        |> Seq.choose (fun (n, v) -> v |> Option.map (fun v -> n, v))

    let namedConstraints =
        description.Constraints.Names
        |> Seq.map (fun arr ->
            match arr.JsonValue.AsArray() with
            | [| n; v |] -> n.AsString(), v.AsInteger()
            | _ -> failwith "Unexpected `names` structure")

    member __.DataType = description.DataType
    member __.DefaultValue =
        // instead of this match we could also instruct the type provider to
        // not infer types from values: `InferTypesFromValues = false`
        // which would turn `DefaultValue` into a `string` and all numbers into `decimal`s
        match description.DefaultValue.Number, description.DefaultValue.String with
        | Some n, _ -> n.ToString()
        | _, Some s -> s
        | _ -> failwith "Missing `defaultValue`"

    // Map<string,int>
    member __.Constraints =
        objectConstraints |> Seq.append namedConstraints
        |> Map

然后,用法如下所示:

// map [("Max", 650); ("Min", 0); ("Scaling", -10); ("Steps", 1)]
Description(Object).Constraints

// map [("High Warn", 6); ("Low Warn", 9); ("OK", 4); ("Too High", 8); ("Too Low", 7); ("n.a.", 1)
Description(MultiDimArray).Constraints