我试图根据
step
字段解析每个 command
。为此,我决定将解析委托给使用工厂创建的 CustomElementJsonConverter::ReadJson
内的特定转换器。但是,如何调用转换器_converters[elementType])
?
我的方法是否有意义,或者我应该/可以采取其他方式吗?可以有多种元素类型。这就是为什么我决定按照我现在的方式做,这样我就不必修改
CustomElementJsonConverter
每个类型添加一个新类型。
目标类型
public class Message
{
[JsonConverter(typeof(CustomElementJsonConverter))]
[JsonProperty("data", Required = Required.Always)]
public Sequence Sequence { get; set; }
}
public class Step
{
public List<ElementData> Elements { get; set; }
}
public class Sequence
{
public List<Step> Steps { get; set; }
}
测试数据
var msg = $$"""
{
"data": [
[
{
"type": "type1",
"command": "Command1_Type1",
"prop1": "test1",
"prop2": "test2",
},
{
"type": "type1",
"command": "Command2_Type1",
"prop1": "test3",
"prop2": "test4",
}
],
[
{
"type": "type2",
"command": "Command1_Type2",
"prop3": 1,
}
]
]
}
""";
代码
public class CustomElementJsonConverter : JsonConverter<Step>
{
private static readonly Dictionary<string, JsonConverter> _converters;
static CustomElementJsonConverter()
{
foreach (var elementType in Enum.GetValues(typeof(ElementType)).Cast<ElementType>())
{
var elemTypeJsonName = elementType.GetEnumMemberValue().ToLower();
_converters[elemTypeJsonName] = ElementJsonConverterFactory.Create(elementType);
}
}
public override Step ReadJson(JsonReader reader, Type objectType, Step existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jElements = JArray.Load(reader);
var elements = new List<ElementData>();
foreach (var jElement in jElements)
{
var elementType = jElement["type"].Value<string>().ToLower();
var convertor = _converters[elementType];
//HOW TO INVOKE THE CONVERTOR?
}
return new Step { Elements = elements };
}
}
public static class ElementJsonConverterFactory
{
public static JsonConverter Create(ElementType elementType) => elementType switch
{
ElementType.Type1 => new ElementType1JsonConverter()
ElementType.Type2 => new ElementType2JsonConverter()
};
}
public class ElementType1JsonConverter : JsonConverter<ElementData>
{
public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// if CommandType == Command1_Type1 should parse and return object ElementData_For_Command1Type1
// if CommandType == Command2_Type1 should parse and return object ElementData_For_Command2Type1
}
public override void WriteJson(JsonWriter writer, ElementData value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class ElementType2JsonConverter : JsonConverter<ElementData>
{
public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// if CommandType == Command1_Type2 should parse and return object ElementData_For_Command1Type2
}
public override void WriteJson(JsonWriter writer, ElementData value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public enum ElementType
{
[EnumMember(Value = "type1")] Type1,
[EnumMember(Value = "type2")] Type2,
}
public class ElementData
{
[JsonProperty("type", Required = Required.Always)]
public ElementType ElementType { get; set; }
}
public class CommandElementData1 : ElementData
{
public enum CommandType
{
[EnumMember] Command1_Type1,
[EnumMember] Command2_Type1,
}
[JsonProperty(Required = Required.Always)]
public CommandType Command { get; set; }
}
public class ElementData_For_Command1Type1 : CommandElementData1
{
[JsonProperty(Required = Required.Always)]
public string Prop1 { get; set; }
[JsonProperty(Required = Required.Always)]
public string Prop2 { get; set; }
}
public class ElementData_For_Command2Type1 : CommandElementData1
{
[JsonProperty(Required = Required.Always)]
public string Prop1 { get; set; }
[JsonProperty(Required = Required.Always)]
public string Prop2 { get; set; }
}
public class CommandElementData2 : ElementData
{
public enum CommandType
{
[EnumMember] Command1_Type2,
}
[JsonProperty(Required = Required.Always)]
public CommandType Command { get; set; }
}
public class ElementData_For_Command1Type2 : CommandElementData2
{
[JsonProperty(Required = Required.Always)]
public int Prop3 { get; set; }
}
JsonConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
即可。它将进行必要的转换以调用派生类泛型方法JsonConverter<T>.ReadJson()
。如果传入的读取器已经正确定位,您可以将其作为第一个参数传递。如果您已将 JSON 加载到 JToken
层次结构中,则可以使用 JToken.CreateReader()
为您想要反序列化的特定令牌创建读取器。
因此你的
CustomElementJsonConverter.ReadJson()
应该看起来像这样:
public override Step ReadJson(JsonReader reader, Type objectType, Step existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null; // TODO : decide whether to return null or throw an exception.
var jElements = JArray.Load(reader);
var elements = jElements.Select(jElement =>
{
// TODO: check for null or missing missing `"type"`
var elementType = jElement["type"].Value<string>().ToLowerInvariant(); // FIXED: replaced ToLower with ToLowerInvariant
var convertor = _converters[elementType];
using var subReader = jElement.CreateReader();
return (ElementData)convertor.ReadJson(subReader.MoveToContentAndAssert(), typeof(ElementData), null, serializer);
}).ToList();
return new Step { Elements = elements };
}
如果您的数组非常大,为了减少内存开销,您可能需要迭代 JSON 数组并单独处理每个条目,而不是将整个数组加载到
JArray
中。这个版本做到了这一点:
public override Step ReadJson(JsonReader reader, Type objectType, Step existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null; // TODO : decide whether to return null or throw an exception.
var elements = reader.EnumerateArray().Select(jElement =>
{
// TODO: check for null or missing missing `"type"`
var elementType = jElement["type"].Value<string>().ToLowerInvariant(); // FIXED: replaced ToLower with ToLowerInvariant
var convertor = _converters[elementType];
using var subReader = jElement.CreateReader();
return (ElementData)convertor.ReadJson(subReader.MoveToContentAndAssert(), typeof(ElementData), null, serializer);
}).ToList();
return new Step { Elements = elements };
}
两个版本都使用以下类的扩展方法:
public static partial class JsonExtensions
{
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
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 static IEnumerable<JToken> EnumerateArray(this JsonReader reader)
{
reader.MoveToContentAndAssert().AssertTokenType(JsonToken.StartArray);
while (reader.ReadAndAssert().TokenType != JsonToken.EndArray)
yield return JToken.Load(reader);
}
}
备注:
当您首次创建
JsonReader
时,它位于第一个 JSON 标记之前。您必须至少调用一次 Read()
才能将其推进到第一个标记。另外,请务必检查来自 JsonReader.Read()
的退货。如果文件被截断,它可能会意外返回 false
。
上面的扩展方法
MoveToContentAndAssert()
会在必要时前进到第一个标记,并且还会跳过注释并针对截断的文件抛出异常。
您的转换器使用
"type"
属性来指示要反序列化的具体类型。序列化或反序列化值时,请务必使用不变区域性将值转换为小写。如果您使用本地化区域性,您的 JSON 可能无法跨区域设置反序列化。 (参见例如*https://stackoverflow.com/q/15822236*.)
ReadJson()
方法预计会检查无效和/或意外的 JSON 内容,并足够优雅地处理它,以抛出异常或让阅读器在退出时正确定位。例如,您需要决定如果数组值为 null 或数组项为 null 时它应如何表现。
对于
Type objectType
的 ReadJson()
参数,传递引用的声明类型,此处为 ElementData
。在多态情况下,转换器可能会返回更派生的类型。
您的转换器不支持填充现有的
Step
。如果您需要这样做,请修改 ReadJson()
,如下所示:
public override Step ReadJson(JsonReader reader, Type objectType, Step existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null; // TODO : decide whether to return null or throw an exception.
// Reuse the existing values if present.
var step = hasExistingValue ? existingValue : new Step();
step.Elements ??= new ();
step.Elements.AddRange(reader.EnumerateArray().Select(jElement =>
{
// TODO: check for null or missing missing `"type"`
var elementType = jElement["type"].Value<string>().ToLowerInvariant(); // FIXED: replaced ToLower with ToLowerInvariant
var convertor = _converters[elementType];
using var subReader = jElement.CreateReader();
return (ElementData)convertor.ReadJson(subReader.MoveToContentAndAssert(), typeof(ElementData), null, serializer);
}));
return step;
}
上述代码无法完全测试,因为问题缺乏可编译的mcve。
使用简化类型的演示此处。