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

发布于 2024-10-20 20:53:44 字数 2457 浏览 1 评论 0原文

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

从简单的墙贴返回的 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;
}

工作正常,但我现在得到一个新的异常:

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

reader.Value 的值为“permalink”。我可以在开关中清楚地看到 JsonToken.PropertyName 没有大小写。

我需要在转换器中做一些不同的事情吗?感谢您的任何帮助。

I'm having a bit of trouble deserializing data returned from Facebook using the JSON.NET libraries.

The JSON returned from just a simple wall post looks like:

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

The JSON returned for a photo looks like:

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

Everything works great and I have no problems. I've now come across a simple wall post from a mobile client with the following JSON, and deserialization now fails with this one single 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"

Here is the class I am deserializing as:

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

Without using the FacebookMediaJsonConverter, I get an error: Cannot deserialize JSON object into type 'System.Collections.Generic.List`1[FacebookMedia]'.
which makes sense, since in the JSON, Media is not a collection.

I found this post which describes a similar problem, so I've attempted to go down this route: Deserialize JSON, sometimes value is an array, sometimes "" (blank string)

My converter looks like:

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

Which works fine, except I now get a new exception:

Inside JsonSerializerInternalReader.cs, CreateValueInternal(): Unexpected token while deserializing object: PropertyName

The value of reader.Value is "permalink". I can clearly see in the switch that there's no case for JsonToken.PropertyName.

Is there something I need to do differently in my converter? Thanks for any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

何以畏孤独 2024-10-27 20:53:44

有关如何处理这种情况的非常详细的说明,请访问 “使用用于修复错误 JSON 结果的自定义 JsonConverter”

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

  1. 用问题注释属性

    [JsonConverter(typeof(SingleValueArrayConverter))]
    公共列表项目;
    
  2. 扩展转换器以返回所需类型的列表,即使对于单个对象也是如此

    public class SingleValueArrayConverter; : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, 对象值, JsonSerializer 序列化器)
        {
            抛出新的NotImplementedException();
        }
    
        公共覆盖对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化器)
        {
            对象 retVal = new Object();
            if (reader.TokenType == JsonToken.StartObject)
            {
                T 实例 = (T)serializer.Deserialize(reader, typeof(T));
                retVal = new List() { 实例 };
            } else if (reader.TokenType == JsonToken.StartArray) {
                retVal = serializer.Deserialize(reader, objectType);
            }
            返回retVal;
        }
    
        公共覆盖 bool CanConvert(类型 objectType)
        {
            返回真;
        }
    }
    

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

A very detailed explanation on how to handle this case is available at "Using a Custom JsonConverter to fix bad JSON results".

To summarize, you can extend the default JSON.NET converter doing

  1. Annotate the property with the issue

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
  2. Extend the converter to return a list of your desired type even for a single object

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

As mentioned in the article this extension is not completely general but it works if you are fine with getting a list.

策马西风 2024-10-27 20:53:44

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

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

The developer of JSON.NET ended up helping on the projects codeplex site. Here is the solution:

The problem was, when it was a JSON object, I wasn't reading past the attribute. Here is the correct 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 was also kind enough to provide unit tests for the above method.

眸中客 2024-10-27 20:53:44

根据上面 Camilo Martinez 的回答,这是一种更现代、类型安全、更精简和完整的方法,使用 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}.")
        };
    }
}

然后这样装饰属性:

[JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;

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

Based on Camilo Martinez's answer above, this is a more modern, type-safe, leaner and complete approach using the generic version of JsonConverter and C# 8.0 as well as implementing the serialization part. It also throws an exception for tokens other than the two expected according to the question. Code should never do more than required otherwise you run the risk of causing a future bug due to mishandling unexpected data.

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

And then decorate the property thus:

[JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;

I've changed the property type from List<> to ICollection<> as a JSON POCO typically need only be this weaker type, but if List<> is required, then just replaced ICollection and Collection with List in all the above code.

携君以终年 2024-10-27 20:53:44

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

然后在属性上写下:

[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }

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

return JsonConvert.DeserializeObject<T>(json);

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

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

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

Expounding upon Martinez and mfanto's answer for Newtonsoft. It does work with Newtonsoft:

Here is an example of doing it with an array instead of a list (and correctly named).

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

Then over the attribute write this:

[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }

Newtonsoft will do the rest for you.

return JsonConvert.DeserializeObject<T>(json);

Cheers to Martinez and mfanto!

Believe it or not, this will work with sub items. (It may even have to.) So... inside of my InsuranceInfo, if I have another object/array hybrid, use this again on that property.

This will also allow you to reserialize the object back to json. When it does reserialize, it will always be an array.

橙味迷妹 2024-10-27 20:53:44

看一下 C# 框架中的 System.Runtime.Serialization 命名空间,它将带您快速到达您想要的位置。

如果需要,您可以查看项目中的一些示例代码(不尝试插入我自己的工作,但我刚刚完成了与您正在做的几乎完全相同的工作,但使用了不同的源 API

希望它有所帮助。

take a look at the System.Runtime.Serialization namespace in the c# framework, it's going to get you to where you want to be very quickly.

If you want, you can check out some example code in this project (not trying to plug my own work but i just finished pretty much exactly what you are doing but with a different source api.

hope it helps.

埖埖迣鎅 2024-10-27 20:53:44

.Net框架

using Newtonsoft.Json;
using System.IO;   

public Object SingleObjectOrArrayJson(string strJson)
{   
    if(String.IsNullOrEmpty(strJson))
    {
       //Example
       strJson= @"{
        'CPU': 'Intel',
        'PSU': '500W',
        'Drives': [
          'DVD read/writer'
          /*(broken)*/,
          '500 gigabyte hard drive',
          '200 gigabyte hard drive'
        ]
      }";
    }

    JsonTextReader reader = new JsonTextReader(new StringReader(strJson));
    
    //Initialize Read
    reader.Read();
    
        if (reader.TokenType == JsonToken.StartArray)
        {
            return JsonConvert.DeserializeObject<List<Object>>(strJson);
        }
        else
        {
            Object media = JsonConvert.DeserializeObject<Object>(strJson);
            return new List<Object>(new[] {media});
        }
}

注意:
“对象”必须根据您的响应的 Json 属性进行定义

.Net Framework

using Newtonsoft.Json;
using System.IO;   

public Object SingleObjectOrArrayJson(string strJson)
{   
    if(String.IsNullOrEmpty(strJson))
    {
       //Example
       strJson= @"{
        'CPU': 'Intel',
        'PSU': '500W',
        'Drives': [
          'DVD read/writer'
          /*(broken)*/,
          '500 gigabyte hard drive',
          '200 gigabyte hard drive'
        ]
      }";
    }

    JsonTextReader reader = new JsonTextReader(new StringReader(strJson));
    
    //Initialize Read
    reader.Read();
    
        if (reader.TokenType == JsonToken.StartArray)
        {
            return JsonConvert.DeserializeObject<List<Object>>(strJson);
        }
        else
        {
            Object media = JsonConvert.DeserializeObject<Object>(strJson);
            return new List<Object>(new[] {media});
        }
}

Note:
"Object" must be defined according to the Json attributes of your response

最好是你 2024-10-27 20:53:44

我认为你应该这样写你的课程......!!!

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

I think you should write your class like this...!!!

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
    {
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文