提问人:theateist 提问时间:11/15/2023 更新时间:11/17/2023 访问量:50
如何从其他JsonConverter内部调用JsonConverter?
How to invoke JsonConverter from inside other JsonConverter?
问:
我正在尝试根据字段解析每个。为此,我决定将解析委托给使用工厂创建的特定转换器。但是,如何调用转换器?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.CreateReader()
为要反序列化的特定令牌创建读取器。JToken
因此,您的应如下所示: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);
}
}
笔记:
首次创建时,它位于第一个 JSON 标记之前。您必须至少调用一次
Read()
才能将其提升到第一个令牌。此外,请务必始终检查 的返回。如果文件被截断,它可能会意外返回。JsonReader
JsonReader.Read()
false
如有必要,上面的扩展方法会前进到第一个标记,并且还会跳过注释并引发截断文件的异常。
MoveToContentAndAssert()
转换器使用属性来指示要反序列化的具体类型。序列化或反序列化值时,请务必使用固定区域性将值转换为小写。如果使用本地化区域性,则 JSON 可能无法跨区域设置反序列化。(例如,参见为什么“i”被替换为“ı”。
"type"
方法应检查无效和/或意外的 JSON 内容,并妥善地处理它,以引发异常或使读取器在退出时正确定位。例如,您需要决定如果数组值为 null 或数组项为 null,它应该如何表现。
ReadJson()
对于传递引用的声明类型的参数,这里是 .在多态情况下,转换器可能会返回更派生的类型。
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,因此无法对上述代码进行全面测试。
在此处使用简化类型的演示。
评论