utf8jsonwriter适合高性能低gc的json生成场景,如流式api响应、结构化日志和大数据导出;需预分配memorystream容量、严格配对begin/end方法、显式调用flush,并选用合适重载方法以避免性能损耗。

直接用 Utf8JsonWriter 写 JSON,不是为了“更酷”,而是当你需要每秒写入数万条日志、流式返回大数据集、或导出百万行结构化数据时,它比 JsonSerializer.Serialize 快 2–5 倍,且几乎不触发 GC ——但前提是别踩坑。
初始化时传错 Stream 或没预分配 MemoryStream 容量
常见错误是直接 new MemoryStream() 不带容量,或把只读的 ReadOnlyMemory<byte></byte> 封装成 Stream 传进去。前者导致频繁扩容(性能断崖),后者在第一次 WriteStartObject() 就抛 IOException。
- 写入内存:用
new MemoryStream(4096)预分配,够大多数小对象;写完记得stream.Position = 0才能读取 - 写入响应体:
context.Response.Body可直接传,但必须提前设好头:context.Response.ContentType = "application/json; charset=utf-8" - 绝对不要传
Encoding.UTF8.GetBytes("...")得到的字节数组——Utf8JsonWriter只接受可写的Stream或实现IBufferWriter<byte></byte>的类型
嵌套结构漏掉 WriteEndXxx() 导致 JSON 无效但不报错
Utf8JsonWriter 不校验结构完整性,漏一个 WriteEndArray(),输出就是截断的 JSON,下游解析时才报 JsonReaderException: Unexpected end of input,排查极难。
- 每个
WriteStartObject()必须配对WriteEndObject(),数组同理 - 循环写数组项时,
WriteStartArray()和WriteEndArray()一定写在循环外 - 条件分支里调用了
WriteStartObject(),所有分支路径都得保证有对应WriteEndObject(),包括 else 和异常路径 - 示例正确写法:
writer.WriteStartObject(); writer.WriteString("name", "Alice"); writer.WriteStartArray("hobbies"); writer.WriteString("reading"); writer.WriteEndArray(); writer.WriteEndObject();
WriteString 重载选错导致性能差一倍
WriteString(string) 会做 UTF-16 → UTF-8 编码,而 WriteString(ReadOnlySpan<char>)</char> 可复用已有字符串切片,避免分配。对高频写入场景(如日志字段),差异明显。
- 已知常量名(如
"id"、"status")优先用JsonEncodedText.Encode("id")静态缓存,再传给WritePropertyName() - 动态字符串(如用户输入)若长度可控,转
ReadOnlySpan<char></char>再写;否则老实用WriteString(string) - 数值写入别混用:写整数用
WriteNumber(string, int),写浮点用WriteNumber(string, double);误用WriteString("123")会让数字变字符串
需要写原始 JSON 片段时别硬拼字符串
比如你要内嵌一段已生成好的 JSON 字符串(如从第三方 API 拿来的值),或想自定义数字格式(强制写 1.0 而非 1),不能用 WriteString("{...}") ——这会把整个字符串当普通值转义,变成 "{...}"(带引号)。
- 改用
WriteRawValue(string, skipInputValidation: true),它直接注入原始字节,不转义、不加引号 - 确保传入的是单个合法 JSON 值(如
"[1,2,3]"、"{\"x\":42}"),否则破坏整体结构 - 若原始内容来自不可信输入,
skipInputValidation: false(默认)可防止注入非法结构,但会多一次解析开销
最易被忽略的点:Utf8JsonWriter 不自动 flush,也不在 Dispose() 时隐式提交未刷出的字节——必须显式调用 Flush(),否则最后几 KB 数据可能永远卡在缓冲区里发不出去。










