转换器不适用于其项目也被序列化为集合的列表,例如
List<string []>
问题是,我需要反序列化一个数组,该数组可能是字符串数组,也可能是字符串数组的数组。所以这个例子说它在我的情况下不起作用。
例如,
{"values": [ "foo", "bar" ]}
对
{"values": [ [ "foo", "bar" ], [ "baz", "qux" ] ]}
我正在考虑实现一个自定义类
public class SingleOrArray<T> : ICollection<T>
{
public bool IsArray { get; set; } = false;
public T Value { get; set; }
public T[] Values { get { return _items.ToArray() } }
private List<T> _items = new();
// etc.
}
使用如下所示的反序列化对象:
public class MyObject
{ [JsonConverter(typeof(SingleOrArrayCollectionConverter<SingleOrArray<string[]>, string[]>))]
public List<SingleOrArray<string[]>> Value { get; set; }
}
但我不知道如何实现 JsonConverter 来处理这个问题。
这是我的单元测试,它们正在通过,但我需要它来处理这种情况:
string[]
[Fact]
public void SingleItemTest()
{
// Arrange
var testData = "{ \"value\": \"a\" }";
// Act
var target = JsonConvert.DeserializeObject<MyObject>(testData);
// Assert
Assert.False(target!.Value.IsArray);
Assert.Equal("a", target!.Value.Value);
}
[Fact]
public void ArrayItemTest1()
{
// Arrange
var testData = "{ \"value\": [\"a\"] }";
// Act
var target = JsonConvert.DeserializeObject<MyObject>(testData);
// Assert
Assert.True(target!.Value.IsArray);
Assert.Equal("a", target!.Value.Values[0]);
}
,我认为将您的
SingleOrArray<T>
属性定义为Values
:会更容易
List<List<string>>
假设您这样做,您需要决定如何反序列化像
public class Root
{
[JsonConverter(typeof(SingleOrArray2DListConverter<string>))]
public List<List<string>> Values { get; set; } = new();
}
这样的一维数组。 你有两个选择:
首先,您可以将每个内部项目反序列化为自己的数组:["foo","bar"]
。通过这种设计,您可以使用
[["foo"], ["bar"]]
从
这个答案到如何使用JSON.net处理同一属性的单个项目和数组,方法是将转换器应用于外部列表中的每个项目。 您可以通过设置
SingleOrArrayCollectionConverter<List<string>, string>
: 来做到这一点
JsonPropertyAttribute.ItemConverterType
请注意,通过这种设计,嵌套不一致的 JSON 数组(例如
public partial class Root
{
[JsonProperty(ItemConverterType = typeof(SingleOrArrayCollectionConverter<List<string>, string>))]
public List<List<string>> Values { get; set; } = new();
}
)会被自动处理并变成 ["foo",["bar"]]
演示小提琴#1 这里。
其次,您可以将所有内部项目反序列化为单个内部数组:
[["foo"],["bar"]]
。
通过这种设计,无法使用链接问题中的转换器,您必须从头开始编写自己的转换器:
[["foo","bar"]]
然后按如下方式应用:
public class SingleOrArray2DListConverter<TItem> : JsonConverter<List<List<TItem>>>
{
public SingleOrArray2DListConverter() : this(false) { }
public SingleOrArray2DListConverter(bool canWrite) { this.CanWrite = canWrite; }
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 List<List<TItem>>? ReadJson(JsonReader reader, Type objectType, List<List<TItem>>? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return default;
var outerList = existingValue ?? (List<List<TItem>>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator!();
if (reader.TokenType == JsonToken.StartArray)
{
int startDepth = reader.Depth;
switch (reader.ReadToContentAndAssert().TokenType)
{
case JsonToken.EndArray:
// Empty 2d array
break;
case JsonToken.StartArray:
{// Nonempty 2d array
do
{
// TODO: decide whether to allow null inner lists. If you want to allow them, remove the new()
// And change the converter type to `List<List<TItem>?>`
outerList.Add(serializer.Deserialize<List<TItem>>(reader) ?? new());
}
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray);
}
break;
default:
{// Nonempty 1d array
var list = new List<TItem>();
do
{
list.Add(serializer.Deserialize<TItem>(reader)!);
}
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray);
outerList.Add(list);
}
break;
}
// Assert we are positioned at the end of the outer array with the same depth.
Debug.Assert(reader.TokenType == JsonToken.EndArray && reader.Depth == startDepth);
}
else
{
// A single non-array item.
outerList.Add(new() { serializer.Deserialize<TItem>(reader)! });
}
return outerList;
}
public override bool CanWrite { get; }
public override void WriteJson(JsonWriter writer, List<List<TItem>>? value, JsonSerializer serializer)
{
if (value == null)
writer.WriteNull();
else if (value.Count == 1)
{
foreach (var item in value)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in value)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
// Base utilities
public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
ArgumentNullException.ThrowIfNull(reader);
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
ArgumentNullException.ThrowIfNull(reader);
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
注意,这种设计无法处理
public class Root
{
[JsonConverter(typeof(SingleOrArray2DListConverter<string>))]
public List<List<string>> Values { get; set; } = new();
}
等嵌套不一致的JSON数组,遇到时会抛出异常。
演示小提琴 #2 这里。