System.Text.Json序列化无法移除空字符串属性,求解决方案
问题描述
我希望序列化JSON时省略空字符串属性,于是做了以下操作,但实际结果不符合预期——空字符串被转为null后,对应的属性依然以null值出现在JSON中,没有被省略。
我的操作步骤
- 创建自定义转换器
EmptyStringToNullConverter,将空字符串转换为null:
public class EmptyStringToNullConverter : JsonConverter<string> { public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetString(); } public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) { if (value == string.Empty) writer.WriteNullValue(); else writer.WriteStringValue(value); } }
- 编写
JsonSerializerClass作为System.Text.Json.JsonSerializer的包装类,添加上述转换器并设置忽略null值:
public class JsonSerializerClass { private JsonSerializerOptions serializeOptions; private JsonSerializerOptions deserializeOptions; public JsonSerializerClass() { serializeOptions = new JsonSerializerOptions(); deserializeOptions = new JsonSerializerOptions(); InitSerializer(); InitDeserializer(); } public JsonSerializerOptions SerializeOptions => serializeOptions; public string Serialize(object value) { return Serialize(value, serializeOptions); } public T Deserialize<T>(string json) { var o = System.Text.Json.JsonSerializer.Deserialize<T>(json, deserializeOptions); if (o == null) throw new ArgumentException($"Cannot deserialize JSON to type {typeof(T)}: {json}"); return o; } public static string Serialize(object value, JsonSerializerOptions options) { return System.Text.Json.JsonSerializer.Serialize(value, options); } private void InitSerializer() { serializeOptions.Converters.Add(new EmptyStringToNullConverter()); serializeOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; } private void InitDeserializer() { deserializeOptions.PropertyNameCaseInsensitive = true; deserializeOptions.Converters.Add(new JsonStringEnumConverter()); deserializeOptions.Converters.Add(new DateOnlyConverter()); deserializeOptions.Converters.Add(new DateOnlyNullableConverter()); deserializeOptions.Converters.Add(new IntConverter()); deserializeOptions.Converters.Add(new DecimalConverter()); deserializeOptions.IncludeFields = true; deserializeOptions.AllowTrailingCommas = true; deserializeOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; deserializeOptions.ReadCommentHandling = JsonCommentHandling.Skip; } }
- 测试用例:
var person = new Person { Name = "John", LastName = "Doe", MiddleName = "" }; var serializer = new JsonSerializerClass(); var json = serializer.Serialize(person); json.ToLower().Should().NotContain(nameof(person.MiddleName).ToLower());
问题原因
JsonIgnoreCondition.WhenWritingNull的判断逻辑是基于原始属性值,而非转换器处理后的结果。当属性值是空字符串(不是null)时,序列化框架会判定该属性需要被序列化,随后调用自定义转换器将其写入为null,但框架不会再对写入后的null值进行二次检查,因此最终JSON中会保留"MiddleName": null。
解决方案
不需要通过转换器将空字符串转为null,直接在序列化阶段判断并跳过空字符串属性即可。以下是两种可行方案:
方案1:通过JsonTypeInfo自定义序列化条件(.NET 7+)
修改InitSerializer方法,使用DefaultJsonTypeInfoResolver为string类型属性添加序列化判断,当值为空字符串时直接跳过:
private void InitSerializer() { // 移除原有的EmptyStringToNullConverter,不需要再转null // serializeOptions.Converters.Add(new EmptyStringToNullConverter()); serializeOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { typeInfo => { if (typeInfo.Kind == JsonTypeInfoKind.Object) { foreach (var property in typeInfo.Properties) { // 仅针对string类型属性,空字符串时不序列化 if (property.PropertyType == typeof(string)) { property.ShouldSerialize = (obj, value) => value is string str && !string.IsNullOrEmpty(str); } } } } } }; // 保留原有null值忽略配置(如果需要忽略null的string属性) serializeOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }
方案2:序列化前批量将空字符串转为null
如果项目使用的是低版本.NET(低于7),可以通过反射遍历对象属性,将所有空字符串赋值为null,再进行序列化:
// 添加一个工具方法,遍历对象属性将空字符串设为null private void SetEmptyStringsToNull(object obj) { if (obj == null) return; var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { if (prop.PropertyType == typeof(string)) { var value = (string?)prop.GetValue(obj); if (value == string.Empty) { prop.SetValue(obj, null); } } // 如果有嵌套对象,可以递归处理 else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { SetEmptyStringsToNull(prop.GetValue(obj)!); } } } // 修改Serialize方法,先处理空字符串再序列化 public string Serialize(object value) { SetEmptyStringsToNull(value); return Serialize(value, serializeOptions); }
使用方案1或方案2后,测试用例中的MiddleName属性会被直接省略,不会出现在最终的JSON中。
内容的提问来源于stack exchange,提问作者Martijn
相关产品推荐
相关产品推荐

