Cómo deserializar JSON con nombres de propiedad duplicados en el mismo objeto

Tengo una cadena JSON que espero contenga claves duplicadas con las que no pueda contentar a JSON.NET.

Me preguntaba si alguien sabe de la mejor manera (¿tal vez usando JsonConverter ?) Para hacer que JSON.NET cambie los JObject JObjects en JArrays cuando ve nombres de claves duplicados.

 // For example: This gives me a JObject with a single "JProperty\JObject" child. var obj = JsonConvert.DeserializeObject("{ \"HiThere\":1}"); // This throws: // System.ArgumentException : Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject. obj = JsonConvert.DeserializeObject("{ \"HiThere\":1, \"HiThere\":2, \"HiThere\":3 }"); 

El JSON real que bash deserializar es mucho más complicado y los duplicados están nesteds en múltiples niveles. Pero el código anterior demuestra por qué falla para mí.

Entiendo que el JSON no es correcto y es por eso que estoy preguntando si JSON.NET tiene una forma de evitar esto. Por el bien de los argumentos, digamos que no tengo control sobre el JSON. De hecho, uso un tipo específico para el objeto principal, pero la propiedad particular que tiene problemas será una cadena u otro objeto JSON nested. El tipo de propiedad que falla es “objeto” por este motivo.

Interesante pregunta. JObject con esto por un tiempo y descubrí que si bien un JObject no puede contener propiedades con nombres duplicados, el JsonTextReader utilizado para poblarlo durante la deserialización no tiene esa restricción. (Esto tiene sentido si lo piensas: es un lector de solo reenvío, no se preocupa por lo que ha leído en el pasado). Armado con este conocimiento, tomé la oportunidad de escribir un código que llenará una jerarquía de JTokens, convirtiendo los valores de las propiedades en JArrays según sea necesario si se encuentra un nombre de propiedad duplicado en un JObject en particular. Dado que no conozco sus requisitos y requisitos JSON, es posible que deba realizar algunos ajustes, pero al menos es algo con lo que debe comenzar.

Aquí está el código:

 public static JToken DeserializeAndCombineDuplicates(JsonTextReader reader) { if (reader.TokenType == JsonToken.None) { reader.Read(); } if (reader.TokenType == JsonToken.StartObject) { reader.Read(); JObject obj = new JObject(); while (reader.TokenType != JsonToken.EndObject) { string propName = (string)reader.Value; reader.Read(); JToken newValue = DeserializeAndCombineDuplicates(reader); JToken existingValue = obj[propName]; if (existingValue == null) { obj.Add(new JProperty(propName, newValue)); } else if (existingValue.Type == JTokenType.Array) { CombineWithArray((JArray)existingValue, newValue); } else // Convert existing non-array property value to an array { JProperty prop = (JProperty)existingValue.Parent; JArray array = new JArray(); prop.Value = array; array.Add(existingValue); CombineWithArray(array, newValue); } reader.Read(); } return obj; } if (reader.TokenType == JsonToken.StartArray) { reader.Read(); JArray array = new JArray(); while (reader.TokenType != JsonToken.EndArray) { array.Add(DeserializeAndCombineDuplicates(reader)); reader.Read(); } return array; } return new JValue(reader.Value); } private static void CombineWithArray(JArray array, JToken value) { if (value.Type == JTokenType.Array) { foreach (JToken child in value.Children()) array.Add(child); } else { array.Add(value); } } 

Y aquí hay una demostración:

 class Program { static void Main(string[] args) { string json = @" { ""Foo"" : 1, ""Foo"" : [2], ""Foo"" : [3, 4], ""Bar"" : { ""X"" : [ ""A"", ""B"" ] }, ""Bar"" : { ""X"" : ""C"", ""X"" : ""D"" }, }"; using (StringReader sr = new StringReader(json)) using (JsonTextReader reader = new JsonTextReader(sr)) { JToken token = DeserializeAndCombineDuplicates(reader); Dump(token, ""); } } private static void Dump(JToken token, string indent) { Console.Write(indent); if (token == null) { Console.WriteLine("null"); return; } Console.Write(token.Type); if (token is JProperty) Console.Write(" (name=" + ((JProperty)token).Name + ")"); else if (token is JValue) Console.Write(" (value=" + token.ToString() + ")"); Console.WriteLine(); if (token.HasValues) foreach (JToken child in token.Children()) Dump(child, indent + " "); } } 

Salida:

 Object Property (name=Foo) Array Integer (value=1) Integer (value=2) Integer (value=3) Integer (value=4) Property (name=Bar) Array Object Property (name=X) Array String (value=A) String (value=B) Object Property (name=X) Array String (value=C) String (value=D) 

Brian Rogers – Aquí está la función auxiliar del JsonConverter que escribí. Lo modifiqué basándome en tus comentarios sobre cómo un JsonTextReader es solo un lector de reenvíos y no le importan los valores duplicados.

 private static object GetObject(JsonReader reader) { switch (reader.TokenType) { case JsonToken.StartObject: { var dictionary = new Dictionary(); while (reader.Read() && (reader.TokenType != JsonToken.EndObject)) { if (reader.TokenType != JsonToken.PropertyName) throw new InvalidOperationException("Unknown JObject conversion state"); string propertyName = (string) reader.Value; reader.Read(); object propertyValue = GetObject(reader); object existingValue; if (dictionary.TryGetValue(propertyName, out existingValue)) { if (existingValue is List) { var list = existingValue as List; list.Add(propertyValue); } else { var list = new List {existingValue, propertyValue}; dictionary[propertyName] = list; } } else { dictionary.Add(propertyName, propertyValue); } } return dictionary; } case JsonToken.StartArray: { var list = new List(); while (reader.Read() && (reader.TokenType != JsonToken.EndArray)) { object propertyValue = GetObject(reader); list.Add(propertyValue); } return list; } default: { return reader.Value; } } } 

No debería utilizar un tipo genérico de objeto, debería ser un tipo más específico.

Sin embargo, usted está mal formado, ¿cuál es su problema?

Tienes :

 "{ \"HiThere\":1, \"HiThere\":2, \"HiThere\":3 }" 

Pero debería ser:

 "{"HiTheres": [{\"HiThere\":1}, {\"HiThere\":2}, {\"HiThere\":3} ]}" 

O

 "{ \"HiThereOne\":1, \"HiThereTwo\":2, \"HiThereThree\":3 }" 

You json es un objeto con 3 campos con el mismo nombre (“HiThere”). Lo cual no funcionará

El json que he mostrado da: una matriz (HiTheres) de tres objetos, cada uno con un campo de HiThere u un objeto con tres campos con diferentes nombres. (HiThereOne, HiThereTwo, “HiThere One”)

Eche un vistazo a http://jsoneditoronline.org/index.html Y http://json.org/