代码之家  ›  专栏  ›  技术社区  ›  Markus Deibel

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

  •  1
  • Markus Deibel  · 技术社区  · 6 年前

    我目前正在尝试使用F#JsonProvider反序列化从restapi接收的一组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>        
    

    现在的问题是:我怎样才能从 parsedInfo.Constraints.JsonValue.Properties()

    但是,由于需求不断变化,我不得不稍微调整一下,因为有多个约束类型用多个属性表示。

    我最终得到了

    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")
    

    我现在可以将所有内容作为字符串返回,因为将使用结果的部分必须处理转换数据。

    1 回复  |  直到 6 年前
        1
  •  3
  •   CaringDev    6 年前

    在处理这类问题时,我发现尽可能长时间地呆在强类型的世界中更容易。如果我们使用 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