utf8jsonwriter 是面向流式、无缓存、严格结构校验的底层 json 写入器,需手动配对 begin/end 方法,正确初始化 stream,慎用 writerawvalue,且所有写入操作均无类型检查与空值防护。

Utf8JsonWriter 不是用来“拼 JSON 字符串”的,它是面向流式、无缓存、严格结构校验的底层写入器;用错场景或漏掉配对方法,生成的 JSON 一定非法,但运行时不报错。
初始化时传入的 Stream 必须可写且已设置容量
常见错误现象:写入中途抛 IOException,提示“Stream 不支持写入”或“无法扩展内存”。
- 传入
new MemoryStream()(无参构造)会导致频繁扩容,性能骤降;应预估大小,如new MemoryStream(4096) - 传入
context.Response.Body前,必须确认它未被封装为只读视图(例如某些中间件会 wrap 成NonWriteableStream) - 绝对不要把
Encoding.UTF8.GetBytes("...")的结果转成ReadOnlyMemory<byte></byte>再塞给Utf8JsonWriter——它只接受Stream或实现IBufferWriter<byte></byte>的类型 - 若需复用
MemoryStream,写完后调用stream.Position = 0才能读取内容
嵌套结构必须手动配对 Begin/End 方法
没有自动补全,没有容错,漏一个 WriteEndArray() 或 WriteEndObject(),JSON 就是非法的;下游解析时才报 JsonReaderException: Unexpected end of input,极难定位。
- 循环写数组项前,必须先调用
WriteStartArray("items");循环结束后,必须调用WriteEndArray() - 条件分支中调用
WriteStartObject(),所有分支路径都必须有对应的WriteEndObject(),包括else和异常处理路径 -
WritePropertyName("data")后不能直接跟WriteString("value")——除非你真想写字符串值;如果要写对象或数组,必须接WriteStartObject()或WriteStartArray() - 深度不匹配(
CurrentDepth不为 0)就 dispose,会丢数据;务必在最后调用Flush()
WriteString / WriteNumber 等方法要选对重载
传参类型决定是否触发编码开销和转义逻辑,也影响数值格式(比如是否带小数点)。
- 写固定字段名(如
"name"、"id"),优先用JsonEncodedText.Encode("name")缓存,再传给WritePropertyName(),避免每次 UTF-16 → UTF-8 转码 - 写动态字符串值,用
WriteString(string);写已知纯 ASCII 值,可用WriteString(ReadOnlySpan<char>)</char>略微提速 - 写数字:整数用
WriteNumberValue(int),浮点数用WriteNumberValue(double);若需保留.0(如兼容 JS number 类型判断),得用WriteRawValue("42.0") - 写 null 值必须显式调用
WriteNull(),不能传null给WriteString()(会抛ArgumentNullException)
需要写原始 JSON 或跳过验证时用 WriteRawValue
这是唯一绕过结构校验和自动转义的方式,但代价是完全失去安全边界。
- 典型场景:把已有 JSON 片段(如从数据库读出的 JSON 字段)直接嵌入新结构,避免双重序列化
- 使用前确保原始内容本身是合法 UTF-8 编码的 JSON 文本,否则整个输出无效
- 不能在未配对的结构内调用;例如在
WriteStartObject()后直接WriteRawValue("{...}"),必须保证原始 JSON 是单个值(对象、数组、字符串等) - 启用
JsonWriterOptions.SkipValidation = true可跳过语法检查,但WriteRawValue仍要求上下文处于可写值位置(如属性值、数组项)
最易被忽略的一点:所有 WriteXxx 方法都不做任何类型检查或空值防护——传入 null 给 WriteString、传 DateTime.MinValue 给 WriteDateTime 都可能静默失败或写出意外内容。真实业务中,建议封装一层校验逻辑,而不是依赖 Utf8JsonWriter 自身兜底。










