2011-11-10 22 views
5

Estoy analizando una cadena JSON a un objeto .NET correspondiente con la biblioteca Newtonsoft. Tengo un problema para analizar las propiedades JSON que son matrices. A veces, la propiedad JSON es una matriz, otras veces, es un elemento único.Analizando la cadena JSON al objeto .NET

Ejemplo:

Este es el objeto de .NET:

public class xx 
    { 
     public string yy { get; set; }  
     public List<string> mm{ get; set; }   
    } 

cuando recibo este JSON:

{ "xx": {"yy":"nn", "mm": [ "zzz", "aaa" ] } } 

que perfectamente puede hacer:

JsonConvert.DeserializeObject<xx>(json); 

Pero a veces recibo este J SON:

{ "xx": {"yy":"nn", "mm":"zzz"} } 

Y la deserialización falla debido a la propiedad de lista en el objeto C#.

¿Cómo puedo definir un objeto para deserializar las dos cadenas JSON en el mismo objeto (con List<string>).

-------- ----- ACTUALIZACIÓN

primer lugar un WS generar un XML haciendo alguna operación .. el XML es como

<xx yy='nn'><mm>zzz</mm></xx> 

y si hay más elementos es:

<xx yy='nn'><mm>zzz</mm><mm>aaa</mm></xx> 

finalmente WS convertir este XML haciendo:

XmlDocument doc = new XmlDocument(); 
doc.LoadXml(xml);   
var json = JsonConvert.SerializeXmlNode(doc); 

y envíeme el json .. y aquí comienza mi problema ..

+2

¿No puedes cambiar la forma en que se produjo el JSON? Porque de esta manera es bastante inconsistente y siempre producir una matriz tiene más sentido. – svick

+1

¿Por qué el formato del objeto JSON entrante es insistente? Si 'mm' puede contener más de un elemento, siempre se debe transmitir como una matriz (' [] ') y nunca como un simple par' nombre: valor'. –

+0

Debería poder realizar un masaje en el JSON antes de enviarlo al servidor para que siempre tenga el formato que funcione. Muestra el JS que usas para construir el objeto JSON. – arb

Respuesta

1

Lo que envía el servicio de envío se supone que se ajusta a un contrato. Si no es así, entonces bien, o bien golpea al desarrollador que envía y hace que lo arreglen, o las diversas cosas que se le envían son el contrato. Una pena que no tenga ningún metadato para estar seguro, solo tendrá que probar una variedad de contratos hasta que uno funcione.

object someValue; 
try 
{ 
    someValue =JsonConvert.DeserializeObject<TypeWithList>(json); 
} 
catch 
{ 
    try 
    { 
     someValue = JsonConvert.DeserializeObject<TypeWithString>(json); 
    } 
    catch 
    { 
    //Darn, yet another type 
    } 
} 
+0

Ouch. Esto sería una pesadilla si este fuera el caso. No pensé que él podría no tener control sobre lo que está recibiendo. – arb

+0

Pensé en esta solución ... pero no me gusta para nada. Pensé que tal vez con algún tipo de propiedades podría resolver ... –

-2

creo que hay que buscar a su objeto Javascript. Si declara explícitamente el tipo de propiedades del objeto que va a serializar en JSON, no debería encontrar ninguna incoherencia.

var stringProperty = new String(); 
var arrayProperty = new Array(); 

// Assign value to stringProperty and push elements into arrayProperty 

var object = { 
    stringProperty: stringProperty, 
    arrayProperty: arrayProperty 
}; 

var jsonObject = JSON.stringify(object); 

document.write(jsonObject); 

Si nos fijamos en la salida se verá que arrayProperty siempre es serializado en una matriz si hay cero, uno o varios elementos.

5

respuesta Actualizado:

En cuanto a cómo los mapas Json.NET XML, se adopta el enfoque de que lo que ve es lo que serializa, excepto que si ve múltiplos, hará una matriz. Esto es ideal para muchos árboles XML DOM con un diseño consistente, pero desafortunadamente no puede funcionar para sus propósitos.

