如何使用 JSON.NET 反序列化为嵌套/递归字典和列表?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5546142/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 15:26:04  来源:igfitidea点击:

How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?

.netjsonjson.netdeserialization

提问by dongryphon

I need to deserialize a complex JSON blob into standard .NET containers for use in code that is not aware of JSON. It expects things to be in standard .NET types, specifically Dictionary<string, object>or List<object>where "object" can be primitive or recurse (Dictionary or List).

我需要将复杂的 JSON blob 反序列化为标准 .NET 容器,以便在不知道 JSON 的代码中使用。它期望的事情是在标准的.NET类型,具体地Dictionary<string, object>List<object>其中“对象”可以是原语或递归(词典或列表)。

I cannot use a static type to map the results and JObject/JToken don't fit. Ideally, there would be some way (via Contracts perhaps?) to convert raw JSON into basic .NET containers.

我不能使用静态类型来映射结果并且 JObject/JToken 不适合。理想情况下,会有某种方式(也许通过合同?)将原始 JSON 转换为基本的 .NET 容器。

I've search all over for any way to coax the JSON.NET deserializer into creating these simple types when it encounters "{}" or "[]" but with little success.

当 JSON.NET 反序列化器遇到“{}”或“[]”时,我一直在寻找任何方法来诱使它创建这些简单类型,但收效甚微。

Any help appreciated!

任何帮助表示赞赏!

采纳答案by dongryphon

You cannot do what I was asking. At least not as far as I can tell after MUCH research. I had to edit the source of Json.NET.

你不能按照我的要求去做。至少在经过大量研究后我可以说不是。我不得不编辑 Json.NET 的源代码。

回答by Brian Rogers

If you just want a generic method that can handle any arbitrary JSON and convert it into a nested structure of regular .NET types (primitives, Lists and Dictionaries), you can use JSON.Net's LINQ-to-JSON APIto do it:

如果您只想要一个可以处理任意 JSON 并将其转换为常规 .NET 类型(原语、列表和字典)的嵌套结构的通用方法,您可以使用 JSON.Net 的LINQ-to-JSON API来做到这一点:

using System.Linq;
using Newtonsoft.Json.Linq;

public static class JsonHelper
{
    public static object Deserialize(string json)
    {
        return ToObject(JToken.Parse(json));
    }

    private static object ToObject(JToken token)
    {
        switch (token.Type)
        {
            case JTokenType.Object:
                return token.Children<JProperty>()
                            .ToDictionary(prop => prop.Name,
                                          prop => ToObject(prop.Value));

            case JTokenType.Array:
                return token.Select(ToObject).ToList();

            default:
                return ((JValue)token).Value;
        }
    }
}

You can call the method as shown below. objwill either contain a Dictionary<string, object>, List<object>, or primitive depending on what JSON you started with.

您可以调用该方法,如下所示。 obj要么含有Dictionary<string, object>List<object>或者基本取决于你开始使用JSON。

object obj = JsonHelper.Deserialize(jsonString);

回答by Anish Patel

One way to deserialize a json string recursively into dictionaries and lists with JSON.NET is to create a custom json converter class that derives from the JsonConverterabstract class provided by JSON.NET.

使用 JSON.NET 将 json 字符串递归反序列化为字典和列表的一种方法是创建一个自定义 json 转换器类,该类派生自JsonConverterJSON.NET 提供的抽象类。

It is in your derived JsonConverterwhere you put the implementation of how an object should be written to and from json.

在您的派生文件中JsonConverter,您将实现如何将对象写入 json 和从 json 写入。

You can use your custom JsonConverterlike this:

您可以JsonConverter像这样使用您的自定义:

var o = JsonConvert.DeserializeObject<IDictionary<string, object>>(json, new DictionaryConverter());

Here is a custom JsonConverter I have used with success in the past to achieve the same goals as you outline in your question:

这是我过去成功使用的自定义 JsonConverter 来实现与您在问题中概述的目标相同的目标:

public class DictionaryConverter : JsonConverter {
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { this.WriteValue(writer, value); }

    private void WriteValue(JsonWriter writer, object value) {
        var t = JToken.FromObject(value);
        switch (t.Type) {
            case JTokenType.Object:
                this.WriteObject(writer, value);
                break;
            case JTokenType.Array:
                this.WriteArray(writer, value);
                break;
            default:
                writer.WriteValue(value);
                break;
        }
    }

    private void WriteObject(JsonWriter writer, object value) {
        writer.WriteStartObject();
        var obj = value as IDictionary<string, object>;
        foreach (var kvp in obj) {
            writer.WritePropertyName(kvp.Key);
            this.WriteValue(writer, kvp.Value);
        }
        writer.WriteEndObject();
    }

    private void WriteArray(JsonWriter writer, object value) {
        writer.WriteStartArray();
        var array = value as IEnumerable<object>;
        foreach (var o in array) {
            this.WriteValue(writer, o);
        }
        writer.WriteEndArray();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        return ReadValue(reader);
    }

    private object ReadValue(JsonReader reader) {
        while (reader.TokenType == JsonToken.Comment) {
            if (!reader.Read()) throw new JsonSerializationException("Unexpected Token when converting IDictionary<string, object>");
        }

        switch (reader.TokenType) {
            case JsonToken.StartObject:
                return ReadObject(reader);
            case JsonToken.StartArray:
                return this.ReadArray(reader);
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return reader.Value;
            default:
                throw new JsonSerializationException
                    (string.Format("Unexpected token when converting IDictionary<string, object>: {0}", reader.TokenType));
        }
    }

