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

前端 未结 5 955
-上瘾入骨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条回答
  •  萌比男神i
    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>(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;
            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;
            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");
            }
    
            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: {0}", reader.TokenType));
            }
        }
    
        private object ReadArray(JsonReader reader) {
            IList list = new List();
    
            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");
        }
    
        private object ReadObject(JsonReader reader) {
            var obj = new Dictionary();
    
            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");
                        }
    
                        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");
        }
    
        public override bool CanConvert(Type objectType) { return typeof(IDictionary).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
            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
            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) =
            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>).IsAssignableFrom t
        override __.WriteJson (writer:JsonWriter, value: obj, _:JsonSerializer) = writeValue writer value 
        override __.ReadJson (reader:JsonReader, _: Type, _:obj, _:JsonSerializer) = readValue reader
    

    提交回复
    热议问题