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

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

你不需要局限于那两种繁琐的方案,有两种更简便的替代方法,既能获取属性名称,又能灵活控制小数位数等需求:

方案1:利用Utf8JsonWriter.CurrentPath直接获取属性路径(快速实现)

如果你的序列化场景以平级DTO为主,没有复杂嵌套或数组,可以直接通过writer.CurrentPath获取当前属性的完整路径,从中提取属性名称:

class DoubleConverter : JsonConverter<double>
{
    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.GetDouble();
    }

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        // 从路径中提取属性名(路径格式为 "$.PropertyName")
        string propertyName = writer.CurrentPath.TrimStart('$', '.');
        // 根据属性名自定义逻辑,比如不同属性用不同小数位数
        int decimalPlaces = propertyName switch
        {
            "Price" => 2,
            "PrecisionValue" => 4,
            _ => 6
        };
        string formattedValue = value.ToString($"F{decimalPlaces}");
        writer.WriteStringValue($"{formattedValue} (来自属性: {propertyName})");
    }
}

缺点:如果是嵌套对象(如$.User.Address.Latitude)或数组元素(如$.Items[0].Price),需要额外解析路径才能拿到目标属性名,灵活性稍差。

方案2:自定义特性 + JsonTypeInfoResolver(推荐,灵活可控)

这种方法通过自定义特性标记属性的配置规则,再通过类型信息解析器自动为每个属性绑定带参数的转换器,完美解决属性名称获取和个性化配置需求:

步骤1:定义自定义特性

用来标记属性的格式化规则:

[AttributeUsage(AttributeTargets.Property)]
public class DoubleFormatAttribute : Attribute
{
    // 小数位数配置
    public int DecimalPlaces { get; }

    public DoubleFormatAttribute(int decimalPlaces)
    {
        DecimalPlaces = decimalPlaces;
    }
}

步骤2:实现带参数的转换器

让转换器支持接收属性名称和配置参数:

public class DoubleFormatConverter : JsonConverter<double>
{
    private readonly int _decimalPlaces;
    private readonly string _propertyName;

    // 全局注册时的无参构造
    public DoubleFormatConverter() : this(6, string.Empty) { }

    // 针对特定属性的带参构造
    public DoubleFormatConverter(int decimalPlaces, string propertyName)
    {
        _decimalPlaces = decimalPlaces;
        _propertyName = propertyName;
    }

    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.GetDouble();
    }

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        // 根据配置格式化数值
        string formattedValue = value.ToString($"F{_decimalPlaces}");
        // 使用属性名执行自定义逻辑
        writer.WriteStringValue($"{formattedValue} (来自属性: {_propertyName})");
    }
}

步骤3:实现自定义TypeInfoResolver

扫描DTO属性上的自定义特性,自动为属性绑定对应的转换器:

public class DoubleFormatTypeInfoResolver : DefaultJsonTypeInfoResolver
{
    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
    {
        JsonTypeInfo typeInfo = base.GetTypeInfo(type, options);

        foreach (JsonPropertyInfo property in typeInfo.Properties)
        {
            // 只处理double类型的属性
            if (property.PropertyType != typeof(double) && property.PropertyType != typeof(double?))
                continue;

            // 获取属性上的DoubleFormat特性
            var formatAttr = property.AttributeProvider
                .GetCustomAttributes(typeof(DoubleFormatAttribute), false)
                .Cast<DoubleFormatAttribute>()
                .FirstOrDefault();

            if (formatAttr != null)
            {
                // 为该属性创建带参数的转换器
                property.CustomConverter = new DoubleFormatConverter(formatAttr.DecimalPlaces, property.Name);
            }
        }

        return typeInfo;
    }
}

步骤4:配置并使用

在序列化时指定自定义的TypeInfoResolver:

// 示例DTO
public class ProductDto
{
    [DoubleFormat(2)]
    public double Price { get; set; }

    [DoubleFormat(4)]
    public double Weight { get; set; }

    // 未标记的属性使用默认6位小数
    public double Discount { get; set; }
}

// 配置序列化选项
var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DoubleFormatTypeInfoResolver(),
    WriteIndented = true
};

// 序列化测试
var product = new ProductDto { Price = 199.995, Weight = 0.45678, Discount = 0.1234567 };
string json = JsonSerializer.Serialize(product, options);

序列化结果:

{
  "Price": "200.00 (来自属性: Price)",
  "Weight": "0.4568 (来自属性: Weight)",
  "Discount": "0.123457 (来自属性: )"
}

这种方法的优势是:无需在每个属性手动指定转换器,也不用为每个DTO编写全局转换器,只需通过特性标记即可实现个性化配置,扩展性极强(可在特性中添加更多配置项,如格式化字符串、是否启用科学计数法等)。

内容的提问来源于stack exchange,提问作者Konrad Viltersten

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 15:17:54