如何从其他JsonConverter内部调用JsonConverter?

How to invoke JsonConverter from inside other JsonConverter?

提问人:theateist 提问时间:11/15/2023 更新时间:11/17/2023 访问量:50

问:

我正在尝试根据字段解析每个。为此,我决定将解析委托给使用工厂创建的特定转换器。但是,如何调用转换器?stepcommandCustomElementJsonConverter::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; }      
}
C# json.net JSON 反序列化

评论

0赞 dbc 11/15/2023
我的代码有很多编译错误,请参阅 dotnetfiddle.net/FwHOpd。你有没有机会编辑你的问题来分享一个最小的可重复的例子

答:

1赞 dbc 11/17/2023 #1

您可以直接调用基类方法 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() 才能将其提升到第一个令牌。此外,请务必始终检查 的返回。如果文件被截断,它可能会意外返回。JsonReaderJsonReader.Read()false

    如有必要,上面的扩展方法会前进到第一个标记,并且还会跳过注释并引发截断文件的异常。MoveToContentAndAssert()

  • 转换器使用属性来指示要反序列化的具体类型。序列化或反序列化值时,请务必使用固定区域性将值转换为小写。如果使用本地化区域性,则 JSON 可能无法跨区域设置反序列化。(例如,参见为什么“i”被替换为“ı”。"type"

  • 方法应检查无效和/或意外的 JSON 内容,并妥善地处理它,以引发异常或使读取器在退出时正确定位。例如,您需要决定如果数组值为 null 或数组项为 null,它应该如何表现。ReadJson()

  • 对于传递引用的声明类型的参数,这里是 .在多态情况下,转换器可能会返回更派生的类型。Type objectTypeReadJson()ElementData

  • 您的转换器不支持填充现有的 .如果需要,请按如下方式进行修改:StepReadJson()

    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,因此无法对上述代码进行全面测试。

在此处使用简化类型的演示。