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

C# 中基于自定义映射规则将JSON反序列化为指定对象的实现方法

参考内容

业务实体

public class GenericDocument
{
    public DateTime Date { get; set; }
    public string SupplierName { get; set; }
    public string SupplierAddress { get; set; }
    public string SupplierIban { get; set; }
}

JSON示例

{ 
   "invoiceDate" : "2021-10-12",
   "vendorName": "just a test",
   "vendorAddres": "just a vendor address",
   "vendorVat": "just a vendor vat"
}

{ 
   "docDate" : "2021-10-12",
   "sellerName": "just a test",
   "sellerAddres": "just a vendor address",
   "sellerVat": "just a vendor vat"
}

API代码

public class DocumentsController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> CreateDocument([FromBody] DocumentCreateRequest document)
    {
        var res = JsonConvert.DeserializeObject<List<GenericDocument>>(Convert.ToString(document.Documents),
            new JsonSerializerSettings
                { ContractResolver = new DocumentSerializeContractResolver(document.SerializeDic) });

        return Ok();
    }
}

public class DocumentCreateRequest
{
    public IDictionary<string, string> SerializeDic { get; set; }
    public dynamic Documents { get; set; }
}

public class DocumentSerializeContractResolver : DefaultContractResolver
{
    public IDictionary<string, string> MapDictionary { get; set; }

    public DocumentSerializeContractResolver(IDictionary<string, string> defaultStringValue)
    {
        this.MapDictionary = defaultStringValue;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        // 给所有字符串属性设置默认值
        foreach (var property in properties.Where(p => p.PropertyType == typeof(string)))
        {
            property.DefaultValue = MapDictionary[property.PropertyName];
            property.DefaultValueHandling = DefaultValueHandling.Populate;

        }
        return properties;
    }
}

public class StringValueProvider : IValueProvider
{
    private PropertyInfo _targetProperty;
    private string _substitutionValue;

    public StringValueProvider(PropertyInfo targetProperty, string substitutionValue)
    {
        _targetProperty = targetProperty;
        _substitutionValue = substitutionValue;
    }

    // Json.Net反序列化时调用SetValue
    // value参数是从JSON读取的原始值,target是要赋值的目标对象
    public void SetValue(object target, object value)
    {
        _targetProperty.SetValue(target, value);
    }

    // Json.Net序列化时调用GetValue
    // target参数是要读取值的源对象,返回值会被写入JSON
    public object GetValue(object target)
    {
        object value = _targetProperty.GetValue(target);
        return value == null ? _substitutionValue : value;
    }
}

请求对象示例

{
   "DeserializeRules":"some rules here",
   "DocumentJson":[
      {
         "docDate":"2021-10-12",
         "sellerName":"just a test",
         "sellerAddres":"just a vendor address",
         "sellerVat":"just a vendor vat"
      },
      {
         "docDate":"2021-10-12",
         "sellerName":"just a test",
         "sellerAddres":"just a vendor address",
         "sellerVat":"just a vendor vat"
      },
      {
         "docDate":"2021-10-12",
         "sellerName":"just a test",
         "sellerAddres":"just a vendor address",
         "sellerVat":"just a vendor vat"
      }
   ]
}

待解决问题

  1. 如何基于自定义映射规则(例如识别到JSON中的docDate字段则映射到GenericDocument的Date属性)实现任意JSON字符串到指定对象的反序列化?
  2. 如何在自定义ContractResolver中访问document.Documents的内容,以提取对应值完成目标模型的填充?

解决方案

问题1:自定义字段映射反序列化实现

通过修改DocumentSerializeContractResolver的属性映射逻辑即可实现,映射规则通过请求传入的SerializeDic传递,字典结构约定为[目标类属性名: 源JSON字段名],例如{"Date":"docDate", "SupplierName":"sellerName"}。
修改后的ContractResolver代码如下:

public class DocumentSerializeContractResolver : DefaultContractResolver
{
    public IDictionary<string, string> MapDictionary { get; set; }

    public DocumentSerializeContractResolver(IDictionary<string, string> mapDictionary)
    {
        MapDictionary = mapDictionary ?? new Dictionary<string, string>();
        // 开启忽略大小写匹配,提高兼容性
        NamingStrategy = new CamelCaseNamingStrategy();
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        foreach (var property in properties)
        {
            // 匹配映射规则,替换反序列化时读取的JSON字段名
            if (MapDictionary.TryGetValue(property.PropertyName, out var sourceFieldName))
            {
                property.PropertyName = sourceFieldName;
            }
            // 原有字符串默认值填充逻辑保留
            if (property.PropertyType == typeof(string))
            {
                property.DefaultValue = MapDictionary.TryGetValue(property.PropertyName, out var defaultVal) ? defaultVal : string.Empty;
                property.DefaultValueHandling = DefaultValueHandling.Populate;
            }
        }
        return properties;
    }
}

无需修改接口调用逻辑,只需在请求的SerializeDic中传入对应映射关系即可完成字段匹配。


问题2:在反序列化逻辑中访问原JSON内容

DefaultContractResolver的定位是配置序列化/反序列化的元数据规则,原生设计不直接关联待处理的JSON内容。如果需要读取原JSON内容做自定义填充,有两种可行方案:

方案1:使用自定义JsonConverter(推荐)

自定义JsonConverter的ReadJson方法可以直接获取JSON读取流,能够读取任意字段内容,适合复杂自定义填充场景:

public class GenericDocumentConverter : JsonConverter<GenericDocument>
{
    private readonly IDictionary<string, string> _mapDictionary;
    public GenericDocumentConverter(IDictionary<string, string> mapDictionary)
    {
        _mapDictionary = mapDictionary;
    }

    public override GenericDocument ReadJson(JsonReader reader, Type objectType, GenericDocument existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var jObj = JObject.Load(reader);
        var doc = new GenericDocument();
        // 此处可访问jObj的所有内容,自定义填充逻辑
        if (_mapDictionary.TryGetValue(nameof(GenericDocument.Date), out var dateField) && jObj.TryGetValue(dateField, out var dateVal))
        {
            doc.Date = dateVal.ToObject<DateTime>();
        }
        // 其他属性填充逻辑同理
        return doc;
    }

    public override void WriteJson(JsonWriter writer, GenericDocument value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

接口调用时修改序列化配置即可:

var res = JsonConvert.DeserializeObject<List<GenericDocument>>(
    Convert.ToString(document.Documents),
    new JsonSerializerSettings
    {
        Converters = new List<JsonConverter> { new GenericDocumentConverter(document.SerializeDic) }
    });

方案2:提前解析JSON传入ContractResolver

如果必须使用ContractResolver实现,可以先把待反序列化内容解析为JArray,作为参数传入ContractResolver构造函数,即可在所有方法中访问原JSON内容:

public class DocumentSerializeContractResolver : DefaultContractResolver
{
    public IDictionary<string, string> MapDictionary { get; set; }
    public JArray SourceDocuments { get; set; }

    public DocumentSerializeContractResolver(IDictionary<string, string> mapDictionary, JArray sourceDocuments)
    {
        MapDictionary = mapDictionary;
        SourceDocuments = sourceDocuments;
    }
    // 后续逻辑中可直接访问SourceDocuments获取原JSON内容
}

接口调用时提前解析:

var docJArray = JArray.Parse(Convert.ToString(document.Documents));
var res = JsonConvert.DeserializeObject<List<GenericDocument>>(
    docJArray.ToString(),
    new JsonSerializerSettings
    {
        ContractResolver = new DocumentSerializeContractResolver(document.SerializeDic, docJArray)
    });

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 23:27:01