如何实现基于属性名称的自定义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
相关产品推荐
相关产品推荐

