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" } ] }
待解决问题
- 如何基于自定义映射规则(例如识别到JSON中的docDate字段则映射到GenericDocument的Date属性)实现任意JSON字符串到指定对象的反序列化?
- 如何在自定义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
相关产品推荐
相关产品推荐

