Sails 框架 res.json() 完全指南:用法、源码实现与最佳实践
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
导读
res.json()是 Sails(基于 Node.js 的 Realtime MVC 框架)中用于向客户端发送 JSON 响应的核心终端方法,广泛应用于 REST API 控制器、自定义 action 与自定义响应(custom responses)中。本文以官方参考文档为主体,结合 Sails 源码仓库中的实际实现,深入讲解res.json()的用法、与res.send()的区别、底层调用链,以及开发/生产环境下的格式化行为,帮助你写出规范、可复用的 JSON 接口代码。
一、基本用法与签名
1.1 语法
return res.json(data);其中data是你希望发送给客户端的任意数据。该方法的全名是全小写的res.json(),不要误写成res.JSON()或res.Json()。
1.2 方法特性
- 发送 JSON 响应:
res.json()将指定的data序列化为 JSON 字符串并作为响应体发送; - 终端方法(terminal):与 Sails 中大多数响应方法一样,
res.json()一般应作为某个请求处理流程中的最后一行代码执行,因此官方文档统一建议通过return res.json(data)的形式调用,既保证响应只发送一次,也避免后续代码继续执行产生二次响应。
二、核心行为:与res.send()的区别
官方文档明确指出:
当传入的是对象(object)或数组(array)时,
res.json()与res.send()行为完全一致。但与res.send()不同的是,res.json()还可以对非对象类型(如null、undefined、数字、字符串等)进行显式 JSON 转换——尽管这些值在严格意义上并不是合法的 JSON。
从源码层面看,这一行为在 lib/router/res.js 的_jsonShim实现中有直观体现:
res.json = res.json || function _jsonShim (data, noLongerSupported) { if (!_.isUndefined(noLongerSupported)) { throw new Error('The 2-ary usage of `res.json()` is no longer supported in Express 4/Sails v1. Please use `res.status(statusCode).json(body)` instead.'); } // If data is a string, JSON stringify it. // (Otherwise, we can just rely on `send` to do that for us.) if (_.isString(data)) { data = JSON.stringify(data); res.set('content-type', 'application/json'); } return res.status(res.statusCode || 200).send(data); };从上述实现可以提炼出三个关键点:
- 字符串数据会被强制 JSON 序列化:如果传入字符串,
res.json()会先对它执行JSON.stringify()(相当于加了一层引号),并显式设置Content-Type: application/json;而res.send()对字符串是原样发送、不设置 JSON 头; - 最终委托给
res.send():res.json()本质上是res.send()的包装,对象/数组由res.send()内部的JSON.stringify完成序列化(见 lib/router/res.js); - 默认状态码为 200:
res.status(res.statusCode || 200)保证在未显式设置状态码时返回 200。
2.1 序列化失败的兜底
在res.send()的内部实现中,如果JSON.stringify(data)抛错(例如数据包含循环引用),框架会捕获异常并给出明确的报错信息,同时将响应状态码置为 500(见 lib/router/res.js),避免应用静默失败。
三、实战示例(完整继承官方用例)
3.1 发送一个对象
return res.json({ firstName: 'Tobi' });响应结果:
{ "firstName": "Tobi" }3.2 与res.status()链式调用,指定状态码
return res.status(201).json({ id: 201721 });res.status()返回res自身以支持链式调用(见 lib/router/res.js),因此可以先设置 201(Created)状态码,再发送 JSON 响应体。
3.3 发送原始类型(primitive)
结合 Waterline 数据查询的典型场景:
var leena = await User.findOne({ firstName: 'Leena' }); if (!leena) { return res.notFound(); } return res.json(leena.id);//« you can send down primitives, like numbers需要注意:虽然res.json()允许发送数字这类原始类型(响应体会是123这样的裸数字),但这在严格 JSON 规范下并不完全合法,实践中更推荐发送对象:
return res.json({ id: leena.id });3.4 发送数组
return res.json([ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]);四、从源码看res.json()的完整调用链
在 Sails v1 中,res.json()并不是凭空存在的方法,而是 Sails 在虚拟请求解释器(virtual request interpreter)中为res对象补齐的一组"shim"之一。其完整调用链如下:
- 请求进入 Sails 路由层后,
res对象经由 lib/router/res.js 构建——若底层没有真实的 Node HTTP 响应流,则使用MockRes,并注入res.status()、res.send()、res.json()等方法的 shim 实现; - 应用代码调用
return res.json(data); _jsonShim对字符串做特殊处理(JSON 序列化 + 设置 JSON 头),其余情况交给res.send();res.send()负责:禁止二次响应(onlyAllowOneResponse,见 lib/router/res.js)、确保字符集为utf-8、序列化非字符串数据、写入响应流并end();- 若使用了虚拟请求解释器(如通过
sails.request()发起请求,见 lib/app/request.js),数据会被缓冲到res._clientRes.body供回调读取(见 lib/router/res.js)。
4.1 幂等性与"一次响应"保护
onlyAllowOneResponse()会在首次发送响应时置位_virtualResponseStarted,此后再次调用res.send()/res.json()会抛出'Cannot write to response more than once'错误。这就是官方文档强调用return包裹调用的根本原因——防止同一次请求中意外发送多次响应。
五、开发环境下的 JSON 美化输出
Sails 在 HTTP 层初始化时(lib/hooks/http/initialize.js)针对res.json()的输出做了环境相关处理:
// In non-production environments, format `res.json()` output nicely. if (process.env.NODE_ENV !== 'production') { expressApp.set('json spaces', 2); }也就是说:
- 开发环境(
NODE_ENV !== 'production'):JSON 响应会以 2 空格缩进的美化格式输出,方便调试与阅读; - 生产环境(
NODE_ENV === 'production'):不设置json spaces,输出压缩后的紧凑 JSON,节省带宽。
六、res.json()与 Sails 自定义响应方法的关系
Sails 内置的多个自定义响应方法(custom responses)底层都调用了res.json(),它们是对res.json()更高层语义的封装:
| 方法 | 状态码 | 底层行为 | 源码位置 |
|---|---|---|---|
res.ok() | 200 | 无数据时走sendStatus(200),有数据时调用res.json(data) | lib/hooks/responses/defaults/ok.js |
res.badRequest() | 400 | 最终调用res.json(data) | lib/hooks/responses/defaults/badRequest.js |
res.serverError() | 500 | 根据环境调用res.json(data)或发送精简消息 | lib/hooks/responses/defaults/serverError.js |
以res.ok()为例(lib/hooks/responses/defaults/ok.js),它对传入的Error实例做了防御处理:若 Error 没有自定义toJSON(),开发环境下用util.inspect()输出可读信息,生产环境下直接sendStatus(200),避免res.json()把 Error 序列化成一个空字典{}。这说明:当你需要自定义响应时,理解res.json()的序列化行为是前提。
此外,历史遗留的res.jsonx()在 Sails v1.0 中已被标记为废弃(见 lib/hooks/responses/index.js),官方建议统一使用res.json()(或res.jsonp()处理 JSONP 场景)。
七、注意事项与常见误区
- 方法名全小写:
res.json()中的json必须小写,这是 Express/Sails 的既定约定; - 终端方法:它是某个请求处理流程的终点,务必配合
return使用,防止后续代码继续执行; - 不要再传第二个参数:Express 4 / Sails v1 起,
res.json(statusCode, data)这种二参数用法已不再支持,调用会直接抛错。正确写法是先res.status(statusCode)再.json(data)(见 lib/router/res.js); - 不要重复发送响应:
res.json()之后再次调用任何发送型方法会触发'Cannot write to response more than once'错误; - 优先发送对象:虽然可以发送数字等原始类型,但从 JSON 规范与客户端解析健壮性考虑,推荐统一发送对象或数组;
- 发送错误对象需谨慎:直接
res.json(err)可能把Error实例序列化成空字典,建议使用res.serverError()或先提取err.message。
八、延伸阅读
- 与
res.json()最常搭配的状态码设置方法:res.status(); res.json()的底层基座方法(字符串/XML/CSV 等非 JSON 响应):res.send();- 在 REST API 中更语义化的响应封装:res.ok()、res.notFound()、res.serverError();
- 自定义响应方法的位置与用法:Custom Responses。
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考