Puede verificar esto mirando el cuerpo para las funciones SerializeGroupedNodes() y SerializeNode() en la siguiente fuente de archivo.

XmlNodeConverter.cs source code @ CodePlex, ChangeSet #63616

Hay otra opción que me previamente pensó que podría ser una exageración, pero sería útil ahora que sabemos qué esperar del comportamiento por defecto en el extremo serialización.

Json.Net admite el uso de convertidores personalizados derivados de JsonConverter para mapear casos particulares de valores caso por caso.

Podemos manejar esto ya sea en el lado de serialización o en el lado de deserialización. He elegido escribir una solución en el lado deserializador, ya que probablemente tenga otras razones existentes para asignar XML a JSON.

Una gran ventaja es que su clase puede permanecer intacta, excepto la anulación, que requiere la aplicación de un atributo. Aquí hay un ejemplo de código que demuestra cómo usar JsonAttribute y una clase de convertidor personalizada (MMArrayConverter) para solucionar su problema. Tenga en cuenta que probablemente quiera probar esto más a fondo y quizás actualice el convertidor para manejar otros casos, por ejemplo, si finalmente migra al IList<string> o algún otro caso funky como Lazy<List<string>>, o incluso hacerlo funcionar con genéricos.

using Newtonsoft.Json; 
using Newtonsoft.Json.Linq; 
using Newtonsoft.Json.Converters; 

namespace JsonArrayImplictConvertTest 
{ 
    public class MMArrayConverter : JsonConverter 
    { 
     public override bool CanConvert(Type objectType) 
     { 
      return objectType.Equals(typeof(List<string>)); 
     } 

     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
     { 
      if (reader.TokenType == JsonToken.StartArray) 
      { 
       List<string> parseList = new List<string>(); 
       do 
       { 
        if (reader.Read()) 
        { 
         if (reader.TokenType == JsonToken.String) 
         { 
          parseList.Add((string)reader.Value); 
         } 
         else 
         { 
          if (reader.TokenType == JsonToken.Null) 
          { 
           parseList.Add(null); 
          } 
          else 
          { 
           if (reader.TokenType != JsonToken.EndArray) 
           { 
            throw new ArgumentException(string.Format("Expected String/Null, Found JSON Token Type {0} instead", reader.TokenType.ToString())); 
           } 
          } 
         } 
        } 
        else 
        { 
         throw new InvalidOperationException("Broken JSON Input Detected"); 
        } 
       } 
       while (reader.TokenType != JsonToken.EndArray); 

       return parseList; 
      } 

      if (reader.TokenType == JsonToken.Null) 
      { 
       // TODO: You need to decide here if we want to return an empty list, or null. 
       return null; 
      } 

      if (reader.TokenType == JsonToken.String) 
      { 
       List<string> singleList = new List<string>(); 
       singleList.Add((string)reader.Value); 
       return singleList; 
      } 

      throw new InvalidOperationException("Unhandled case for MMArrayConverter. Check to see if this converter has been applied to the wrong serialization type."); 
     } 

     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
     { 
      // Not implemented for brevity, but you could add this if needed. 
      throw new NotImplementedException(); 
     } 
    } 

    public class ModifiedXX 
    { 
     public string yy { get; set; } 

     [JsonConverter(typeof(MMArrayConverter))] 
     public List<string> mm { get; set; } 

     public void Display() 
     { 
      Console.WriteLine("yy is {0}", this.yy); 
      if (null == mm) 
      { 
       Console.WriteLine("mm is null"); 
      } 
      else 
      { 
       Console.WriteLine("mm contains these items:"); 
       mm.ForEach((item) => { Console.WriteLine(" {0}", item); }); 
      } 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      string jsonTest1 = "{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] }"; 
      ModifiedXX obj1 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest1); 
      obj1.Display(); 

