You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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的细分解析。但存在两个明显的可优化点:

  1. 工厂类需手动维护:新增ElementType时必须修改ElementJsonConverterFactory的switch分支,违反开闭原则。
  2. 转换器职责臃肿:单个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 12:27:32