JSON反序列化句柄[]

JSON Deserialization Handle []

我正在使用 Newtonsoft.Json 反序列化一个具有挑战性的 JSON 文件,并且遇到了以下 属性 问题。此文件来自第 3 方,因此我无法对其进行改进,因此不得不按原样处理它。

它正确地将以下 属性 表示为:

{
   "propertyName":[
      {
         "value1":"value",
         "value2":"value",
         "value3":"value"
      }
   ]
}

然而,至少还有一个 属性 显示为:

{
   "propertyName":[
      []
   ]
}

我需要的是对于任何发生上述第二种情况的实例,它都会被忽略并被视为空数组,即如下所示:

"propertyNames": []

作为参考,这是我在 class 中对此 属性 的定义:

[JsonProperty("propertyNames")]
public List<PropertyName> PropertyNames { get; set; }

我已经使用 JsonConvertor 处理了此文件的其他问题,还修改了 JSON 文件的 class 定义。

您的 public List<PropertyName> PropertyNames 包含应序列化为 JSON 对象的项目,但由于某种原因,服务器偶尔会包含一个 空数组 作为项目在数组中。您想默默地过滤掉这些项目。

一种方法是为所有 List<T> 类型引入 custom JsonConverter,在读取 JSON 数组时跳过数组项。以下转换器完成这项工作:

public class ArrayItemFilteringListConverter : JsonConverter
{
    static readonly IContractResolver defaultResolver = JsonSerializer.CreateDefault().ContractResolver;
    readonly IContractResolver resolver;

    public ArrayItemFilteringListConverter() : this(null) { }

    public ArrayItemFilteringListConverter(IContractResolver resolver) => this.resolver = resolver ?? defaultResolver;

    public override bool CanConvert(Type objectType) => CanConvert(resolver, objectType, out _);

    static bool CanConvert(IContractResolver resolver, Type objectType, out Type itemType)
    {
        if (objectType.IsArray || objectType.IsPrimitive || objectType == typeof(string) || !typeof(IList).IsAssignableFrom(objectType))
        {
            itemType = null;
            return false;
        }
        itemType = objectType.GetListItemType();
        if (itemType == null)
            return false;
        if (resolver.ResolveContract(itemType) is JsonArrayContract)
            return false;
        return true;
    }
    
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (!CanConvert(serializer.ContractResolver, objectType, out var itemType))
            throw new JsonException(string.Format("Invalid collection type {0}", objectType));
        var contract = serializer.ContractResolver.ResolveContract(objectType);

        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("Invalid start token {0}", reader.TokenType));
        
        var list = existingValue as IList ?? (IList)contract.DefaultCreator();
        
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.EndArray:
                    return list;
                case JsonToken.StartArray:
                case JsonToken.Comment:
                    reader.Skip();
                    break;
                default:
                    // Here we take advantage of the fact that List<T> implements the non-generic IList interface.
                    list.Add(serializer.Deserialize(reader, itemType));
                    break;
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed array at path: " + reader.Path);     
    }   

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
    
    public static Type GetListItemType(this Type type)
    {
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

然后按如下方式将其应用于您的数据模型:

public class Root    
{
    [JsonConverter(typeof(ArrayItemFilteringListConverter))]
    public List<PropertyValue> propertyName { get; set; } 
}

备注:

  • 转换器适用于类型 T 未作为数组序列化为 JSON 的任何 List<T> 类型。 IE。转换器不能应用于 List<string []> 属性.

  • 服务器为默认或未初始化的对象发送空数组的问题似乎时有发生。参见例如 其中的要求是映射一个空的 JSON 数组,它应该是一个 JSON 对象到 null.

演示 fiddle here.