如何获取匹配模型序列化命名策略的JSON属性名称
基于内置契约解析器获取最终JSON属性名的实现方案
核心思路
完全不需要绕路实现冗余逻辑,Newtonsoft.Json 本身就内置了完整的属性名映射解析能力,你只需要复用项目实际使用的序列化器配置对应的契约解析器实例,就能零偏差拿到序列化后的最终属性名,整个过程没有任何实际序列化/反序列化的开销。
具体实现
首先需要明确:所有序列化规则的核心都封装在IContractResolver实例中,不管是大小写转换规则、[JsonProperty]特性的优先级、自定义的属性筛选逻辑,都会在契约解析阶段被统一处理。你不需要自己手写大小写转换、特性读取的逻辑,只需要直接读取解析结果即可。
步骤1:准备可复用的契约解析器实例
直接复用你项目全局配置中使用的CamelCasePropertyNamesContractResolver实例,不要在方法内新建同类型实例——否则一旦你后续对解析器做了自定义修改,新实例的规则就会和实际运行的序列化规则脱节。
// 项目全局实际使用的序列化配置 var globalSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
步骤2:实现JsonName<T>方法
public string JsonName<T>(string cSharpPropertyName, IContractResolver resolver) { // 解析目标类型的JSON对象合约,该过程自带全局缓存,重复查询无性能损耗 var objectContract = resolver.ResolveContract(typeof(T)) as JsonObjectContract; if (objectContract is null) { throw new InvalidOperationException($"类型 {typeof(T)} 不属于可序列化的对象类型"); } // 基于C#属性的原始名称匹配对应的合约条目 var targetProperty = objectContract.Properties.FirstOrDefault(prop => prop.UnderlyingName == cSharpPropertyName); if (targetProperty is null) { throw new KeyNotFoundException($"类型 {typeof(T)} 中不存在名为 {cSharpPropertyName} 的可序列化属性"); } // 直接返回合约解析出的最终JSON属性名,完全匹配实际序列化逻辑 return targetProperty.PropertyName; }
实际运行效果
传入项目全局的解析器实例后,返回结果完全符合预期:
var resolver = (CamelCasePropertyNamesContractResolver)globalSerializerSettings.ContractResolver; JsonName<Model>(nameof(Model.Id), resolver); // 结果:"id" —— 无显式JsonProperty特性的属性,自动应用CamelCase规则 JsonName<Model>(nameof(Model.Text), resolver); // 结果:"content_text" —— 显式标注的JsonProperty名称优先级高于大小写转换规则
方案优势
和你之前考虑的“包装JToken后反序列化再读取属性名”的方案相比:
- 性能提升明显:整个过程仅读取被Newtonsoft.Json全局缓存的类型元数据,没有中间对象分配、反序列化流程执行的开销,高频调用场景下几乎没有额外性能损耗。
- 规则100%对齐:因为直接复用了实际运行的契约解析器实例,所有内置规则都会被自动遵循——包括属性忽略规则、特性优先级、自定义的名称转换逻辑,完全不会出现自己手写转换逻辑和实际序列化结果不匹配的问题。
- 适配自定义JsonConverter的场景:你甚至不需要循环调用单个属性的查询方法,直接遍历
objectContract.Properties就能拿到全量的“C#属性名-最终JSON名”映射字典,在自定义转换器初始化阶段把这个映射表缓存下来,处理JToken时直接做精准匹配即可,完全不需要做不区分大小写的模糊查找。
注意事项
- 匹配C#属性时一定要用
UnderlyingName字段:这个字段存的是属性在C#代码中的原始元数据名,和已经转换完成的最终JSON名(即PropertyName字段)是独立的,匹配的时候如果搞混就会查不到对应的属性。 - 不要在
JsonName方法内部新建CamelCasePropertyNamesContractResolver实例:一定要传入项目全局正在运行的那个解析器实例,不然如果你后续给解析器加了自定义规则(比如给属性名加前缀、修改大小写转换逻辑),新实例的规则就会和实际序列化的逻辑脱节。 - 如果你是在写自定义JsonConverter,建议在初始化阶段就把某个类型的全量属性映射表查出来做缓存:比如第一次处理某个类型的时候,遍历
JsonObjectContract.Properties生成一个“最终JSON名-C#属性元数据”的字典,之后每个JToken节点解析的时候直接做O(1)的查找,性能比逐个JToken做不区分大小写的匹配高很多。
内容的提问来源于stack exchange,提问作者Jacek
相关产品推荐
相关产品推荐

