Rob*_*aws 82 json.net sendgrid
我正在尝试修复我的SendGridPlus库来处理SendGrid事件,但是我在API中对类别的处理方式不一致时遇到了一些麻烦.
在以下示例从SendGrid API引用获取的有效内容中,您会注意到category每个项的属性可以是单个字符串,也可以是字符串数组.
[
  {
    "email": "john.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  },
  {
    "email": "jane.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  }
]
看起来像我这样制作JSON.NET的选择是在字符串进入之前修复字符串,或者配置JSON.NET以接受不正确的数据.如果我能逃脱它,我宁愿不做任何字符串解析.
有没有其他方法我可以使用Json.Net处理这个?
Bri*_*ers 164
处理这种情况的最佳方法是使用自定义JsonConverter.
在我们到达转换器之前,我们需要定义一个类来反序列化数据.对于Categories可以在单个项和数组之间变化的属性,将其定义为a List<string>并使用[JsonConverter]属性对其进行标记,   以便JSON.Net知道为该属性使用自定义转换器.我还建议使用[JsonProperty]属性,以便可以为成员属性提供有意义的名称,而不依赖于JSON中定义的名称.
class Item
{
    [JsonProperty("email")]
    public string Email { get; set; }
    [JsonProperty("timestamp")]
    public int Timestamp { get; set; }
    [JsonProperty("event")]
    public string Event { get; set; }
    [JsonProperty("category")]
    [JsonConverter(typeof(SingleOrArrayConverter<string>))]
    public List<string> Categories { get; set; }
}
这是我如何实现转换器.注意我已经使转换器通用,以便它可以根据需要与字符串或其他类型的对象一起使用.
class SingleOrArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(List<T>));
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }
    public override bool CanWrite
    {
        get { return false; }
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
这是一个简短的程序,演示了使用您的样本数据的转换器:
class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
          {
            ""email"": ""john.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": [
              ""newuser"",
              ""transactional""
            ],
            ""event"": ""open""
          },
          {
            ""email"": ""jane.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": ""olduser"",
            ""event"": ""open""
          }
        ]";
        List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
        foreach (Item obj in list)
        {
            Console.WriteLine("email: " + obj.Email);
            Console.WriteLine("timestamp: " + obj.Timestamp);
            Console.WriteLine("event: " + obj.Event);
            Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
            Console.WriteLine();
        }
    }
}
最后,这是上面的输出:
email: john.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional
email: jane.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: olduser
小提琴:https://dotnetfiddle.net/lERrmu
编辑
如果你需要走另一条路,即序列化,同时保持相同的格式,你可以实现WriteJson()转换器的方法,如下所示.(务必删除CanWrite覆盖或更改它以返回true,否则WriteJson()永远不会被调用.)
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        List<T> list = (List<T>)value;
        if (list.Count == 1)
        {
            value = list[0];
        }
        serializer.Serialize(writer, value);
    }
小提琴:https://dotnetfiddle.net/XG3eRy
多年来我一直在研究这个问题,感谢Brian的回答.我要补充的是vb.net的答案!:
Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
    Inherits JsonConverter
    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Throw New NotImplementedException()
    End Sub
    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim retVal As Object = New [Object]()
        If reader.TokenType = JsonToken.StartObject Then
            Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
            retVal = New List(Of T)() From { _
                instance _
            }
        ElseIf reader.TokenType = JsonToken.StartArray Then
            retVal = serializer.Deserialize(reader, objectType)
        End If
        Return retVal
    End Function
    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return False
    End Function
End Class
然后在你班上:
 <JsonProperty(PropertyName:="JsonName)> _
 <JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
    Public Property YourLocalName As List(Of YourObject)
希望这能为您节省一些时间
作为布赖恩罗杰斯的伟大答案的一个小变化,这里有两个经过调整的.SingleOrArrayConverter<T>
首先,这是一个适用于所有本身不是集合的List<T>类型的版本T:
public class SingleOrArrayListConverter : JsonConverter
{
    // Adapted from this answer /sf/answers/1329802071/
    // to /sf/ask/1329627981/
    // by Brian Rogers /sf/users/718441/
    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);
笔记:
转换器无需将整个 JSON 值作为JToken层次结构预加载到内存中。
转换器不适用于其项目也被序列化为集合的列表,例如 List<string []>
canWrite传递给构造函数的布尔参数控制是将单元素列表重新序列化为 JSON 值还是 JSON 数组。
转换器ReadJson()使用existingValueif 预分配以支持填充仅获取列表成员。
其次,这是一个适用于其他通用集合的版本,例如ObservableCollection<T>:
public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
    where TCollection : ICollection<TItem>
{
    // Adapted from this answer /sf/answers/1329802071/
    // to /sf/ask/1329627981/
    // by Brian Rogers /sf/users/718441/
    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类型必须是读/写的并且具有无参数构造函数。演示摆弄基本单元测试here。
| 归档时间: | 
 | 
| 查看次数: | 27120 次 | 
| 最近记录: |