【问题标题】:Handling JSON response which sometimes doesn't have array处理有时没有数组的 JSON 响应
【发布时间】:2017-11-15 21:44:21
【问题描述】:

我在 C# 控制台应用程序中使用一些 JSON,对于一些数据,有一系列选项。示例 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 中的记录,OPTIONS 为空:

{"FIELD_NAME":"Product_Node3","VALUE":"","FIELD_ID":1740,"OPTIONS":{}}

如您所见,选项设置为 {},但我的理解是 {} 是一个空对象,而不是一个空数组。

当我尝试反序列化为 POCO 时,我收到一个异常,抱怨它需要 OPTIONS 属性中的 JSON 数组。

我的字段类:

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.

【问题讨论】:

标签: c# json


【解决方案1】:

当预期的 JSON 值类型(数组、集合或基元)与观察到的值类型不匹配时,Json.NET 将引发异常。由于对于您的 List&lt;Option&gt; OPTIONS,您希望跳过意外的值类型,因此您需要创建一个 custom JsonConverter,如下所示:

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,如下所示:

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将其用于所有收藏:

var settings = new JsonSerializerSettings
{
    Converters = { new TolerantCollectionConverter() },
};
var obj = JsonConvert.DeserializeObject<Field>(stringTest, settings);

注意事项:

  • 转换器仅适用于可写集合,因为它首先分配集合然后填充它。对于只读集合或数组,您需要先填充List&lt;T&gt;,然后从中分配只读集合或数组。

  • 我的假设是,当需要数组值时,您希望忽略一个空对象。如果您想将对象反序列化为集合项,然后将其添加到返回的集合中,您可以使用 How to handle both a single item and an array for the same property using JSON.net 中的 SingleOrArrayConverter&lt;T&gt;

  • 您的问题中显示的根 JSON 容器是一个对象——一组无序的名称/值对,以 { 开头并以 } 结尾——而不是一个数组。因此,您需要将其反序列化为 Field 而不是 List&lt;Field&gt;

示例fiddle

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-05
    • 1970-01-01
    • 2020-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-22
    • 1970-01-01
    相关资源
    最近更新 更多