.NET中如何序列化忽略空集合,反序列化自动设为空集合?
序列化忽略空集合、反序列化补全空集合的实现方案
需求背景
存在一批支持JSON序列化/反序列化的POCO,其中大量属性为集合类型。需要实现两个核心逻辑:
- 序列化时:仅保留非空集合对应的属性(节省空间、简化人工查看的内容);
- 反序列化时:若JSON中缺失集合类型属性,自动将其设为空集合而非null。
现有JsonIgnoreCondition枚举仅支持忽略null或默认值,无法直接处理空集合场景。
示例场景
原POCO定义
public class Data { public DocData MainDoc { get; } public IList<DocData> SubDocs { get; } public Data(DocData mainDoc, IList<DocData> subDocs) { MainDoc = mainDoc; SubDocs = subDocs; } } public class DocData { public string Id { get; set; } = string.Empty; }
当前问题
使用DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull配置序列化时,若SubDocs为空集合,生成的JSON仍会包含该属性:
{ "MainDoc": { "Id": "Main1" }, "SubDocs": [] }
期望结果为仅保留非空属性:
{ "MainDoc": { "Id": "Main1" } }
此前尝试的自定义转换器会将空集合转为null,但最终JSON仍会输出"SubDocs": null,无法达到忽略属性的目的。
解决方案
通过自定义契约解析器控制序列化逻辑 + 构造函数处理反序列化补全,完全满足需求。
1. 修改POCO构造函数(处理反序列化补全)
添加带[JsonConstructor]标记的构造函数,当JSON缺失集合属性时,将null参数转为空集合:
public class Data { public DocData MainDoc { get; } public IList<DocData> SubDocs { get; } // 供反序列化使用:自动将null集合转为空集合 [JsonConstructor] public Data(DocData mainDoc, IList<DocData>? subDocs) { MainDoc = mainDoc; SubDocs = subDocs ?? List<DocData>.Empty; } // 正常业务构造函数 public Data(DocData mainDoc, IList<DocData> subDocs) { MainDoc = mainDoc; SubDocs = subDocs; } }
2. 自定义契约解析器(控制序列化忽略空集合)
继承DefaultJsonTypeInfoResolver,重写GetTypeInfo方法,为集合类型属性设置序列化条件:
using System.Collections; using System.Text.Json; using System.Text.Json.Serialization; public class IgnoreEmptyCollectionsResolver : DefaultJsonTypeInfoResolver { public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { JsonTypeInfo typeInfo = base.GetTypeInfo(type, options); foreach (JsonPropertyInfo property in typeInfo.Properties) { // 判断是否为集合类型(排除字符串,因为string实现了IEnumerable<char>) if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.PropertyType != typeof(string)) { // 设置序列化条件:仅当集合非空时才序列化该属性 property.ShouldSerialize = (obj, value) => { if (value is IEnumerable enumerable) { return enumerable.GetEnumerator().MoveNext(); } return value != null; }; } } return typeInfo; } }
3. 使用示例
配置序列化选项并验证效果:
// 初始化序列化选项 var jsonOptions = new JsonSerializerOptions { TypeInfoResolver = new IgnoreEmptyCollectionsResolver(), WriteIndented = true // 格式化输出,便于查看 }; // 测试序列化:SubDocs为空集合 var emptySubDocsData = new Data(new DocData { Id = "Main1" }, List<DocData>.Empty); string serializedJson = JsonSerializer.Serialize(emptySubDocsData, jsonOptions); // 输出结果:仅包含MainDoc属性,无SubDocs Console.WriteLine(serializedJson); // 测试反序列化:JSON中无SubDocs属性 string jsonWithoutSubDocs = @"{""MainDoc"": {""Id"": ""Main2""}}"; var deserializedData = JsonSerializer.Deserialize<Data>(jsonWithoutSubDocs, jsonOptions); // 验证:SubDocs为空集合而非null Console.WriteLine(deserializedData?.SubDocs.Count); // 输出0
方案说明
- 序列化逻辑:通过契约解析器的
ShouldSerialize回调,直接跳过空集合属性的序列化,从根源上避免空集合出现在JSON中,比自定义转换器更直接。 - 反序列化逻辑:利用
[JsonConstructor]标记的构造函数,在反序列化过程中自动将缺失属性对应的null参数转为空集合,确保POCO的集合属性始终非null。
内容的提问来源于stack exchange,提问作者Kjara
相关产品推荐
相关产品推荐

