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

如何动态生成JsonPropertyName?基于System.Text.Json的多字段映射需求

问题描述

我有如下Model类:

using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

public class FormField
{
    [Required]
    [JsonPropertyName("STD_USERTYPEID")]
    public string UserTypeId { get; set; }

    [Required]
    [JsonPropertyName("STD_OFFICETYPEID")]
    public string OfficeTypeId { get; set; }
}

存在多种场景:STD_OFFICETYPEID可能以LegacyOFFICETYPEID或OfficeID的形式传入。我正在使用System.Text.Json NuGet包,能否动态生成JsonPropertyName?

注:示例已简化,生产环境中会有20-25个具体属性,每个属性可能对应5-10个不同的JsonPropertyName。


解决方案

System.Text.Json原生的JsonPropertyNameAttribute只支持单个别名,没法直接动态生成多个名称。针对你这种多属性多别名的场景,推荐两种高效实现方式:

1. 自定义JsonConverter(兼容性强)

给FormField写一个自定义转换器,反序列化时遍历属性的所有可能别名,匹配到就赋值。这种方式适配所有.NET版本,性能可控,适合属性和别名较多的场景。

示例代码:

using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

public class FormFieldConverter : JsonConverter<FormField>
{
    // 维护属性与所有别名的映射,生产环境可抽成配置文件
    private static readonly Dictionary<string, List<string>> _propertyAliasMap = new()
    {
        { nameof(FormField.UserTypeId), new List<string> { "STD_USERTYPEID" } },
        { nameof(FormField.OfficeTypeId), new List<string> { "STD_OFFICETYPEID", "LegacyOFFICETYPEID", "OfficeID" } }
        // 补充其他20+属性的别名映射
    };

    public override FormField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var formField = new FormField();
        using var jsonDoc = JsonDocument.ParseValue(ref reader);
        var rootElement = jsonDoc.RootElement;

        foreach (var (propName, aliases) in _propertyAliasMap)
        {
            var property = typeof(FormField).GetProperty(propName);
            if (property == null) continue;

            foreach (var alias in aliases)
            {
                if (rootElement.TryGetProperty(alias, out var valueElement))
                {
                    property.SetValue(formField, valueElement.GetString());
                    break; // 匹配到第一个有效别名就停止
                }
            }
        }

        // 执行数据验证,保留Required特性的作用
        var validationContext = new ValidationContext(formField);
        Validator.ValidateObject(formField, validationContext, validateAllProperties: true);

        return formField;
    }

    public override void Write(Utf8JsonWriter writer, FormField value, JsonSerializerOptions options)
    {
        // 序列化时使用默认的JsonPropertyName,也可自定义输出字段名
        JsonSerializer.Serialize(writer, value, options);
    }
}

使用时注册转换器即可:

var options = new JsonSerializerOptions();
options.Converters.Add(new FormFieldConverter());

// 测试不同别名的JSON
var json1 = "{\"STD_OFFICETYPEID\": \"1\", \"STD_USERTYPEID\": \"admin\"}";
var json2 = "{\"LegacyOFFICETYPEID\": \"2\", \"STD_USERTYPEID\": \"user\"}";
var json3 = "{\"OfficeID\": \"3\", \"STD_USERTYPEID\": \"guest\"}";

var field1 = JsonSerializer.Deserialize<FormField>(json1, options);
var field2 = JsonSerializer.Deserialize<FormField>(json2, options);
var field3 = JsonSerializer.Deserialize<FormField>(json3, options);

2. 修改JsonTypeInfo元数据(.NET 6+)

如果用的是.NET 6及以上版本,直接通过修改JsonTypeInfo添加多别名,无需写完整转换器,代码更简洁。

示例代码:

using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

// 定义属性别名映射
var propertyAliases = new Dictionary<string, List<string>>
{
    { nameof(FormField.OfficeTypeId), new List<string> { "LegacyOFFICETYPEID", "OfficeID" } }
    // 补充其他属性的别名
};

var options = new JsonSerializerOptions();
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
    Modifiers =
    {
        typeInfo =>
        {
            if (typeInfo.Type != typeof(FormField)) return;

            foreach (var property in typeInfo.Properties)
            {
                if (propertyAliases.TryGetValue(property.Name, out var aliases))
                {
                    // 为属性添加额外的匹配别名
                    foreach (var alias in aliases)
                    {
                        property.PropertyNames.Add(alias);
                    }
                }
            }
        }
    }
};

// 测试反序列化
var json = "{\"OfficeID\": \"4\", \"STD_USERTYPEID\": \"operator\"}";
var formField = JsonSerializer.Deserialize<FormField>(json, options);

// 执行验证
var validationContext = new ValidationContext(formField);
Validator.ValidateObject(formField, validationContext, validateAllProperties: true);

注意事项

  • 两种方式都能保留Required特性的验证逻辑,记得在反序列化后执行验证步骤。
  • 生产环境建议把别名映射抽成外部配置文件(如JSON、XML),避免硬编码,方便后续维护和修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 21:20:31