C#中如何序列化任意类型对象时仅保留嵌套列表的首尾元素到JSON
解决方案:自定义ContractResolver处理List序列化
嘿,你要的这个需求用Newtonsoft.Json的自定义ContractResolver就能完美搞定,它能全局拦截所有List类型的属性,自动只保留首尾元素,不用在每个类上重复配置,非常适合你说的「任意类型对象」的场景。
实现思路
我们继承DefaultContractResolver,重写CreateProperty方法,针对所有实现了IList接口的属性(包括泛型List<T>),替换它的ValueProvider——在获取属性值时,把原List转换成只包含第一个和最后一个元素的新List(如果元素数量不足2个,就保留原集合,避免空指针或异常)。
完整代码实现
首先创建自定义的ContractResolver:
using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Collections; using System.Collections.Generic; using System.Linq; public class TrimListContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); // 检查属性类型是否是IList(兼容所有List<T>和实现IList的集合) if (typeof(IList).IsAssignableFrom(property.PropertyType)) { // 替换ValueProvider,自定义List的读取逻辑 property.ValueProvider = new TrimListValueProvider(property.ValueProvider); } return property; } // 自定义ValueProvider,处理List的裁剪逻辑 private class TrimListValueProvider : IValueProvider { private readonly IValueProvider _originalProvider; public TrimListValueProvider(IValueProvider originalProvider) { _originalProvider = originalProvider; } public object GetValue(object target) { var originalList = _originalProvider.GetValue(target) as IList; if (originalList == null || originalList.Count <= 2) { // 空列表、只有1个元素时直接返回原集合 return originalList; } // 创建同类型的新集合,只保留首尾元素 var trimmedList = (IList)System.Activator.CreateInstance(originalList.GetType()); trimmedList.Add(originalList[0]); trimmedList.Add(originalList[originalList.Count - 1]); return trimmedList; } public void SetValue(object target, object value) { // 反序列化时沿用原逻辑,这里我们只关注序列化流程 _originalProvider.SetValue(target, value); } } }
然后修改你的DoTheThing方法,启用这个自定义ContractResolver:
public static string DoTheThing(object dynamicObject) { var settings = new JsonSerializerSettings { ContractResolver = new TrimListContractResolver(), // 可选:格式化输出方便调试,生产环境可移除 Formatting = Formatting.Indented }; return JsonConvert.SerializeObject(dynamicObject, settings); }
测试验证
运行你的Main方法:
public static void Main() { var list = new List<Product>(){ new Product("product1",100), new Product("product2",100), new Product("product3",100), new Product("product4",100) }; var result = new ProductResult(list,0); string jsonObj = DoTheThing(result); Console.WriteLine(jsonObj); }
输出结果(格式化后):
{ "Code": 0, "Products": [ { "Name": "product1", "Price": 100 }, { "Name": "product4", "Price": 100 } ] }
完全符合你的需求!而且这个方案支持任意包含List属性的对象,不管是ProductResult还是其他类,只要属性是List/IList类型,都会自动裁剪。
边界情况处理
- 如果List是空的:序列化后还是空数组
- 如果List只有1个元素:序列化后只保留这一个元素
- 如果List正好2个元素:原样保留
备选方案:自定义JsonConverter
如果你只想针对特定的List属性(而非全局所有List)处理,可以用自定义JsonConverter,比如给ProductResult的Products属性加[JsonConverter(typeof(TrimListConverter<Product>))]特性,然后实现转换器:
public class TrimListConverter<T> : JsonConverter<List<T>> { public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) { var trimmed = value.Count <= 2 ? value : new List<T> { value[0], value[^1] }; serializer.Serialize(writer, trimmed); } public override List<T> ReadJson(JsonReader reader, Type objectType, List<T> existingValue, bool hasExistingValue, JsonSerializer serializer) { return serializer.Deserialize<List<T>>(reader); } }
但这种方式需要逐个属性配置,不如ContractResolver灵活,适合局部场景。
内容的提问来源于stack exchange,提问作者B.L.Coskey
相关产品推荐
相关产品推荐

