处理有时没有数组的JSON响应 [英] Handling JSON response which sometimes doesn't have array
问题描述
我在C#控制台应用程序中使用了一些JSON,对于某些数据,有很多选项. JSON示例:
I am consuming some JSON in a C# console application and for some of the data, there is an array of options. Example JSON:
{
"FIELD_NAME": "Survey",
"VALUE": "",
"FIELD_ID": 1234,
"OPTIONS":[
{ "VALUE": "GENERAL", "DISPLAY_ORDER": 1, "DISPLAY": "GENERAL" },
{ "VALUE": "HPEFS", "DISPLAY_ORDER": 3, "DISPLAY": "HPEFS" },
{ "VALUE": "NONE", "DISPLAY_ORDER": 3, "DISPLAY": "NONE" }]
}
但有时对于JSON中的记录,选项为空:
But sometimes for records in the JSON the OPTIONS is empty:
{"FIELD_NAME":"Product_Node3","VALUE":"","FIELD_ID":1740,"OPTIONS":{}}
如您所见,选项设置为{},但据我了解,{}是一个空对象,而不是一个空数组.
As you can see the options is set to {} but it is my understanding that {} is an empty object, not an empty array.
当我尝试反序列化为POCO时,我收到一个异常,抱怨它在OPTIONS属性中需要一个JSON数组.
When I try deserialize to a POCO I get an exception complaining that it requires a JSON array in the OPTIONS property.
我的领域课:
public class Field
{
public string FIELD_NAME { get; set; }
public string VALUE { get; set; }
public int FIELD_ID { get; set; }
public List<Option> OPTIONS { get; set;
}
}
和选项类:
public class Option
{
public string VALUE { get; set; }
public int DISPLAY_ORDER { get; set; }
public string DISPLAY { get; set; }
}
导致此异常的代码是:
var stringTest = File.ReadAllText("json.txt");
var data = JsonConvert.DeserializeObject<List<Field>>(stringTest);
例外是:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[testproj.Option]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
推荐答案
当预期的JSON值类型(数组,集合或基元)与观察到的值类型不匹配时,Json.NET将引发异常.由于对于List<Option> OPTIONS
,您希望跳过意外的值类型,因此需要创建
Json.NET will throw an exception when the expected JSON value type (array, collection or primitive) does not match the observed value type. Since, in the case of your List<Option> OPTIONS
, you want unexpected value types to be skipped, you will need to create a custom JsonConverter
such as the following:
public class TolerantCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string) || objectType.IsArray)
return false;
return objectType.GetCollectionItemTypes().Count() == 1;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.StartArray)
{
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
}
else
{
reader.Skip();
return existingValue;
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class TypeExtensions
{
/// <summary>
/// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<Type> GetCollectionItemTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
yield return intType.GetGenericArguments()[0];
}
}
}
}
然后按如下所示将其应用于Field
:
Then apply it to Field
as follows:
public class Field
{
public string FIELD_NAME { get; set; }
public string VALUE { get; set; }
public int FIELD_ID { get; set; }
[JsonConverter(typeof(TolerantCollectionConverter))]
public List<Option> OPTIONS { get; set; }
}
或通过 JsonSerializerSettings
将其用于所有收藏集:
Or use it for all collections via JsonSerializerSettings
:
var settings = new JsonSerializerSettings
{
Converters = { new TolerantCollectionConverter() },
};
var obj = JsonConvert.DeserializeObject<Field>(stringTest, settings);
注意:
-
转换器仅适用于可写的集合,因为它先分配集合,然后填充它.对于只读集合或数组,您需要先填充
List<T>
,然后从中分配只读集合或数组.
The converter only works for collections that are writable, since it allocates the collection first and then populates it. For read-only collections or arrays you need to populate a
List<T>
first then allocate the read-only collection or array from it.
我在这里的假设是,当需要数组值时,您想忽略一个空对象.相反,如果您想将对象反序列化为集合项,然后将其添加到返回的集合中,则可以使用
My assumption here is that you want to ignore an empty object when an array value is expected. If instead you want to deserialize the object into a collection item then add that to the returned collection you could use SingleOrArrayConverter<T>
from How to handle both a single item and an array for the same property using JSON.net.
您的问题中显示的JSON根容器是一个对象-一个以{
开头并以}
结束的名称/值对的无序集合-而不是数组.因此,您需要将其反序列化为Field
而不是List<Field>
.
The root JSON container shown in your question is an object -- an unordered set of name/value pairs that begins with {
and ends with }
-- rather than an array. Thus you need to deserialize it as a Field
not a List<Field>
.
示例小提琴.
这篇关于处理有时没有数组的JSON响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!