JSON.net を使用して、同じプロパティの単一の項目と配列の両方を処理する方法 質問する

JSON.net を使用して、同じプロパティの単一の項目と配列の両方を処理する方法 質問する

SendGrid イベントを処理するために SendGridPlus ライブラリを修正しようとしていますが、API でのカテゴリの扱いが一貫していないために問題が発生しています。

次の例では、SendGrid API リファレンスcategory、各項目のプロパティは単一の文字列または文字列の配列のいずれかになることがわかります。

[
  {
    "email": "[email protected]",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  },
  {
    "email": "[email protected]",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  }
]

JSON.NET をこのようにするためのオプションは、文字列が入力される前に修正するか、JSON.NET が不正なデータを受け入れるように構成することのようです。できれば、文字列の解析は行わないほうがいいと思います。

Json.Net を使用してこれを処理できる他の方法はありますか?

ベストアンサー1

この状況に対処する最善の方法は、カスタムJsonConverter

コンバーターに進む前に、データをデシリアライズするクラスを定義する必要があります。Categories単一の項目と配列の間で変化するプロパティについては、それを として定義しList<string>、属性でマークして[JsonConverter]、JSON.Net がそのプロパティにカスタム コンバーターを使用することを認識できるようにします。また、メンバー プロパティに JSON での定義とは関係なく意味のある名前を付けることができるように、属性を使用することをお勧めします[JsonProperty]

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>>();
        }
        if (token.Type == JTokenType.Null)
        {
            return null;
        }
        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"": ""[email protected]"",
            ""timestamp"": 1337966815,
            ""category"": [
              ""newuser"",
              ""transactional""
            ],
            ""event"": ""open""
          },
          {
            ""email"": ""[email protected]"",
            ""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: [email protected]
timestamp: 1337966815
event: open
categories: newuser, transactional

email: [email protected]
timestamp: 1337966815
event: open
categories: olduser

フィドル:https://dotnetfiddle.net/lERrmu

編集

同じ形式を維持しながら、逆の方法、つまりシリアル化を行う必要がある場合は、WriteJson()以下に示すようにコンバーターのメソッドを実装できます。(CanWriteオーバーライドを削除するか、 return に変更してください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

おすすめ記事