如何在JsonConverter内部调用其他转换器?多类型JSON解析场景
我希望基于command字段解析每个step,因此决定在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; } }
一、如何调用转换器
你需要将JToken转换为JsonReader,再调用转换器的ReadJson方法。修改CustomElementJsonConverter::ReadJson中的循环逻辑即可:
foreach (var jElement in jElements) { var elementType = jElement["type"].Value<string>().ToLower(); var converter = _converters[elementType]; // 将JToken转为可被转换器读取的JsonReader using var elementReader = jElement.CreateReader(); // 调用转换器解析,并强制转换为ElementData类型 var element = converter.ReadJson(elementReader, typeof(ElementData), null, false, serializer); elements.Add((ElementData)element); }
注意:用using包裹elementReader确保资源释放,非泛型JsonConverter返回object类型,需强制转换为ElementData。
二、现有实现思路的合理性分析
你的思路基本可行,核心是通过工厂模式按type字段分发到对应转换器,再由转换器处理command的细分解析。但存在两个明显的可优化点:
- 工厂类需手动维护:新增
ElementType时必须修改ElementJsonConverterFactory的switch分支,违反开闭原则。 - 转换器职责臃肿:单个
ElementTypeXJsonConverter需要处理同一type下的所有command解析,逻辑会随命令增多变得复杂。
三、更优实现方案
方案1:双重分发+自动发现转换器
通过两层分发(type→command)实现细粒度解析,同时利用反射自动发现转换器,彻底避免修改原有代码。
步骤1:定义转换器元数据标记接口
// 标记转换器对应的ElementType和CommandType public interface IElementConverterMetadata { ElementType TargetElementType { get; } Enum TargetCommandType { get; } }
步骤2:为每个命令单独实现转换器
每个命令对应一个转换器,同时实现JsonConverter<ElementData>和元数据接口:
public class Command1Type1Converter : JsonConverter<ElementData>, IElementConverterMetadata { public ElementType TargetElementType => ElementType.Type1; public Enum TargetCommandType => CommandElementData1.CommandType.Command1_Type1; public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, JsonSerializer serializer) { return serializer.Deserialize<ElementData_For_Command1Type1>(reader); } public override void WriteJson(JsonWriter writer, ElementData value, JsonSerializer serializer) { throw new NotImplementedException(); } } // 同理实现Command2Type1Converter、Command1Type2Converter等
步骤3:重构工厂类,自动发现所有转换器
public static class ElementJsonConverterFactory { // 存储结构:ElementType → (CommandType → JsonConverter) private static readonly Dictionary<ElementType, Dictionary<Enum, JsonConverter>> _converterMap; static ElementJsonConverterFactory() { _converterMap = new Dictionary<ElementType, Dictionary<Enum, JsonConverter>>(); // 反射加载所有实现IElementConverterMetadata的转换器 var converterTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => typeof(IElementConverterMetadata).IsAssignableFrom(t) && !t.IsAbstract); foreach (var type in converterTypes) { var metadata = (IElementConverterMetadata)Activator.CreateInstance(type); if (!_converterMap.ContainsKey(metadata.TargetElementType)) { _converterMap[metadata.TargetElementType] = new Dictionary<Enum, JsonConverter>(); } _converterMap[metadata.TargetElementType][metadata.TargetCommandType] = (JsonConverter)Activator.CreateInstance(type); } } // 根据type和command获取对应转换器 public static JsonConverter GetConverter(string type, string command) { var elementType = Enum.Parse<ElementType>(type, true); // 根据ElementType匹配对应的命令枚举类型 var commandEnumType = elementType switch { ElementType.Type1 => typeof(CommandElementData1.CommandType), ElementType.Type2 => typeof(CommandElementData2.CommandType), _ => throw new NotSupportedException($"不支持的ElementType: {elementType}") }; var commandType = Enum.Parse(commandEnumType, command, true); return _converterMap[elementType][commandType]; } }
步骤4:修改CustomElementJsonConverter
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 type = jElement["type"].Value<string>(); var command = jElement["command"].Value<string>(); var converter = ElementJsonConverterFactory.GetConverter(type, command); using var elementReader = jElement.CreateReader(); var element = converter.ReadJson(elementReader, typeof(ElementData), null, false, serializer); elements.Add((ElementData)element); } return new Step { Elements = elements }; }
方案2:简化的单转换器多态解析
如果不需要极致的扩展性,可直接在ElementData上标记自定义转换器,一次性根据type和command解析到具体子类:
[JsonConverter(typeof(ElementDataConverter))] public class ElementData { [JsonProperty("type", Required = Required.Always)] public ElementType ElementType { get; set; } } public class ElementDataConverter : JsonConverter<ElementData> { public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, JsonSerializer serializer) { var jObject = JObject.Load(reader); var type = jObject["type"].Value<string>(); var command = jObject["command"].Value<string>(); var targetType = (type, command) switch { ("type1", "Command1_Type1") => typeof(ElementData_For_Command1Type1), ("type1", "Command2_Type1") => typeof(ElementData_For_Command2Type1), ("type2", "Command1_Type2") => typeof(ElementData_For_Command1Type2), _ => throw new NotSupportedException($"不支持的组合: type={type}, command={command}") }; return (ElementData)jObject.ToObject(targetType, serializer); } public override void WriteJson(JsonWriter writer, ElementData value, JsonSerializer serializer) { throw new NotImplementedException(); } }
这种方式代码更简洁,但新增命令时仍需修改switch分支。若想自动化,可在子类上添加自定义特性(如[ElementMapping(Type = "type1", Command = "Command1_Type1")]),再通过反射读取特性生成映射关系。
总结
- 调用转换器的核心是将
JToken转为JsonReader,再调用ReadJson方法。 - 现有思路可行,但扩展性不足。
- 优先推荐方案1,通过反射自动发现转换器,完全符合开闭原则,新增类型或命令时只需添加新的转换器类,无需修改原有代码。
内容的提问来源于stack exchange,提问作者theateist

