Brian Rogersによるこの回答と、 JSON.net を使用して同じプロパティの単一項目と配列の両方を処理する方法に対する他の回答に触発されて、着信 JSON 値が配列であるかどうかをチェックするジェネリックを作成できます。、タイプのアイテムを逆シリアル化し、適切なリストにラップされたアイテムを返します。さらに良いことに、シリアライゼーション グラフで検出されたすべてのリスト タイプに対して、そのようなコンバーターを作成する を作成できます。JsonConverter<List<T>>
T
JsonConverterFactory
List<T>
最初に、次のコンバーターとコンバーター ファクトリを定義します。
public class SingleOrArrayConverter<TItem> : SingleOrArrayConverter<List<TItem>, TItem>
{
public SingleOrArrayConverter() : this(true) { }
public SingleOrArrayConverter(bool canWrite) : base(canWrite) { }
}
public class SingleOrArrayConverterFactory : JsonConverterFactory
{
public bool CanWrite { get; }
public SingleOrArrayConverterFactory() : this(true) { }
public SingleOrArrayConverterFactory(bool canWrite) => CanWrite = canWrite;
public override bool CanConvert(Type typeToConvert)
{
var itemType = GetItemType(typeToConvert);
if (itemType == null)
return false;
if (itemType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(itemType))
return false;
if (typeToConvert.GetConstructor(Type.EmptyTypes) == null || typeToConvert.IsValueType)
return false;
return true;
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var itemType = GetItemType(typeToConvert);
var converterType = typeof(SingleOrArrayConverter<,>).MakeGenericType(typeToConvert, itemType);
return (JsonConverter)Activator.CreateInstance(converterType, new object [] { CanWrite });
}
static Type GetItemType(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];
// Add here other generic collection types as required, e.g. HashSet<> or ObservableCollection<> or etc.
}
type = type.BaseType;
}
return null;
}
}
public class SingleOrArrayConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new()
{
public SingleOrArrayConverter() : this(true) { }
public SingleOrArrayConverter(bool canWrite) => CanWrite = canWrite;
public bool CanWrite { get; }
public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return null;
case JsonTokenType.StartArray:
var list = new TCollection();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
list.Add(JsonSerializer.Deserialize<TItem>(ref reader, options));
}
return list;
default:
return new TCollection { JsonSerializer.Deserialize<TItem>(ref reader, options) };
}
}
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
if (CanWrite && value.Count == 1)
{
JsonSerializer.Serialize(writer, value.First(), options);
}
else
{
writer.WriteStartArray();
foreach (var item in value)
JsonSerializer.Serialize(writer, item, options);
writer.WriteEndArray();
}
}
}
JsonSerializerOptions.Converters
次に、逆シリアル化の前にコンバーター ファクトリを追加します。
var options = new JsonSerializerOptions
{
Converters = { new SingleOrArrayConverterFactory() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var list = JsonSerializer.Deserialize<List<Item>>(json, options);
または、次を使用して、オプションまたはデータ モデルに特定のコンバーターを直接追加しますJsonConverterAttribute
。
class Item
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Category { get; set; }
}
データ モデルが他のタイプのコレクションを使用している場合、たとえば、次のようObservableCollection<string>
に下位レベルのコンバーターを適用できます。SingleOrArrayConverter<TCollection, TItem>
[JsonConverter(typeof(SingleOrArrayConverter<ObservableCollection<string>, string>))]
public ObservableCollection<string> Category { get; set; }
ノート:
逆シリアル化中にのみコンバーターを適用する場合はcanWrite: false
、パラメーター化されたコンストラクターに渡します。
Converters = { new SingleOrArrayConverterFactory(canWrite: false) }
コンバーターは引き続き使用されますが、無条件にデフォルトのシリアライゼーションを生成します。
2d
コンバーターは、ギザギザやnD
などのコレクションには実装されていませんList<List<string>>
。また、配列および読み取り専用コレクションに対しても実装されていません。
Serializer support for easy object and collection converters #1562によるとJsonConverter<T>
、非同期Read()
メソッドがないため、
既存の [JsonConverter] モデルの制限は、デシリアライゼーション中に「先読み」して、現在の JSON レベルまでバッファを完全に設定する必要があることです。この先読みは、async+stream JsonSerializer
deserialize メソッドが呼び出され、そのコンバーターの現在の JSON が StartArray または StartObject トークンで始まる場合にのみ発生します。
したがって、このコンバーターを使用して、潜在的に非常に大きな配列を逆シリアル化すると、パフォーマンスが低下する可能性があります。
同じスレッドで説明されているように、コンバーター API は System.Text.Json - 5.0 で再設計され、配列とオブジェクトのコンバーターによる逆シリアル化を完全にサポートする可能async
性があります。 "Core") が最終的にリリースされます。
ここでデモフィドル。