      string jsonTest2 = "{\"yy\":\"nn\", \"mm\": \"zzz\" }"; 
      ModifiedXX obj2 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest2); 
      obj2.Display(); 

      // This test is now required in case we messed up the parser state in our converter. 
      string jsonTest3 = "[{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] },{\"yy\":\"nn\", \"mm\": \"zzz\" }]"; 
      List<ModifiedXX> obj3 = JsonConvert.DeserializeObject<List<ModifiedXX>>(jsonTest3); 
      obj3.ForEach((obj) => { obj.Display(); }); 

      Console.ReadKey(); 
     } 
    } 
} 

respuesta original:

que sería mejor para solucionar el JSON está recibiendo en la fuente, como muchos ya han señalado. Es posible que desee publicar una actualización que muestre cómo se correlaciona el XML en su comentario actualizado con JSON, ya que esa sería la mejor ruta en general.

Sin embargo, si encuentra que esto no es posible y quiere forma de serializar y manejar el valor de la variante después de los hechos, puede corregir las cosas declarando mm como tipo object, y luego manejando la posible utiliza usted mismo el soporte Linq de JSON.Net. En los dos escenarios que describió, encontrará que declarar mm como tipo object dará como resultado un null, un string, o un JArray asignado a mm por la llamada al DeserializeObject<>.

Aquí hay un ejemplo de código que muestra esto en acción. También hay un caso en otras circunstancias donde podría recibir un JObject, que también se trata en esta muestra. Tenga en cuenta que la función miembro mmAsList() hace el trabajo de corregir la diferencia por usted. También tenga en cuenta que he manejado null aquí devolviendo un null para List<string>; es probable que desee revisar esto para su implementación.

using Newtonsoft.Json; 
using Newtonsoft.Json.Linq; 

namespace JsonArrayUnionTest 
{ 
    public class ModifiedXX 
    { 
     public string yy { get; set; } 
     public object mm { get; set; } 

     public List<string> mmAsList() 
     { 
      if (null == mm) { return null; } 
      if (mm is JArray) 
      { 
       JArray mmArray = (JArray)mm; 
       return mmArray.Values<string>().ToList(); 
      } 

      if (mm is JObject) 
      { 
       JObject mmObj = (JObject)mm; 
       if (mmObj.Type == JTokenType.String) 
       { 
        return MakeList(mmObj.Value<string>()); 
       } 
      } 

      if (mm is string) 
      { 
       return MakeList((string)mm); 
      } 

      throw new ArgumentOutOfRangeException("unhandled case for serialized value for mm (cannot be converted to List<string>)"); 
     } 

     protected List<string> MakeList(string src) 
     { 
      List<string> newList = new List<string>(); 
      newList.Add(src); 
      return newList; 
     } 

     public void Display() 
     { 
      Console.WriteLine("yy is {0}", this.yy); 
      List<string> mmItems = mmAsList(); 
      if (null == mmItems) 
      { 
       Console.WriteLine("mm is null"); 
      } 
      else 
      { 
       Console.WriteLine("mm contains these items:"); 
       mmItems.ForEach((item) => { Console.WriteLine(" {0}", item); }); 
      } 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      string jsonTest1 = "{\"yy\":\"nn\", \"mm\": [ \"zzz\", \"aaa\" ] }"; 
      ModifiedXX obj1 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest1); 
      obj1.Display(); 

      string jsonTest2 = "{\"yy\":\"nn\", \"mm\": \"zzz\" }"; 
      ModifiedXX obj2 = JsonConvert.DeserializeObject<ModifiedXX>(jsonTest2); 
      obj2.Display(); 

      Console.ReadKey(); 
     } 
    } 
} 
+1

Solución interesante ... Gracias meklarian !! –

0

En su caso se puede usar directamente el método estático de la clase JsonConvert

PopulateObject (configuración de valores de cadena, objetivo objeto, JsonSerializerSettings);

pasar los JsonSerializerSettings objeto como

new JsonSerializerSettings(){TypeNameHandling = TypeNameHandling.All})