必须用SELECT...FOR JSON PATH输出,禁用PRINT和字符串拼接;PATH模式支持点号嵌套、ROOT、WITHOUT_ARRAY_WRAPPER、INCLUDE_NULL_VALUES等选项,AUTO模式会将"user.name"视为键名而非嵌套路径,多层结构需子查询+JSON_QUERY避免转义,且须SET NOCOUNT ON确保单结果集。

存储过程必须用 SELECT ... FOR JSON 输出,不能靠 PRINT 或拼接字符串;否则 API 拿不到有效 JSON。
FOR JSON PATH 是唯一可靠选择
SQL Server 2016+ 原生支持 JSON 输出,FOR JSON PATH 是最可控、最稳定的方式。它不依赖表结构推导,允许你用点号语法控制嵌套,也支持 ROOT、WITHOUT_ARRAY_WRAPPER、INCLUDE_NULL_VALUES 等关键选项。
常见错误是误用 FOR JSON AUTO:它在多表 JOIN 时会自动嵌套,但字段别名含点号(如 user.name)会被当作文本键名而非嵌套路径,导致前端解析失败。
-
FOR JSON PATH下,SELECT name AS 'user.name'→{"user": {"name": "xxx"}} -
FOR JSON AUTO下,同样写法 →{"user.name": "xxx"}(不是对象嵌套) - 单行结果需返回对象而非数组?加
WITHOUT_ARRAY_WRAPPER - 要保留
NULL字段?必须显式加INCLUDE_NULL_VALUES
嵌套多层结构必须用子查询 + JSON_QUERY
原生 FOR JSON 不支持“同级多个数组”或混合结构(比如一个用户对象里同时有 orders 数组和 preferences 对象)。强行用 AUTO 或多层 PATH 别名无法满足。
正确做法是把子结构作为列表达式,用子查询生成 JSON 字符串,再用 JSON_QUERY 告诉 SQL Server “这是合法 JSON,别转义”:
SELECT u.id, u.name, (SELECT o.order_id, o.total FROM orders o WHERE o.user_id = u.id FOR JSON PATH) AS orders, JSON_QUERY((SELECT p.key, p.value FROM prefs p WHERE p.user_id = u.id FOR JSON PATH)) AS preferences FROM users u FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
注意:JSON_QUERY 的输入必须是合法 JSON 字符串;如果子查询结果为 NULL,整个字段会消失,需配合 ISNULL(..., '[]') 处理。
避免 JSON 字符串被双转义
如果你从表中读出一个已存的 JSON 字段(比如 nvarchar(max) 类型的 payload),直接 SELECT payload FROM t FOR JSON PATH 会导致它变成字符串字面量:"{\"key\":1}",而不是真正的嵌套对象。
解决方法只有两个:
- 用
JSON_QUERY(payload)包一层(前提是该字段内容确实是合法 JSON) - 确保该字段类型是
json(SQL Server 2022+ 支持),此时引擎能自动识别并跳过转义
别试图用 REPLACE 或字符串拼接绕过——既不可靠,又破坏 JSON 合法性。
存储过程里别忘了 SET NOCOUNT ON
API 调用存储过程时,如果过程里有中间 INSERT/UPDATE 或未抑制的消息,SQL Server 会返回多个结果集。多数 HTTP 客户端(如 C# HttpClient、fetch)只取第一个结果,导致 JSON 被截断或解析报错。
务必在存储过程开头加:
SET NOCOUNT ON;
并且最终输出必须是且仅是一条 SELECT ... FOR JSON 语句。任何 PRINT、RAISERROR(非严重级)、临时表 SELECT 都会污染结果流。
最容易被忽略的是:JSON 字段名大小写敏感,而 SQL 列别名默认不保大小写。如果前端严格按 userName 读取,就得写 SELECT name AS "userName"(双引号强制保留),否则可能变成 username。这点在跨团队协作时经常引发隐性故障。


















