是否存在用于构建来自 API 的 JSON 响应的标准或最佳实践?显然,每个应用程序的数据都是不同的,所以我不关心这些,而是“响应样板”,如果你愿意的话。我的意思的一个例子:
请求成功:
{
"success": true,
"payload": {
/* Application-specific data would go here. */
}
}
请求失败:
{
"success": false,
"payload": {
/* Application-specific data would go here. */
},
"error": {
"code": 123,
"message": "An error occurred!"
}
}
是的,已经出现了几个标准(尽管对标准的定义有一些自由):
JSON API - JSON API 还包括创建和更新资源,而不仅仅是响应。 JSend - 很简单,可能你已经在做。 OData JSON 协议 - 非常复杂。 HAL - 像 OData,但目标是像 HATEOAS。
还有 JSON API 描述格式:
Swagger JSON Schema(由 swagger 使用,但您可以单独使用它)
JSON Schema(由 swagger 使用,但您可以单独使用它)
JSON 格式的 WADL
随机存取存储器
HAL,因为理论上 HATEOAS 是自我描述的。
谷歌 JSON 指南
成功响应返回 data
{
"data": {
"id": 1001,
"name": "Wing"
}
}
错误响应返回 error
{
"error": {
"code": 404,
"message": "ID not found"
}
}
如果你的客户端是JS,你可以使用if ("error" in response) {}
来检查是否有错误。
error
上的链接,谷歌的页面给出了一个例子
我想事实上的标准还没有真正出现(而且可能永远不会出现)。但无论如何,这是我的看法:
请求成功:
{
"status": "success",
"data": {
/* Application-specific data would go here. */
},
"message": null /* Or optional success message */
}
请求失败:
{
"status": "error",
"data": null, /* or optional error payload */
"message": "Error xyz has occurred"
}
优点:成功和错误情况下的顶级元素相同
缺点:没有错误代码,但是如果您愿意,您可以将状态更改为(成功或失败)代码,或者您可以添加另一个名为“代码”的顶级项目。
messages
的字段,它是一个 消息数组,而不是单个字符串。
200
,为什么还需要 status
字段?直接返回数据对象。你知道这可能会给 TypeScript 等类型化的 FE 语言带来额外的痛苦。
假设您的问题是关于 REST Web 服务设计,更准确地说是关于成功/错误。
我认为有3种不同类型的设计。
仅使用 HTTP 状态代码来指示是否存在错误,并尝试将自己限制为标准状态代码(通常就足够了)。优点:它是独立于您的 api 的标准。缺点:关于真实情况的信息较少。使用 HTTP 状态 + json 正文(即使是错误)。为错误定义一个统一的结构(例如:代码、消息、原因、类型等)并将其用于错误,如果成功则只返回预期的 json 响应。优点:仍然是标准的,因为您使用现有的 HTTP 状态代码并返回描述错误的 json(您提供有关发生情况的更多信息)。缺点:输出 json 会因错误或成功而异。忘记 http 状态(例如:总是状态 200),总是使用 json 并在响应的根部添加一个布尔值 responseValid 和一个错误对象(代码、消息等),如果它是一个错误则将被填充,否则其他字段(成功)已填充。优点:客户端仅处理作为 json 字符串的响应主体并忽略状态(?)。缺点:标准较低。
由你来选择:)
根据 API,我会选择 2 或 3(对于 json rest api,我更喜欢 2)。我在设计 REST Api 时经历的另一件事是每个资源 (url) 文档的重要性:参数、正文、响应、标题等 + 示例。
我还建议您使用 jersey(jax-rs 实现)+ genson(java/json 数据绑定库)。您只需在类路径中删除 genson + jersey 即可自动支持 json。
编辑:
解决方案 2 是最难实现的,但优点是您可以很好地处理异常,而不仅仅是业务错误,最初的努力更重要,但从长远来看,您会赢。
解决方案 3 在服务器端和客户端都很容易实现,但它不是很好,因为您必须将要返回的对象封装在一个包含 responseValid + 错误的响应对象中。
RFC 7807: Problem Details for HTTP APIs 是目前我们拥有的最接近官方标准的东西。
以下是 Instagram 使用的 json 格式
{
"meta": {
"error_type": "OAuthException",
"code": 400,
"error_message": "..."
}
"data": {
...
},
"pagination": {
"next_url": "...",
"next_max_id": "13872296"
}
}
我不会傲慢地声称这是一个标准,所以我会使用“我更喜欢”的形式。
我更喜欢简洁的响应(当请求 /articles 列表时,我想要一个 JSON 文章数组)。
在我的设计中,我使用 HTTP 进行状态报告,200 只返回有效负载。
400 返回请求错误的消息:
{"message" : "Missing parameter: 'param'"}
如果模型/控制器/URI 不存在,则返回 404
如果我这边的处理出现错误,我会返回 501 并显示一条消息:
{"message" : "Could not connect to data store."}
从我所看到的情况来看,相当多的 REST-ish 框架倾向于遵循这些原则。
理由:
JSON 应该是 payload 格式,它不是会话协议。冗长的会话式有效负载的整个想法来自 XML/SOAP 世界以及导致这些臃肿设计的各种错误选择。在我们意识到这一切都令人头疼之后,REST/JSON 的全部意义在于亲吻它,并坚持 HTTP。我认为 JSend 中没有任何远程标准,尤其是其中更冗长的。 XHR 将对 HTTP 响应做出反应,如果您将 jQuery 用于您的 AJAX(就像大多数人一样),您可以使用 try
/catch
和 done()
/fail()
回调来捕获错误。我看不出用 JSON 封装状态报告比这更有用。
为了它的价值,我以不同的方式做这件事。成功的调用只有 JSON 对象。我不需要包含指示 true 的成功字段和具有 JSON 对象的有效负载字段的更高级别的 JSON 对象。对于标头中的 HTTP 状态,我只返回 200 或 200 范围内适当的 JSON 对象。
但是,如果出现错误(400 系列中的某个错误),我会返回一个格式正确的 JSON 错误对象。例如,如果客户端正在使用电子邮件地址和电话号码发布用户,并且其中一个格式错误(即我无法将其插入到我的基础数据库中),我将返回如下内容:
{
"description" : "Validation Failed"
"errors" : [ {
"field" : "phoneNumber",
"message" : "Invalid phone number."
} ],
}
这里的重要一点是“字段”属性必须与无法验证的 JSON 字段完全匹配。这使客户可以确切地知道他们的请求出了什么问题。此外,“消息”位于请求的语言环境中。如果“emailAddress”和“phoneNumber”都无效,则“errors”数组将包含两者的条目。 409(冲突)JSON 响应正文可能如下所示:
{
"description" : "Already Exists"
"errors" : [ {
"field" : "phoneNumber",
"message" : "Phone number already exists for another user."
} ],
}
有了 HTTP 状态代码和这个 JSON,客户端就拥有了以确定性方式响应错误所需的一切,并且它不会创建一个新的错误标准来尝试完全替换 HTTP 状态代码。请注意,这些仅发生在 400 个错误的范围内。对于 200 范围内的任何内容,我都可以返回合适的内容。对我来说,它通常是一个类似 HAL 的 JSON 对象,但这并不重要。
我想添加的一件事是“错误”数组条目或 JSON 对象本身的根中的数字错误代码。但到目前为止,我们还不需要它。
他们对大型软件巨头——谷歌、Facebook、Twitter、亚马逊等的其余 api 响应格式没有一致意见,尽管上面的答案中提供了许多链接,其中一些人试图标准化响应格式。
由于 API 的需求可能不同,因此很难让每个人都参与并同意某种格式。如果您有数百万用户使用您的 API,为什么要更改响应格式?
以下是我对受谷歌、推特、亚马逊和互联网上一些帖子启发的响应格式的看法:
https://github.com/adnan-kamili/rest-api-response-format
招摇文件:
https://github.com/adnan-kamili/swagger-sample-template
JSON 的重点在于它是完全动态和灵活的。随心所欲地弯曲它,因为它只是一组序列化的 JavaScript 对象和数组,植根于单个节点。
根节点的类型取决于您,它包含的内容取决于您,是否将元数据与响应一起发送取决于您,是否将 mime-type 设置为 application/json
或将其保留为 {2 } 取决于你(只要你知道如何处理边缘情况)。
构建您喜欢的轻量级架构。就个人而言,我发现分析跟踪和 mp3/ogg 服务和图像库服务以及用于在线游戏的文本消息和网络数据包,以及博客文章和博客评论在什么方面都有非常不同的要求发送和接收什么以及如何使用它们。
因此,在做所有这些时,我最不想做的就是尝试使每个都符合相同的样板标准,该样板标准基于 XML2.0 或类似的东西。
也就是说,使用对您有意义且经过深思熟虑的模式有很多话要说。只需阅读一些 API 响应,记下您喜欢的内容,批评您不喜欢的内容,写下这些批评并理解它们为什么会以错误的方式影响您,然后考虑如何将您学到的知识应用到您需要的东西上。
JSON-RPC 2.0 定义了标准的请求和响应格式,在使用 REST API 之后是一股清新的空气。
code
字段是字符串。幸运的是,规范允许我们将任何我们想要的信息填充到错误的 data
字段中。在我的 JSON-RPC 项目中,我通常使用单个数字代码来处理所有应用程序层错误(而不是标准协议错误之一)。然后我将详细的错误信息(包括一个指示真正错误类型的字符串代码)放在 data
字段中。
建议的基本框架看起来不错,但定义的错误对象太有限了。人们通常不能使用单个值来表达问题,而是使用 chain of problems and causes is needed。
我做了一些研究,发现返回错误(异常)的最常见格式是这种形式的结构:
{
"success": false,
"error": {
"code": "400",
"message": "main error message here",
"target": "approx what the error came from",
"details": [
{
"code": "23-098a",
"message": "Disk drive has frozen up again. It needs to be replaced",
"target": "not sure what the target is"
}
],
"innererror": {
"trace": [ ... ],
"context": [ ... ]
}
}
}
这是 OASIS 数据标准 OASIS OData 提出的格式,似乎是目前最标准的选项,但目前似乎没有任何标准的高采用率。这种格式与 JSON-RPC 规范一致。
您可以在以下位置找到实现此功能的完整开源库:Mendocino JSON Utilities。该库支持 JSON 对象以及异常。
我在 Error Handling in JSON REST API 上的博文中讨论了详细信息
对于后来者,除了包括 HAL、JSend 和 JSON API 在内的公认答案之外,我还要添加一些其他值得研究的规范:
JSON-LD,它是 W3C 推荐标准,指定如何在 JSON 中构建可互操作的 Web 服务
REST 的 Ion 超媒体类型,它声称自己是“一种简单直观的基于 JSON 的 REST 超媒体类型”
我曾经遵循这个标准,在客户端层非常好、简单、干净。
通常情况下,HTTP 状态 200,所以这是我在顶部使用的标准检查。我通常使用以下 JSON
我还使用 API 的模板
dynamic response;
try {
// query and what not.
response.payload = new {
data = new {
pagination = new Pagination(),
customer = new Customer(),
notifications = 5
}
}
// again something here if we get here success has to be true
// I follow an exit first strategy, instead of building a pyramid
// of doom.
response.success = true;
}
catch(Exception exception){
response.success = false;
response.message = exception.GetStackTrace();
_logger.Fatal(exception, this.GetFacadeName())
}
return response;
{
"success": boolean,
"message": "some message",
"payload": {
"data" : []
"message": ""
... // put whatever you want to here.
}
}
在客户端层我将使用以下内容:
if(response.code != 200) {
// woops something went wrong.
return;
}
if(!response.success){
console.debug ( response.message );
return;
}
// if we are here then success has to be true.
if(response.payload) {
....
}
请注意我是如何早早打破厄运金字塔的。
除了常识,没有违法或违法的标准。如果我们将其抽象为两个人交谈,标准是他们可以在最短的时间内用最少的单词准确理解彼此的最佳方式。在我们的例子中,“最少词”是优化带宽以提高传输效率,“准确理解”是提高解析器效率的结构;最终导致数据越少,结构越通用;这样它就可以通过一个针孔并可以通过一个通用范围进行解析(至少在最初)。
几乎在所有建议的情况下,我都会看到“成功”和“错误”场景的不同响应,这对我来说有点模棱两可。如果这两种情况下的反应不同,那么为什么我们真的需要在那里放置一个“成功”标志呢?没有“错误”就是“成功”这不是很明显吗?是否可以在“成功”为 TRUE 并设置“错误”的情况下做出响应?或者,“成功”是假的,没有设置“错误”?一个flag还不够?我宁愿只使用“错误”标志,因为我相信“错误”会比“成功”少。
另外,我们真的应该将“错误”设置为标志吗?如果我想响应多个验证错误怎么办?因此,我发现将每个错误作为该节点的子节点的“错误”节点更有效;其中一个空的(计数为零)“错误”节点表示“成功”。
我将此结构用于 REST API:
{
"success": false,
"response": {
"data": [],
"pagination": {}
},
"errors": [
{
"code": 500,
"message": "server 500 Error"
}
]
}
有点晚了,但这是我对 HTTP 错误响应的看法,我发送代码(通过状态)、通用消息和详细信息(如果我想提供特定端点的详细信息,有些是不言自明的,所以不需要详细信息但它可以是自定义消息,甚至可以是完整的堆栈跟踪,具体取决于用例)。为了成功,它是一个类似的格式、代码、消息和数据属性中的任何数据。
ExpressJS 响应示例:
// Error
res
.status(422)
.json({
error: {
message: 'missing parameters',
details: `missing ${missingParam}`,
}
});
// or
res
.status(422)
.json({
error: {
message: 'missing parameters',
details: 'expected: {prop1, prop2, prop3',
}
});
// Success
res
.status(200)
.json({
message: 'password updated',
data: {member: { username }}, // [] ...
});
移动开发人员可以轻松理解的 Web api 的最佳响应。
这是为了“成功”响应
{
"code":"1",
"msg":"Successfull Transaction",
"value":"",
"data":{
"EmployeeName":"Admin",
"EmployeeID":1
}
}
这是针对“错误”响应
{
"code": "4",
"msg": "Invalid Username and Password",
"value": "",
"data": {}
}