Deserializar JSON cuando a veces array y a veces objeto


Estoy teniendo un poco de problemas para deserializar los datos devueltos desde Facebook utilizando el JSON.NET bibliotecas.

El JSON devuelto de un simple post de muro se ve como:

{
    "attachment":{"description":""},
    "permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}

El JSON devuelto para una foto se ve como:

"attachment":{
        "media":[
            {
                "href":"http://www.facebook.com/photo.php?fbid=12345",
                "alt":"",
                "type":"photo",
                "src":"http://photos-b.ak.fbcdn.net/hphotos-ak-ash1/12345_s.jpg",
                "photo":{"aid":"1234","pid":"1234","fbid":"1234","owner":"1234","index":"12","width":"720","height":"482"}}
        ],

Todo funciona muy bien y no tengo problemas. Ahora me he encontrado con un simple post mural de un cliente móvil con el siguiente JSON, y la deserialización ahora falla con este solo post:

"attachment":
    {
        "media":{},
        "name":"",
        "caption":"",
        "description":"",
        "properties":{},
        "icon":"http://www.facebook.com/images/icons/mobile_app.gif",
        "fb_object_type":""
    },
"permalink":"http://www.facebook.com/1234"

Aquí está la clase que soy deserializar como:

public class FacebookAttachment
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public string Href { get; set; }
        public FacebookPostType Fb_Object_Type { get; set; }
        public string Fb_Object_Id { get; set; }

        [JsonConverter(typeof(FacebookMediaJsonConverter))]
        public List<FacebookMedia> { get; set; }

        public string Permalink { get; set; }
    }

Sin usar FacebookMediaJsonConverter, obtengo un error: No se puede deserializar el objeto JSON en el tipo 'System.Colecciones.Generico.List ' 1 [FacebookMedia]'. lo que tiene sentido, ya que en el JSON, Media no es una colección.

He encontrado este post que describe un problema similar, así que he intentado ir por esta ruta: Deserializar JSON, a veces el valor es una matriz, a veces "" (cadena en blanco)

Mi convertidor se ve como:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
     if (reader.TokenType == JsonToken.StartArray)
          return serializer.Deserialize<List<FacebookMedia>>(reader);
     else
          return null;
}

Que funciona bien, excepto que ahora obtengo una nueva excepción:

Dentro de JsonSerializerInternalReader.cs, createvaluueinternal (): Token inesperado mientras deserializa object: propertyName

El valor del lector.Valor es "permalink". Puedo ver claramente en el interruptor que no hay caso para JsonToken.PropertyName.

¿Hay algo que deba hacer de manera diferente en mi convertidor? Gracias por cualquier ayuda.

Author: Community, 2011-03-07

4 answers

Una explicación muy detallada sobre cómo manejar este caso está disponible en "Usar un JsonConverter personalizado para corregir resultados JSON incorrectos".

Para resumir, puede extender el valor predeterminado JSON.NET convertidor haciendo

  1. Anote la propiedad con el problema

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
  2. Extienda el convertidor para devolver una lista del tipo deseado incluso para un solo objeto

    public class SingleValueArrayConverter<T> : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            object retVal = new Object();
            if (reader.TokenType == JsonToken.StartObject)
            {
                T instance = (T)serializer.Deserialize(reader, typeof(T));
                retVal = new List<T>() { instance };
            } else if (reader.TokenType == JsonToken.StartArray) {
                retVal = serializer.Deserialize(reader, objectType);
            }
            return retVal;
        }
    
        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    }
    

Como se menciona en el artículo, esta extensión no es completamente general, pero funciona si usted está bien con conseguir una lista.

 25
Author: Camilo Martinez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-06 00:18:33

El desarrollador de JSON.NET terminó ayudando en el sitio de proyectos codeplex. Aquí está la solución:

El problema era que, cuando era un objeto JSON, no estaba leyendo más allá del atributo. Aquí está el código correcto:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    if (reader.TokenType == JsonToken.StartArray)
    {
        return serializer.Deserialize<List<FacebookMedia>>(reader);
    }
    else
    {
        FacebookMedia media = serializer.Deserialize<FacebookMedia>(reader);
        return new List<FacebookMedia>(new[] {media});
    }
}

James también tuvo la amabilidad de proporcionar pruebas unitarias para el método anterior.

 22
Author: mfanto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-16 13:16:29

Echa un vistazo al Sistema.Ejecución.Serialización espacio de nombres en el marco de c#, que va a llegar a donde desea estar muy rápidamente.

Si lo desea, puede revisar algún código de ejemplo en este proyecto (no intento conectar mi propio trabajo, pero acabo de terminar casi exactamente lo que está haciendo, pero con una api de origen diferente.

Espero que ayude.

 2
Author: jonezy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-07 21:00:27

Creo que deberías escribir tu clase así...!!!

public class FacebookAttachment
    {

        [JsonProperty("attachment")]
        public Attachment Attachment { get; set; }

        [JsonProperty("permalink")]
        public string Permalink { get; set; }
    }

public class Attachment
    {

        [JsonProperty("media")]
        public Media Media { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("caption")]
        public string Caption { get; set; }

        [JsonProperty("description")]
        public string Description { get; set; }

        [JsonProperty("properties")]
        public Properties Properties { get; set; }

        [JsonProperty("icon")]
        public string Icon { get; set; }

        [JsonProperty("fb_object_type")]
        public string FbObjectType { get; set; }
    }
 public class Media
    {
    }
 public class Properties
    {
    }
 -2
Author: radhey_mishra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-29 10:20:11