作为对Brian Rogers出色答案的一个小改动,这里有两个经过调整的.SingleOrArrayConverter<T>
首先,这是一个适用于所有本身不是集合的List<T>类型的版本:T
public class SingleOrArrayListConverter : JsonConverter
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
    readonly bool canWrite;
    readonly IContractResolver resolver;
    public SingleOrArrayListConverter() : this(false) { }
    public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null) { }
    public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
    {
        this.canWrite = canWrite;
        // Use the global default resolver if none is passed in.
        this.resolver = resolver ?? new JsonSerializer().ContractResolver;
    }
    static bool CanConvert(Type objectType, IContractResolver resolver)
    {
        Type itemType;
        JsonArrayContract contract;
        return CanConvert(objectType, resolver, out itemType, out contract);
    }
    static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
    {
        if ((itemType = objectType.GetListItemType()) == null)
        {
            itemType = null;
            contract = null;
            return false;
        }
        // Ensure that [JsonObject] is not applied to the type.
        if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
            return false;
        var itemContract = resolver.ResolveContract(itemType);
        // Not implemented for jagged arrays.
        if (itemContract is JsonArrayContract)
            return false;
        return true;
    }
    public override bool CanConvert(Type objectType) { return CanConvert(objectType, resolver); }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Type itemType;
        JsonArrayContract contract;
        if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), objectType));
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (IList)(existingValue ?? contract.DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
            list.Add(serializer.Deserialize(reader, itemType));
        return list;
    }
    public override bool CanWrite { get { return canWrite; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var list = value as ICollection;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
        // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
        if (list.Count == 1)
        {
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        }
    }
}
public static partial class JsonExtensions
{
    public static JsonReader MoveToContent(this JsonReader reader)
    {
        while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
            ;
        return reader;
    }
    internal static Type GetListItemType(this Type type)
    {
        // Quick reject for performance
        if (type.IsPrimitive || type.IsArray || type == typeof(string))
            return null;
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}
它可以按如下方式使用:
var settings = new JsonSerializerSettings
{
    // Pass true if you want single-item lists to be reserialized as single items
    Converters = { new SingleOrArrayListConverter(true) },
};
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);
笔记:
JToken转换器避免了将整个 JSON 值作为层次结构预加载到内存中的需要。
 
该转换器不适用于其项目也被序列化为集合的列表,例如List<string []>
 
传递给构造函数的布尔canWrite参数控制是否将单元素列表重新序列化为 JSON 值或 JSON 数组。
 
转换器ReadJson()使用existingValueif pre-allocated 以支持填充 get-only 列表成员。
 
其次,这是一个适用于其他通用集合的版本,例如ObservableCollection<T>:
public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
    where TCollection : ICollection<TItem>
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
    readonly bool canWrite;
    public SingleOrArrayCollectionConverter() : this(false) { }
    public SingleOrArrayCollectionConverter(bool canWrite) { this.canWrite = canWrite; }
    public override bool CanConvert(Type objectType)
    {
        return typeof(TCollection).IsAssignableFrom(objectType);
    }
    static void ValidateItemContract(IContractResolver resolver)
    {
        var itemContract = resolver.ResolveContract(typeof(TItem));
        if (itemContract is JsonArrayContract)
            throw new JsonSerializationException(string.Format("Item contract type {0} not supported.", itemContract));
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        ValidateItemContract(serializer.ContractResolver);
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            list.Add(serializer.Deserialize<TItem>(reader));
        return list;
    }
    public override bool CanWrite { get { return canWrite; } }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ValidateItemContract(serializer.ContractResolver);
        var list = value as ICollection<TItem>;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
        if (list.Count == 1)
        {
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        }
    }
}
然后,如果您的模型正在使用例如 an ObservableCollection<T>for some T,您可以按如下方式应用它:
class Item
{
    public string Email { get; set; }
    public int Timestamp { get; set; }
    public string Event { get; set; }
    [JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
    public ObservableCollection<string> Category { get; set; }
}
笔记:
- 除了 的注释和限制之外
SingleOrArrayListConverter,该TCollection类型必须是读/写的并且具有无参数的构造函数。 
演示在这里摆弄基本的单元测试。