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

前端 未结 5 829
-上瘾入骨i
-上瘾入骨i 2020-11-22 13:13

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

相关标签:
5条回答
  • 2020-11-22 13:38

    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 API to do it:

    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. obj will either contain a Dictionary<string, object>, List<object>, or primitive depending on what JSON you started with.

    object obj = JsonHelper.Deserialize(jsonString);
    
    0 讨论(0)
  • 2020-11-22 13:51

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

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

    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.

    0 讨论(0)
  • 2020-11-22 13:53

    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 .

    Also, according to this blog post you need to use JArray for a List, and JObject for a dictionary.

    0 讨论(0)
  • 2020-11-22 13:56

    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.

    0 讨论(0)
  • 2020-11-22 14:03

    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 JsonConverter abstract class provided by JSON.NET.

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

    You can use your custom JsonConverter like this:

    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:

    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#:

    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
    
    0 讨论(0)
提交回复
热议问题