有时数组和有时反对时反序列化JSON

mfa*_*nto 47 c# json json.net facebook-c#-sdk json-deserialization

我在使用JSON.NET库对从Facebook返回的数据进行反序列化时遇到了一些麻烦.

从简单的墙贴文件返回的JSON看起来像:

{
    "attachment":{"description":""},
    "permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
Run Code Online (Sandbox Code Playgroud)

为照片返回的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"}}
        ],
Run Code Online (Sandbox Code Playgroud)

一切都很好,我没有问题.我现在遇到一个来自移动客户端的简单墙帖,其中包含以下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"
Run Code Online (Sandbox Code Playgroud)

这是我反序列化的类:

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; }
    }
Run Code Online (Sandbox Code Playgroud)

不使用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;
}
Run Code Online (Sandbox Code Playgroud)

哪个工作正常,除了我现在得到一个新的例外:

在JsonSerializerInternalReader.cs中,CreateValueInternal():反序列化对象时出现意外的标记:PropertyName

reader.Value的值是"永久链接".我可以清楚地看到交换机中没有JsonToken.PropertyName的情况.

在我的转换器中有什么我需要做的事情吗?谢谢你的帮助.

Cam*_*nez 32

有关如何处理此案例的详细说明,请参阅"使用自定义JsonConverter修复错误的JSON结果".

总而言之,您可以扩展默认的JSON.NET转换器

  1. 使用问题注释属性

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
    Run Code Online (Sandbox Code Playgroud)
  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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

正如文章中所提到的,这个扩展并不是完全一般的,但是如果你能得到一个列表就可以了.

  • 绝对完美。谢谢。 (2认同)

mfa*_*nto 22

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});
    }
}
Run Code Online (Sandbox Code Playgroud)

詹姆斯也非常友好地为上述方法提供单元测试.


Pat*_*ott 6

阐述 Martinez 和 mfanto 对 Newtonsoft 的回答。它确实可以与 Newtonsoft 一起使用:

这是使用数组而不是列表(并且命名正确)执行此操作的示例。

public class SingleValueArrayConverter<T> : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartObject 
            || reader.TokenType == JsonToken.String
            || reader.TokenType == JsonToken.Integer)
        {
            return new T[] { serializer.Deserialize<T>(reader) };
        }
        return serializer.Deserialize<T[]>(reader);
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在属性上写下:

[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }
Run Code Online (Sandbox Code Playgroud)

Newtonsoft 将为您完成剩下的工作。

return JsonConvert.DeserializeObject<T>(json);
Run Code Online (Sandbox Code Playgroud)

为马丁内斯和姆凡托干杯!

不管你相信与否,这适用于子项目。(甚至可能必须这样做。)所以...在我的 InsuranceInfo 中,如果我有另一个对象/数组混合体,请在该属性上再次使用它。

这还允许您将对象重新序列化回 json。当它重新序列化时,它将始终是一个数组。


Neo*_*Neo 5

基于上面 Camilo Martinez 的回答,这是一种使用JsonConverterC# 8.0的通用版本以及实现序列化部分的更现代、类型安全、更精简和更完整的方法。除了根据问题预期的两个令牌之外,它还抛出异常。代码永远不应该做超过要求的事情,否则您将面临由于处理意外数据而导致未来错误的风险。

internal class SingleObjectOrArrayJsonConverter<T> : JsonConverter<ICollection<T>> where T : class, new()
{
    public override void WriteJson(JsonWriter writer, ICollection<T> value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value.Count == 1 ? (object)value.Single() : value);
    }

    public override ICollection<T> ReadJson(JsonReader reader, Type objectType, ICollection<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        return reader.TokenType switch
        {
            JsonToken.StartObject => new Collection<T> {serializer.Deserialize<T>(reader)},
            JsonToken.StartArray => serializer.Deserialize<ICollection<T>>(reader),
            _ => throw new ArgumentOutOfRangeException($"Converter does not support JSON token type {reader.TokenType}.")
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

然后这样装饰物业:

[JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;
Run Code Online (Sandbox Code Playgroud)

我已将属性类型从List<>更改ICollection<>为 JSON POCO 通常只需要这种较弱的类型,但如果List<>需要,则只需在上述所有代码中替换ICollectionCollection使用List