web-dev-qa-db-fra.com

Désérialisation de JSON lorsque parfois un tableau et parfois un objet

J'ai un peu de mal à désérialiser les données renvoyées par Facebook à l'aide des bibliothèques JSON.NET.

Le JSON revenu d'un simple poteau mural ressemble à ceci:

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

Le JSON retourné pour une photo ressemble à:

"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"}}
        ],

Tout fonctionne très bien et je n'ai aucun problème. Je suis maintenant tombé sur un simple message mural d'un client mobile avec le JSON suivant, et la désérialisation échoue maintenant avec ce seul message:

"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"

Voici la classe que je désérialise:

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

Sans utiliser FacebookMediaJsonConverter, j'obtiens une erreur: impossible de désérialiser l'objet JSON dans le type 'System.Collections.Generic.List`1 [FacebookMedia]'. ce qui est logique, car dans le JSON, Media n'est pas une collection.

J'ai trouvé cet article qui décrit un problème similaire, j'ai donc tenté de suivre cette voie: Désérialiser JSON, parfois la valeur est un tableau, parfois "" (chaîne vide)

Mon convertisseur ressemble à:

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

Ce qui fonctionne bien, sauf que je reçois maintenant une nouvelle exception:

Dans JsonSerializerInternalReader.cs, CreateValueInternal (): jeton inattendu lors de la désérialisation de l'objet: PropertyName

La valeur de reader.Value est "permalink". Je peux clairement voir dans le commutateur qu'il n'y a pas de cas pour JsonToken.PropertyName.

Y a-t-il quelque chose que je dois faire différemment dans mon convertisseur? Merci pour toute aide.

48
mfanto

Le développeur de JSON.NET a fini par aider sur le site du projet codeplex. Voici la solution:

Le problème était que, lorsqu'il s'agissait d'un objet JSON, je ne lisais pas au-delà de l'attribut. Voici le bon code:

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 a également eu la gentillesse de fournir des tests unitaires pour la méthode ci-dessus.

22
mfanto

Une explication très détaillée sur la façon de gérer ce cas est disponible à "Utilisation d'un JsonConverter personnalisé pour corriger les mauvais résultats JSON" .

Pour résumer, vous pouvez étendre le convertisseur JSON.NET par défaut en faisant

  1. Annoter la propriété avec le problème

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
  2. Étendez le convertisseur pour renvoyer une liste du type souhaité, même pour un seul objet

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

Comme mentionné dans l'article, cette extension n'est pas complètement générale mais elle fonctionne si vous êtes d'accord pour obtenir une liste.

30
Camilo Martinez

jetez un oeil à l'espace de noms System.Runtime.Serialization dans le framework c #, cela vous amènera là où vous voulez être très rapidement.

Si vous le souhaitez, vous pouvez consulter un exemple de code dans le projet this (n'essayant pas de brancher mon propre travail mais je viens de terminer à peu près exactement ce que vous faites mais avec une API source différente.

j'espère que ça aide.

2
jonezy