如何实现基于属性名的自定义JsonConverter?

hjzp0vay  于 2023-05-19  发布在  其他
关注(0)|答案(1)|浏览(204)

我有一个转换器准备我的双打这样。

class DoubleConverter : JsonConverter<double>
{
  public override double Read(...) { ... }
  public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
  {
    writer.WriteStringValue(value + " double from...?");
  }
}

我希望能够指定它转换自的属性的名称。(实际上,这是关于控制数字的数量,但也可能需要其他东西)。
没有真正找到任何好的方法(只是遵循docs)或信息在overriden方法。我有两个想法,没有一个对我有吸引力。
1.使用属性为每个特性分别分配DTO类中的自定义转换器。我甚至不确定它是否会工作,即使是这样,它的waaaay乏味。
1.在JsonConverter<MyDto>上实现转换器。这将意味着很多不必要的自定义实现。还有一个乏味的部分
这些是唯一的选择吗?还是有办法绕过它?

wmomyfyw

wmomyfyw1#

不可能从Utf8JsonWriter获取当前路径或属性名,原因很简单,它不跟踪路径。它唯一跟踪的是一个BitStack,它指示当前容器是数组还是对象。[1]第一章
相反,在**.NET 7及更高版本**中,您可以使用typeInfo modifier来自定义类型的contract,以将转换器添加到每个双值成员,其构造函数被传递其MemberInfo
首先定义以下修饰符:

public static partial class JsonExtensions
{
    public static Action<JsonTypeInfo> AddMemberAwareDoubleConverters { get; } = static typeInfo => 
    {
        if (typeInfo.Kind != JsonTypeInfoKind.Object)
            return;
        foreach (var property in typeInfo.Properties)
            if (property.CustomConverter == null && property.GetMemberInfo() is {} memberInfo)
                if (property.PropertyType == typeof(double)) 
                    property.CustomConverter = new MemberAwareDoubleConverter(memberInfo);
                else if (property.PropertyType == typeof(double?))
                    property.CustomConverter = new MemberAwareNullableDoubleConverter(memberInfo);
    };

    public static MemberInfo? GetMemberInfo(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo);
}

class MemberAwareDoubleConverter : JsonConverter<double>
{
    MemberInfo MemberInfo { get; }
    
    public MemberAwareDoubleConverter(MemberInfo memberInfo) => this.MemberInfo = memberInfo ?? throw new ArgumentNullException(nameof(memberInfo));

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) =>
        writer.WriteRawValue($"{JsonSerializer.Serialize(value)} /* double from {MemberInfo.Name} */", true);
    
    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 
        // TODO: Handle "NaN", "Infinity", "-Infinity"
        reader.GetDouble();
}

class MemberAwareNullableDoubleConverter : JsonConverter<double?>
{
    public override bool HandleNull => true;
    MemberInfo MemberInfo { get; }
    
    public MemberAwareNullableDoubleConverter(MemberInfo memberInfo) => this.MemberInfo = memberInfo ?? throw new ArgumentNullException(nameof(memberInfo));

    public override void Write(Utf8JsonWriter writer, double? value, JsonSerializerOptions options) =>
        writer.WriteRawValue($"{JsonSerializer.Serialize(value)} /* double? from {MemberInfo.Name} */", true);
    
    public override double? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 
        // TODO: Handle "NaN", "Infinity", "-Infinity"
        reader.TokenType switch
        {
            JsonTokenType.Number => reader.GetDouble(),
            JsonTokenType.Null => null,
            _ => throw new JsonException(),
        };
}

现在,如果你的模型看起来像这样,例如:

public class Root
{
    public double RootDoubleValue { get; set; }
    public double? RootNullableValue { get; set; }
    public List<Item> Items { get; set; } = new ();
}

public record Item(double ItemDoubleValue, double? ItemNullableValue);

然后使用修饰符序列化,如下所示:

var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers = { JsonExtensions.AddMemberAwareDoubleConverters },
    },
    // Others as required
    WriteIndented = true,
    ReadCommentHandling = JsonCommentHandling.Skip,
};
var json = JsonSerializer.Serialize(root, options);

您将看到生成的JSON包含显示属性名称的注解:

{
  "RootDoubleValue": 101.01 /* double from RootDoubleValue */,
  "RootNullableValue": 202.02 /* double? from RootNullableValue */,
  "Items": [
    {
      "ItemDoubleValue": 2101.01 /* double from ItemDoubleValue */,
      "ItemNullableValue": null /* double? from ItemNullableValue */
    }
  ]
}

注意事项:

  • 老实说,我不推荐这种方法。基于属性名定制转换器的逻辑有点像God object code smell,因为转换器需要知道应用程序中所有双值属性的所有格式规则。

相反,考虑向模型中添加自定义属性,指示所需的格式,然后在typeInfo修饰符中,将该信息传递给适当的转换器。
或者,如果您只需要控制位数,请应用适当的RoundingJsonConverterthis answerConvert JsonConverter to System.Text.Json to support multiple primitive types and nullable
演示小提琴here
[1]您也无法从Utf8JsonReader获取路径。详情请参见 How do I get the property path from Utf8JsonReader?

相关问题