如何使用Newtonsoft.JSON将嵌套对象属性提取加前缀转为顶层属性
实现方案
推荐直接自定义 ContractResolver 即可满足需求,不需要额外搭配自定义JsonWriter。该方案可以完美兼容你已有的蛇形命名配置,完全不侵入原有业务类,也不需要创建额外包装类。
实现代码
首先实现自定义合约解析器和对应的值提供器:
using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Reflection; public class FlattenAddressContractResolver : DefaultContractResolver { public FlattenAddressContractResolver() { // 复用你原有的蛇形命名策略,保持全局命名规则统一 NamingStrategy = new SnakeCaseNamingStrategy(); } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { var properties = base.CreateProperties(type, memberSerialization); // 仅针对Order类做属性打平处理 if (type == typeof(Order)) { var resultProps = new List<JsonProperty>(); foreach (var prop in properties) { // 非Address类型的普通属性直接保留 if (prop.PropertyType != typeof(Address)) { resultProps.Add(prop); continue; } // 匹配Address属性,生成对应前缀 var prefix = prop.UnderlyingName switch { nameof(Order.FromAddress) => "from_", nameof(Order.ToAddress) => "to_", _ => string.Empty }; if (string.IsNullOrEmpty(prefix)) { resultProps.Add(prop); continue; } // 读取Address类的所有属性,生成带前缀的新属性映射 var addressProps = base.CreateProperties(typeof(Address), memberSerialization); foreach (var addressProp in addressProps) { var flattenProp = new JsonProperty { // 属性名加前缀后走蛇形命名处理,直接生成from_country这类符合要求的名称 PropertyName = NamingStrategy.GetPropertyName($"{prefix}{addressProp.UnderlyingName}", false), PropertyType = addressProp.PropertyType, ValueProvider = new NestedAddressValueProvider(prop.ValueProvider, addressProp.ValueProvider), Readable = true, Writable = true }; resultProps.Add(flattenProp); } } return resultProps; } return properties; } } // 嵌套属性值提供器,用来读写Address对象的属性值 public class NestedAddressValueProvider : IValueProvider { private readonly IValueProvider _parentPropProvider; private readonly IValueProvider _childPropProvider; public NestedAddressValueProvider(IValueProvider parentProvider, IValueProvider childProvider) { _parentPropProvider = parentProvider; _childPropProvider = childProvider; } // 序列化时读取值 public object GetValue(object target) { var addressObj = _parentPropProvider.GetValue(target); return addressObj == null ? null : _childPropProvider.GetValue(addressObj); } // 反序列化时赋值,仅序列化需求可删除该方法实现 public void SetValue(object target, object value) { var addressObj = _parentPropProvider.GetValue(target); if (addressObj == null) { addressObj = Activator.CreateInstance(typeof(Address)); _parentPropProvider.SetValue(target, addressObj); } _childPropProvider.SetValue(addressObj, value); } }
使用方法
序列化时直接指定该合约解析器即可:
var order = new Order { FromAddress = new Address { Country = "US", Zip = "10001", State = "NY", City = "New York", Street = "1st St" }, ToAddress = new Address { Country = "US", Zip = "90001", State = "CA", City = "Los Angeles", Street = "2nd St" }, Amount = 10.00m }; var serializeSettings = new JsonSerializerSettings { ContractResolver = new FlattenAddressContractResolver(), Formatting = Formatting.Indented }; // 序列化结果完全符合要求 string json = JsonConvert.SerializeObject(order, serializeSettings); // 也支持反序列化 var deserializedOrder = JsonConvert.DeserializeObject<Order>(json, serializeSettings);
注意事项
- 如果你原有项目已经存在自定义的ContractResolver,直接把上述
CreateProperties的逻辑合并到你现有的解析器中即可,不需要重复定义 - 若仅需要序列化能力,可删除
NestedAddressValueProvider中SetValue方法的实现逻辑,减少冗余代码
内容的提问来源于stack exchange,提问作者James Parsons
相关产品推荐
相关产品推荐

