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

问题描述 投票:0回答:1

我试图根据

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; }      
}
c# json.net json-deserialization
1个回答
1
投票

直接调用基类方法

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

使用简化类型的演示此处

© www.soinside.com 2019 - 2024. All rights reserved.