C# 有时是数组有时是对象时反序列化JSON

C# 有时是数组有时是对象时反序列化JSON,c#,json,json.net,facebook-c#-sdk,json-deserialization,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" } "attachment":{ "media":[ { "href":"

我在使用JSON.NET库反序列化Facebook返回的数据时遇到了一些问题

从一个简单的墙柱返回的JSON如下所示:

{
    "attachment":{"description":""},
    "permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
"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"}}
        ],
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;
}
为照片返回的JSON如下所示:

{
    "attachment":{"description":""},
    "permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
"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"}}
        ],
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;
}
一切都很好,我没有问题。现在,我遇到了一个来自移动客户端的带有以下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; }
    }
公共类facebook附件
{
公共字符串名称{get;set;}
公共字符串说明{get;set;}
公共字符串Href{get;set;}
PublicFaceBookPostType Fb_对象_类型{get;set;}
公共字符串Fb_Object_Id{get;set;}
[JsonConverter(类型(FacebookMediaJsonConverter))]
公共列表{get;set;}
公共字符串Permalink{get;set;}
}
如果不使用FacebookMediaJsonConverter,我会遇到一个错误:无法将JSON对象反序列化为“System.Collections.Generic.List`1[FacebookMedia]”类型。 这是有道理的,因为在JSON中,媒体不是集合

我发现这篇文章描述了一个类似的问题,所以我尝试走这条路:

我的转换器看起来像:

{
    "attachment":{"description":""},
    "permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
"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"}}
        ],
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;
}
public override object ReadJson(JsonReader reader,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
if(reader.TokenType==JsonToken.StartArray)
返回序列化程序。反序列化(读取器);
其他的
返回null;
}
这很好,但我现在有一个新的例外:

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

reader的值。值为“permalink”。我可以在开关中清楚地看到,没有必要使用JsonToken.PropertyName


在我的转换器中有什么需要做的不同吗?谢谢你的帮助

看看c#framework中的System.Runtime.Serialization名称空间,它会让您很快找到想要的位置

如果您愿意,您可以查看project中的一些示例代码(不尝试插入我自己的工作,但我刚刚完成了您正在做的工作,但使用了不同的源api)


希望有帮助。

JSON.NET的开发人员最终在projects 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});
    }
}
public override object ReadJson(JsonReader reader,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
if(reader.TokenType==JsonToken.StartArray)
{
返回序列化程序。反序列化(读取器);
}
其他的
{
FacebookMedia=序列化程序。反序列化(读取器);
返回新列表(新[]{media});
}
}

James也很友好地为上述方法提供了单元测试。

我认为你应该这样编写你的类

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

有关如何处理此案件的详细说明,请访问

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

  • 用问题注释属性

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
    [JsonConverter(typeof(SingleValueArrayConverter))]
    公共清单项目;
    
  • 扩展转换器以返回所需类型的列表,即使对于单个对象也是如此

    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;
        }
    }
    
    公共类SingleValueArrayConverter:JsonConverter
    {
    公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
    {
    抛出新的NotImplementedException();
    }
    公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
    {
    object retVal=新对象();
    if(reader.TokenType==JsonToken.StartObject)
    {
    反序列化(reader,typeof(T));
    retVal=new List(){instance};
    }else if(reader.TokenType==JsonToken.StartArray){
    retVal=序列化程序。反序列化(读取器,对象类型);
    }
    返回返回;
    }
    公共覆盖布尔CanConvert(类型objectType)
    {
    返回true;
    }
    }
    

  • 正如文章中提到的,此扩展并不完全通用,但如果您对获取列表很满意,它就可以工作。

    基于卡米洛·马丁内斯的上述回答,这是一种更现代、更安全、更精简、更完整的方法,使用了
    JsonConverter
    的通用版本和C#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}.")
            };
        }
    }
    
    内部类SingleObjectOrArrayJsonConverter:JsonConverter其中T:class,new()
    {
    公共重写void WriteJson(JsonWriter编写器、ICollection值、JsonSerializer序列化器)
    {
    serializer.Serialize(writer,value.Count==1?(对象)value.Single():value);
    }
    公共重写ICollection ReadJson(JsonReader阅读器,类型objectType,ICollection existingValue,bool hasExistingValue,JsonSerializer序列化程序)
    {
    返回reader.TokenType开关
    {
    JsonToken.StartObject=>新集合{serializer.Deserialize(reader)},
    JsonToken.StartArray=>serializer.Deserialize(reader),
    _=>抛出新ArgumentOutOfRangeException($“转换器不支持JSON令牌类型{reader.TokenType}。”)
    };
    }
    }
    
    然后对物业进行如下装饰:

    [JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
    public ICollection<OrderItem> items;
    
    [JsonConverter(typeof(SingleObjectOrArrayJsonConverter))]
    公共信息收集项目;
    
    我已经将属性类型从
    List
    更改为
    ICollection
    ,因为JSON POCO通常只需要这种较弱的类型,但是如果需要
    List