如何使用JSON.net为同一属性同时处理单个项和数组?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (25)

我正在尝试修复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来处理这个问题?

提问于
用户回答回答于

处理这种情况的最佳方式是使用自定义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

fiddle: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);
    }

fiddle: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)

扫码关注云+社区