    private object ReadArray(JsonReader reader) {
        IList<object> list = new List<object>();

        while (reader.Read()) {
            switch (reader.TokenType) {
                case JsonToken.Comment:
                    break;
                default:
                    var v = ReadValue(reader);

                    list.Add(v);
                    break;
                case JsonToken.EndArray:
                    return list;
            }
        }

        throw new JsonSerializationException("Unexpected end when reading IDictionary<string, object>");
    }

    private object ReadObject(JsonReader reader) {
        var obj = new Dictionary<string, object>();

        while (reader.Read()) {
            switch (reader.TokenType) {
                case JsonToken.PropertyName:
                    var propertyName = reader.Value.ToString();

                    if (!reader.Read()) {
                        throw new JsonSerializationException("Unexpected end when reading IDictionary<string, object>");
                    }

                    var v = ReadValue(reader);

                    obj[propertyName] = v;
                    break;
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return obj;
            }
        }

        throw new JsonSerializationException("Unexpected end when reading IDictionary<string, object>");
    }

    public override bool CanConvert(Type objectType) { return typeof(IDictionary<string, object>).IsAssignableFrom(objectType); }
}

Here is the equivalent in f#:

这是等效的f#

type IDictionaryConverter() =
    inherit JsonConverter()

    let rec writeValue (writer: JsonWriter) (value: obj) =
            let t = JToken.FromObject(value)
            match t.Type with
            | JTokenType.Object -> writeObject writer value
            | JTokenType.Array -> writeArray writer value
            | _ -> writer.WriteValue value    

    and writeObject (writer: JsonWriter) (value: obj) =
        writer.WriteStartObject ()
        let obj = value :?> IDictionary<string, obj>
        for kvp in obj do
            writer.WritePropertyName kvp.Key
            writeValue writer kvp.Value
        writer.WriteEndObject ()    

    and writeArray (writer: JsonWriter) (value: obj) = 
        writer.WriteStartArray ()
        let array = value :?> IEnumerable<obj>
        for o in array do
            writeValue writer o
        writer.WriteEndArray ()

    let rec readValue (reader: JsonReader) =
        while reader.TokenType = JsonToken.Comment do
            if reader.Read () |> not then raise (JsonSerializationException("Unexpected token when reading object"))

        match reader.TokenType with
        | JsonToken.Integer
        | JsonToken.Float
        | JsonToken.String
        | JsonToken.Boolean
        | JsonToken.Undefined
        | JsonToken.Null
        | JsonToken.Date
        | JsonToken.Bytes -> reader.Value
        | JsonToken.StartObject -> readObject reader Map.empty
        | JsonToken.StartArray -> readArray reader []
        | _ -> raise (JsonSerializationException(sprintf "Unexpected token when reading object: %O" reader.TokenType))


    and readObject (reader: JsonReader) (obj: Map<string, obj>) =
        match reader.Read() with
        | false -> raise (JsonSerializationException("Unexpected end when reading object"))
        | _ -> reader.TokenType |> function
            | JsonToken.Comment -> readObject reader obj
            | JsonToken.PropertyName ->
                let propertyName = reader.Value.ToString ()
                if reader.Read() |> not then raise (JsonSerializationException("Unexpected end when reading object"))
                let value = readValue reader
                readObject reader (obj.Add(propertyName, value))
            | JsonToken.EndObject -> box obj
            | _ -> raise (JsonSerializationException(sprintf "Unexpected token when reading object: %O" reader.TokenType))

    and readArray (reader: JsonReader) (collection: obj list) =
        match reader.Read() with
        | false -> raise (JsonSerializationException("Unexpected end when reading array"))
        | _ -> reader.TokenType |> function
            | JsonToken.Comment -> readArray reader collection
            | JsonToken.EndArray -> box collection
            | _ -> collection @ [readValue reader] |> readArray reader

    override __.CanConvert t = (typeof<IDictionary<string, obj>>).IsAssignableFrom t
    override __.WriteJson (writer:JsonWriter, value: obj, _:JsonSerializer) = writeValue writer value 
    override __.ReadJson (reader:JsonReader, _: Type, _:obj, _:JsonSerializer) = readValue reader

回答by smartcaveman

You can have full control over the serialization of a type by using a custom JsonConverter. Documentation at http://james.newtonking.com/projects/json/help/html/T_Newtonsoft_Json_JsonConverter.htm.

您可以通过使用自定义JsonConverter. http://james.newtonking.com/projects/json/help/html/T_Newtonsoft_Json_JsonConverter.htm 上的文档。

Also, according to this blog postyou need to use JArrayfor a List, and JObjectfor a dictionary.

此外,根据此博客文章,您需要将其JArray用于列表和JObject字典。

回答by John Sobolewski

I love AutoMapper and seem to think it solves many problems... like this one...

我喜欢 AutoMapper 并且似乎认为它解决了很多问题......就像这个......

why not just let the JSON.NET convert the thing into whatever it wants to... and use AutoMapperto map it into the object you really want.

为什么不让 JSON.NET 将事物转换为它想要的任何东西……并使用AutoMapper将其映射到您真正想要的对象中。

Unless performance is paramount this extra step should be worth it for the reduction in complexity and the ability to use the serializer you want.

除非性能是最重要的,否则这个额外的步骤对于降低复杂性和使用所需序列化程序的能力来说应该是值得的。