如何将嵌套的 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
我目前正在尝试使用 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