Как я могу использовать JSON.NET для десериализации во вложенный / рекурсивный словарь и список? - PullRequest
37 голосов
/ 05 апреля 2011

Мне нужно десериализовать сложный большой двоичный объект JSON в стандартные контейнеры .NET для использования в коде, который не знает о JSON . Он ожидает, что вещи будут в стандартных типах .NET, в частности Dictionary<string, object> или List<object>, где «объект» может быть примитивным или рекурсивным (словарь или список).

Я не могу использовать статический тип для отображения результатов, а JObject / JToken не подходит. В идеале был бы какой-то способ (возможно, через контракты?) Конвертировать необработанный JSON в базовые контейнеры .NET.

Я всюду искал способ заставить десериализатор JSON.NET создать эти простые типы, когда он встречает «{}» или «[]», но с небольшим успехом.

Любая помощь приветствуется!

Ответы [ 5 ]

52 голосов
/ 02 октября 2013

Если вам просто нужен универсальный метод, который может обрабатывать любой произвольный JSON и преобразовывать его во вложенную структуру обычных типов .NET (примитивы, списки и словари), вы можете использовать LINQ-to-JSON JSON.Net.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;
        }
    }
}

Вы можете вызвать метод, как показано ниже.obj будет содержать Dictionary<string, object>, List<object> или примитив в зависимости от того, с каким JSON вы начали.

object obj = JsonHelper.Deserialize(jsonString);
12 голосов
/ 25 июня 2016

Одним из способов рекурсивной десериализации строки json в словари и списки с помощью JSON.NET является создание пользовательского класса преобразователя json, который является производным от абстрактного класса JsonConverter, предоставляемого JSON.NET.

Это в вашем производном JsonConverter, куда вы помещаете реализацию того, как объект должен быть записан в json и из него.

Вы можете использовать свой пользовательский JsonConverter, например:

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

Вот пользовательский 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); }
}

Вот эквивалент в 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
1 голос
/ 08 апреля 2011

Вы не можете сделать то, что я просил.По крайней мере, не настолько, насколько я могу судить после многих исследований.Мне пришлось редактировать исходный код Json.NET.

0 голосов
/ 05 апреля 2011

Я люблю AutoMapper и, кажется, думаю, что он решает много проблем ... как этот ...

почему бы просто не позволить JSON.NET преобразовать вещь во все, что она хочет ... и использовать AutoMapper , чтобы отобразить ее в объект, который вы действительно хотите.

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

0 голосов
/ 05 апреля 2011

Вы можете иметь полный контроль над сериализацией типа, используя пользовательский JsonConverter. Документация на http://james.newtonking.com/projects/json/help/html/T_Newtonsoft_Json_JsonConverter.htm.

Кроме того, согласно этому сообщению в блоге вам необходимо использовать JArray для списка и JObject для словаря.

...