使用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不是一个集合。
我发现这篇文章描述了一个类似的问题,所以我试图沿着这条路走下去:Deserialize JSON, sometimes value is an array, sometimes "" (blank string)
我的转换器看起来像:
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
reader.Value的值是“永久链接”。我可以清楚地看到交换机中没有JsonToken.PropertyName的情况。
我的转换器中是否需要采取不同的措施?谢谢你的帮助。
答案 0 :(得分:28)
"Using a Custom JsonConverter to fix bad JSON results"提供了有关如何处理此案例的详细说明。
总而言之,您可以扩展默认的JSON.NET转换器
使用问题
注释属性[JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
public List<OrderItem> items;
扩展转换器以返回所需类型的列表,即使对于单个对象
也是如此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;
}
}
正如文章中所提到的,这个扩展并不是完全一般的,但是如果你能获得一个列表就可以了。
答案 1 :(得分: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});
}
}
詹姆斯也非常友好地为上述方法提供单元测试。
答案 2 :(得分:2)
看一下c#框架中的System.Runtime.Serialization命名空间,它会让你快速到达你想要的位置。
如果你愿意,可以查看this项目中的一些示例代码(不是试图插入我自己的工作,但我刚刚完成了你正在做的事情,但使用了不同的源API。
希望它有所帮助。答案 3 :(得分:1)
基于上述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(EmptyObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;
我已将属性类型从List<>
更改为ICollection<>
,因为JSON POCO通常只需要是这种较弱的类型,但是如果需要List<>
,则只需替换{{1 }}和ICollection
以及以上所有代码中的Collection
。
答案 4 :(得分:1)
.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 属性定义
答案 5 :(得分:0)
阐述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);
为Martinez和mfanto喝彩!
信不信由你,这将与子项目一起使用。 (甚至可能必须这样做。)因此,在我的InsuranceInfo中,如果我还有另一个对象/数组混合对象,请在该属性上再次使用它。
这还将允许您将对象重新序列化回json。重新序列化时,它将始终是一个数组。
答案 6 :(得分:-3)
我认为你应该像这样写你的课...... !!!
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
{
}