Десериализация JSON, когда иногда массив, а иногда объект - PullRequest
43 голосов
/ 07 марта 2011

У меня возникли проблемы с десериализацией данных, возвращаемых из Facebook с использованием библиотек JSON.NET.

JSON, вернувшийся из простого сообщения на стене, выглядит так:

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

JSON, возвращенный для фотографии, выглядит следующим образом:

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

Все отлично работает, и у меня нет проблем. Теперь я наткнулся на простую запись на стене от мобильного клиента со следующим JSON, и десериализация теперь не удалась с этой единственной записью:

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

Вот класс, который я десериализую как:

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

Не используя FacebookMediaJsonConverter, я получаю сообщение об ошибке: Невозможно десериализовать объект JSON в тип 'System.Collections.Generic.List`1 [FacebookMedia]'. что имеет смысл, так как в JSON Media не является коллекцией.

Я нашел этот пост, в котором описана похожая проблема, поэтому я попытался пойти по этому пути: Десериализовать JSON, иногда значение - это массив, иногда "" (пустая строка)

Мой конвертер выглядит так:

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

Что отлично работает, за исключением того, что теперь я получаю новое исключение:

Внутри JsonSerializerInternalReader.cs, CreateValueInternal (): неожиданный токен при десериализации объекта: PropertyName

Значение читателя. Значение - «постоянная ссылка». Я ясно вижу в переключателе, что JsonToken.PropertyName не подходит.

Что-то мне нужно сделать по-другому в моем конвертере? Спасибо за любую помощь.

Ответы [ 4 ]

29 голосов
/ 27 января 2015

Очень подробное объяснение того, как обрабатывать этот случай, доступно на «Использование пользовательского JsonConverter для исправления неверных результатов JSON» .

Подводя итог, вы можете расширить JSON по умолчанию.NET конвертер делает

  1. Аннотируйте свойство с проблемой

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
  2. Расширьте конвертер, чтобы получить список желаемого типа даже дляодин объект

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

Как уже упоминалось в статье, это расширение не является полностью общим, но оно работает, если у вас все в порядке с получением списка.

22 голосов
/ 08 марта 2011

Разработчик JSON.NET в конечном итоге помогал на сайте Codeplex проектов. Вот решение:

Проблема заключалась в том, что, когда это был объект JSON, я не читал мимо атрибута. Вот правильный код:

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

Джеймс также любезно предоставил юнит-тесты для вышеупомянутого метода.

2 голосов
/ 08 марта 2011

взгляните на пространство имен System.Runtime.Serialization в среде c #, оно очень быстро приведет вас туда, куда вы хотите.

Если вы хотите, вы можете посмотреть пример кодав этом проекте (я не пытался подключить свою собственную работу, но я только что почти закончил то, что вы делаете, но с другим исходным API.

надеюсь, это поможет.

0 голосов
/ 29 ноября 2013

Я думаю, тебе стоит написать свой класс вот так ...

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
    {
    }
...