Как я могу десериализовать вложенный массив Json в словарь? - PullRequest
0 голосов
/ 26 сентября 2018

В настоящее время я пытаюсь использовать F # JsonProvider для десериализации набора объектов Json, которые я получаю от REST API.

Это уже работает для большей части части, но объекты содержат вложенный элемент, которыйможет иметь разное содержание.

Когда-то это может быть обычный JsonObject, как в

{
    "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() к словарю, который я хотел бы вернуть?

[ОБНОВЛЕНИЕ после принятого ответа]

Принятый ответ был и является правильным, однако из-за меняющегося требования мне пришлось немного подправить, поскольку существует более одного типа ограничения, представленного с несколькими свойствами.

В итоге я набрал

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 Ответ

0 голосов
/ 27 сентября 2018

Когда я решаю подобные проблемы, мне легче оставаться в строго типизированном мире как можно дольше.Если мы используем 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
...