REST API-API是否应返回嵌套的JSON对象?


37

当涉及JSON API时,将响应展平并避免嵌套JSON对象是一种好习惯吗?

例如,假设我们有一个类似于IMDb的API,但用于视频游戏。有几个实体,分别是Game,Platform,ESRBRating和GamePlatformMap,它们映射了Game和Platform。

假设您请求/ game / 1来获取ID为1的游戏,并返回嵌套了平台和esrbRating的游戏对象。

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
  "platforms": [
    {"id":1,"name":"Xbox"},
    {"id":2,"name":"Playstation"}
  ],
  "esrbRating": {
    "id": 1,
    "code": "E",
    "name": "Everyone"
  }
}

如果您使用的是JPA / Hibernate之类的工具,并且将其设置为FETCH.EAGER,它可能会自动为您执行此操作。

另一个选择是简单地使用API​​并添加更多端点。

在这种情况下,当请求/ game / 1时,仅返回游戏对象。

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
}

如果需要平台和/或ESRBRating,则必须调用以下命令:

/ game / 1 /平台/ game / 1 / esrb

这种方法似乎可能会根据客户端需要什么数据以及何时需要它们而向服务器添加多个调用。

最后有一个想法,我曾在这里遇到过类似的问题。

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
  "platforms": ["Xbox","Playstation"]
}

但是,这假设他们不需要ID或与这些平台对象关联的任何其他信息。

我通常会问,构造从API返回的JSON对象的最佳方法是什么。您是否应该尝试尽可能靠近实体,还是使用域对象或数据传输对象很好?我知道这些方法会有所取舍,要么在数据访问层上进行更多工作,要么为客户端进行更多工作。

我还想听到一个与使用Spring MVC作为API的后端技术有关的答案,可以使用JPA / Hibernate或MyBatis进行持久化。


6
您有什么异议(如果有的话)返回了嵌入的对象?从不同的端点分别返回嵌入的对象将变得非常恼人(更不用说缓慢了)。
罗伯特·哈维

1
我个人对此没有异议。我只是不知道什么是最佳做法。一位同事声称在AngularJS中使用嵌入式对象不是直接的,最终我希望AngularJS应用程序的Ember都可以使用该API。我对Angular或Ember的了解不多,是否会产生影响。
greyfox 2015年

3
答案将取决于您是否要返回域对象,DTO,ViewModel对象或KitchenSink对象。返回的对象很可能取决于应用程序的需求以及该对象在Internet上的行为。 例如:如果您试图用发票数据填充网页,则很可能会返回一个包含所需内容的对象(除非您计划在订单项中进行AJAXing处理)。
罗伯特·哈维

在您请求游戏时,会遇到这种情况,您可能想知道游戏的类型,平台和ESRBRating。那讲得通。从Java的角度来看,您是否建议使用具有JPA实体的Entity包,然后再向其返回业务对象/ DTO的域包?
greyfox 2015年

1
调用服务器非常昂贵。要求您使用多个调用发送数据的API会比允许您在一个调用中获得所有内容的API慢,即使该API返回不需要的信息也是如此。

Answers:


11

另一种选择(使用HATEOAS)。这很简单,实际上在大多数情况下,您会根据对HATEOAS的使用在json中添加一个links标签。

http://api.example.com/games/1

{
  "id": 1,
  "title": "Game A",
  "publisher": "Publisher ABC",
  "developer": "Developer DEF",
  "releaseDate": "2015-01-01",
  "platforms": [
    {"_self": "http://api.example.com/games/1/platforms/53", "name": "Playstation"},
    {"_self": "http://api.example.com/games/1/platforms/34", "name": "Xbox"},
  ]
}

http://api.example.com/games/1/platforms/34

{
  "id": 34,
  "title": "Xbox",
  "publisher": "Microsoft",
  "releaseDate": "2015-01-01",
  "testReport": "http://api.example.com/games/1/platforms/34/reports/84848.pdf",
  "forms": [
    {"type": "edit", "fields: [] },
  ]
}

您当然可以将所有数据嵌入到所有列表中,但这可能是过多的数据。这样,您可以嵌入所需的数据,然后在确实需要使用时加载更多数据。

技术实现可以包含缓存。您可以在游戏对象中缓存平台链接和名称并立即发送,而无需加载平台api。然后在需要时可以加载它。

例如,您看到我添加了一些表单信息。我这样做是为了向您展示在一个详细的json对象中,甚至比您希望在游戏列表中加载的信息还多得多。


我不认为这在技术上是HATEOS,因为没有状态。
RibaldEddie'2

是的,不确定此过程的确切字词。一般而言,HATEOS用于链接其余的API,但我同意它也与状态有关。尽管实现的想法是相同的。在这里,您会看到更多有关示例如何使用它的信息:stormpath.com/blog/linking-and-resource-expansion-rest-api-tips
Luc Franken

这是个好主意!
RibaldEddie

1
如果您正在开发一个客户端与api本身之间存在内聚力的API(例如内部api),则返回嵌套(或扁平化)响应而不是提供指向另一个资源的链接可能更有意义,这意味着更多的API请求这可能是不希望的。
布鲁诺

@bruno是的,但有一个局限性:在较大的系统上,您不能或不想完全提供所有相关对象。默认情况下,您包含的字段是任意的,您可以根据api的用法选择它们。因此,在这种情况下,您可能具有包含数百个字段的平台,用例显示了一个选择框来选择平台。然后,包含平台名称是有意义的,但是不需要平台的财务详细信息。
Luc Franken

16

这是有关REST API设计的那些基本问题之一。每个设计师在第一天都会问自己这个问题。抱歉,答案是“取决于”。每种方法都各有利弊,您只需要做出决定并坚持下去即可。


10
这根本没有帮助。OP自己知道“这取决于并且每种方法都有其优缺点”。您应该解释它所依赖的东西,或者至少给出一些例子。
Pratik Singhal '18

5

我第二次在这里介绍的方法https://www.slideshare.net/stormpath/rest-jsonapis

简而言之,将嵌套资源作为链接包含在父资源中,同时在父端点中提供一个expand参数。

我认为,在大多数情况下,这是一种高效而灵活的方法。


2
我喜欢这种方法。对于任何想知道的人,它都从链接的幻灯片放映的幻灯片57开始。
亚当·普洛彻
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.