C# System.Text.Json序列化如何实现自定义差异化缩进格式
实现方案
System.Text.Json 原生的WriteIndented是全局开关,不支持按JSON结构差异化配置缩进规则。要实现「所有{起始的对象单独新起一行、同个对象内属性保持同行、数组内对象元素单独换行」的效果,最稳妥的方式是自定义Utf8JsonWriter拦截写入逻辑,不需要为每个实体类型编写单独的JsonConverter,也不需要事后用正则替换序列化结果,不会出现字符串内容被误替换的问题,性能和原生序列化基本一致。
核心实现逻辑
- 序列化时全局关闭
WriteIndented配置,默认所有内容同行输出 - 跟踪当前JSON写入的嵌套深度、上一个写入的Token类型
- 每当检测到要写入对象起始标记
{时,主动写入换行符和对应层级的缩进空格 - 数组内的对象元素写入前自动补充换行缩进,数组内的简单值(数字、字符串等)保持同行
实现代码
首先自定义带换行规则的JsonWriter:
public class ObjectNewlineJsonWriter : Utf8JsonWriter { private readonly int _indentSize; private int _currentDepth; private bool _previousTokenIsArrayStart; public ObjectNewlineJsonWriter(Stream stream, int indentSize = 4, JsonWriterOptions options = default) : base(stream, options) { _indentSize = indentSize; } public override void WriteStartObject() { // 非根对象、非数组首元素前,补充换行和缩进 if (_currentDepth > 0 && !_previousTokenIsArrayStart) { WriteLineAndIndent(); } // 数组内的第一个对象,同样补充换行缩进 if (_previousTokenIsArrayStart) { WriteLineAndIndent(); _previousTokenIsArrayStart = false; } base.WriteStartObject(); _currentDepth++; } public override void WriteEndObject() { _currentDepth--; base.WriteEndObject(); } public override void WriteStartArray() { base.WriteStartArray(); _previousTokenIsArrayStart = true; } public override void WriteEndArray() { _previousTokenIsArrayStart = false; base.WriteEndArray(); } private void WriteLineAndIndent() { WriteRawValue(Environment.NewLine, skipInputValidation: true); // 按当前嵌套层级写入对应长度的缩进空格 for (int i = 0; i < _currentDepth * _indentSize; i++) { WriteRawValue(" ", skipInputValidation: true); } } }
序列化调用示例:
// 序列化全局配置:默认关闭缩进 var serializeOptions = new JsonSerializerOptions { WriteIndented = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; // 用自定义Writer执行序列化 using var ms = new MemoryStream(); using var writer = new ObjectNewlineJsonWriter(ms, indentSize: 4); JsonSerializer.Serialize(writer, yourSchoolInstance, serializeOptions); writer.Flush(); // 拿到最终格式化后的JSON字符串 string formattedJson = Encoding.UTF8.GetString(ms.ToArray());
注意事项
你给出的期望JSON示例和实体类定义存在属性名不匹配的问题:示例中class字段对应实体类的topic属性、grad字段对应实体类的grade属性,使用时可以通过给对应属性加[JsonPropertyName("xxx")]特性做映射,避免输出字段名不符合预期。
生成的JSON完全匹配需求:根对象属性同行显示,每个{开头的对象从新行开始按层级缩进,数组内对象单独占行,无pets字段的学生对象不会产生多余换行。
内容的提问来源于stack exchange,提问作者mnewsum
相关产品推荐
相关产品推荐

