¿Cómo uso JSON.NET para deserializar en Diccionario y Lista nesteds / recursivos?

Necesito deserializar una burbuja compleja de JSON en contenedores .NET estándar para usar en código que no tenga conocimiento de JSON . Espera que las cosas estén en tipos .NET estándar, específicamente Dictionary [string, object] o List [object] donde “object” puede ser primitivo o recurse (Dictionary o List).

No puedo usar un tipo estático para mapear los resultados y JObject / JToken no encajan. Idealmente, habría alguna forma (¿quizás a través de los contratos?) De convertir JSON sin formato en contenedores .NET básicos.

Busqué por todas partes alguna manera de lograr que el deserializador JSON.NET cree estos tipos simples cuando encuentra “{}” o “[]” pero con poco éxito.

Cualquier ayuda apreciada!

Si solo desea un método genérico que pueda manejar cualquier JSON arbitrario y convertirlo en una estructura anidada de tipos regulares .NET (primitivas, listas y diccionarios), puede usar la API LINQ-to-JSON de JSON.Net para hacerlo:

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() .ToDictionary(prop => prop.Name, prop => ToObject(prop.Value)); case JTokenType.Array: return token.Select(ToObject).ToList(); default: return ((JValue)token).Value; } } } 

Puede llamar al método como se muestra a continuación. obj contendrá un Dictionary , List o primitiva, dependiendo de con qué JSON haya comenzado.

 object obj = JsonHelper.Deserialize(jsonString); 

Una forma de deserializar json string recursivamente en diccionarios y listas con JSON.NET es crear una clase de convertidor json personalizada que se derive de la clase abstracta JsonConverter proporcionada por JSON.NET.

Es en su JsonConverter derivado donde pone la implementación de cómo debe escribirse un objeto JsonConverter desde json.

Puedes usar tu JsonConverter personalizado así:

 var o = JsonConvert.DeserializeObject>(json, new DictionaryConverter()); 

Este es un JsonConverter personalizado que utilicé con éxito en el pasado para lograr los mismos objectives que describe en su pregunta:

 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); } } 

Aquí está el equivalente en 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 

No puedes hacer lo que te pedí. Al menos no tan lejos como puedo decir después de MUCHA investigación. Tuve que editar la fuente de Json.NET.

Amo AutoMapper y parezco pensar que resuelve muchos problemas … como este …

¿Por qué no dejar que JSON.NET convierta la cosa en lo que quiera … y usar AutoMapper para mapearlo en el objeto que realmente desea?

A menos que el rendimiento sea primordial, este paso adicional debería valer la pena por la reducción de la complejidad y la capacidad de utilizar el serializador que desee.

Puede tener control total sobre la serialización de un tipo utilizando un JsonConverter personalizado. Documentación en http://james.newtonking.com/projects/json/help/html/T_Newtonsoft_Json_JsonConverter.htm .

Además, de acuerdo con esta publicación de blog necesita usar JArray para una lista, y JObject para un diccionario.