jackson REST API,用于更新具有空值的信息

mf98qq94  于 2022-11-08  发布在  其他
关注(0)|答案(6)|浏览(132)

我有一个关于如何最好地构建一个可以修改数据库中记录的API的一般性问题。
假设我们有一个包含10列的表,我们可以使用REST(GET)查询这10列,JSON响应将包含所有10个字段,这很简单,也没有问题。
下一步是某人想通过POST创建一个新记录。在这种情况下,这个人只发送JSON请求中10个字段中的8个。然后我们只在数据库中填写这8个字段(其余的字段为NULL)。这也没有问题。
但是如果有人想更新记录会发生什么呢?我们在这里看到了不同的可能性及其优缺点。
1.仅发送应更新的内容。问题:如何显式清空/删除一个字段?如果在JSON中传递了一个“NULL”,我们在对象中得到NULL,但任何其他没有传递的字段也是NULL。因此,我们无法区分哪些字段可以删除,哪些字段不能访问。
1.已发送完整对象。问题:这里的对象以前可以通过GET来获取,然后进行相应的修改,最后通过PUT返回。现在我们可以获取所有的信息,并且可以直接将这些信息写回到数据库中。因为空字段以前已经是空的,或者已经被用户清除了。
如果通过更新API扩展对象会发生什么情况?假设我们将数据库再扩展5个字段。API的用户执行GET,获取15个字段,但只能读取他知道的页面上的10个字段(因为他还没有更新他的边)。然后,他修改了10个字段中的一些字段,并通过PUT将它们发送回来。然后,我们将只更新我们网站上的10个字段,而5个新字段将从数据库中清空。
或者你必须为每个字段创建一个单独的端点吗?我们也考虑过用键/值创建一个Map,确切地说应该改变什么。
关于技术:我们使用Wildfly 15 with Resteasy and Jackson

例如:

数据库开头

+----+----------+---------------+-----+--------+-------+
| ID | Name     | Country       | Age | Weight | Phone |
+----+----------+---------------+-----+--------+-------+
| 1  | Person 1 | Germany       | 22  | 60     | 12345 |
| 2  | Person 2 | United States | 32  | 78     | 56789 |
| 3  | Person 3 | Canada        | 52  | 102    | 99999 |
+----+----------+---------------+-----+--------+-------+

GET.../人/2

{
   "id" : 2,
   "name" : "Person 2",
   "country" : "United States",
   "age" : 22,
   "weight" :62,
   "phone": "56789"
}

现在我想更新他的体重并删除电话号码
放.../人/2

{
   "id" : 2,
   "name" : "Person 2",
   "country" : "United States",
   "age" : 22,
   "weight" :78
}

{
   "id" : 2,
   "name" : "Person 2",
   "country" : "United States",
   "age" : 22,
   "weight" :78,
   "phone" : null
}

现在,数据库应如下所示:

+----+----------+---------------+-----+--------+-------+
| ID | Name     | Country       | Age | Weight | Phone |
+----+----------+---------------+-----+--------+-------+
| 1  | Person 1 | Germany       | 22  | 60     | 12345 |
| 2  | Person 2 | United States | 32  | 78     | NULL  |
| 3  | Person 3 | Canada        | 52  | 102    | 99999 |
+----+----------+---------------+-----+--------+-------+

"问题是"
我们这样扩展table(salery)

+----+----------+---------------+-----+--------+--------+-------+
| ID | Name     | Country       | Age | Weight | Salery | Phone |
+----+----------+---------------+-----+--------+--------+-------+
| 1  | Person 1 | Germany       | 22  | 60     | 1929   | 12345 |
| 2  | Person 2 | United States | 32  | 78     | 2831   | NULL  |
| 3  | Person 3 | Canada        | 52  | 102    | 3921   | 99999 |
+----+----------+---------------+-----+--------+--------+-------+

使用API的人不知道JSON中有一个新的工资字段。这个人现在想再次更改某人的电话号码,但不发送工资。这也会清空工资:

{
   "id" : 3,
   "name" : "Person 3",
   "country" : "Cananda",
   "age" : 52,
   "weight" :102,
   "phone" : null
}

+----+----------+---------------+-----+--------+--------+-------+
| ID | Name     | Country       | Age | Weight | Salery | Phone |
+----+----------+---------------+-----+--------+--------+-------+
| 1  | Person 1 | Germany       | 22  | 60     | 1929   | 12345 |
| 2  | Person 2 | United States | 32  | 78     | 2831   | NULL  |
| 3  | Person 3 | Canada        | 52  | 102    | NULL   | NULL  |
+----+----------+---------------+-----+--------+--------+-------+

并且salary不应为空,因为它不是在JSON请求中设置的

sd2nnvve

sd2nnvve1#

您可以将JSON反序列化为Map。这样,如果尚未发送属性,则该属性不会出现在Map中。如果该属性为空,则其在Map中的值将为空。

ObjectMapper mapper = new ObjectMapper();
  TypeReference<HashMap<String, Object>> typeReference = new TypeReference<>() {};
  HashMap<String, Object> jsonMap = mapper.readValue(json, typeReference);
  jsonMap.entrySet().stream().map(Map.Entry::getKey).forEach(System.out::println);

不是一个很方便的解决方案,但它可能对你有用。

aor9mmx1

aor9mmx12#

一种常见的技术是跟踪实体POJO上的更改。
1.将color =黑色、size =空值和age =空值装入狗
1.将size设置为空(设置器将此字段标记为已更改)
1.运行更新SQL
POJO将有一个内部状态,知道size已更改,因此将该字段包含在UPDATE中。另一方面,age从未设置,因此保持不变。jOOQ是这样工作的,我相信还有其他的。

3pvhb19x

3pvhb19x3#

1.仅发送应更新的内容。问题:如何显式清空/删除一个字段?如果在JSON中传递了一个“NULL”,我们在对象中得到NULL,但任何其他没有传递的字段也是NULL。因此,我们无法区分哪些字段可以删除,哪些字段不能访问。
您确定的问题是真实的;我也遇到过这种情况。我认为为此提供技术解决方案是合理的,而是记录API的使用,让调用者知道省略一个字段或将其作为null发送的影响。当然,假设服务器端的验证是严格的,并确保健全的。
1.已发送完整对象。问题:这里的对象以前可以通过GET来获取,然后进行相应的修改,最后通过PUT返回。现在我们可以获取所有的信息,并且可以直接将这些信息写回到数据库中。因为空字段以前已经是空的,或者已经被用户清除了。
这是“直接的”,应该记录在API中。
如果对象通过API的更新而扩展,会发生什么情况。
通过文档将责任放在调用者身上,这也是隐式处理的。
还是必须为每个字段创建单独的端点?
这也是一个设计问题,解决方案因人而异。我宁愿在记录级别而不是在个人值级别保留API。然而,可能有这样的情况需要它们。例如,状态更新。

mklgxw1f

mklgxw1f4#

假设我们将数据库再扩展5个字段。API的用户执行GET,得到15个字段,但只能读取他在页面上知道的10个字段(因为他还没有更新他的页面)。然后,他更改了10个字段中的一些字段,并通过PUT将它们发送回来。然后,我们将只更新我们网站上的10个字段,而5个新字段将从数据库中清空。
让我们从一个例子开始-在Web上会发生什么,客户端通过浏览器中呈现的HTML与您的API交互。客户端将获取一个表单,该表单将为每个字段提供输入控件。客户端更新表单中的字段,提交表单,然后您将这些更改应用到数据库中。
当您想扩展API以包含更多字段时,您可以将这些字段添加到表单中。客户端并不知道这些字段。那么会发生什么呢?
管理此问题的一种方法是,确保在表单中包含新字段的正确默认值;那么,如果客户端忽略新字段,则在提交表单时将返回正确的值。
更一般地说,我们在HTTP有效负载中交换的表示是 * 消息 *;如果我们希望支持旧的客户端,那么我们需要以向后兼容的方式发展消息模式的规则,并且我们的客户端必须在理解消息模式可以用附加字段扩展的情况下编写。
使用API的人并不知道JSON中有一个新的薪金字段。
同样的想法在这里也适用--新的表示包括客户端不知道的字段“薪金”,因此客户端有责任将数据 * 原样 * 转发给您,而不是假设数据不重要就将其丢弃。
在15-20年前,有很多关于这方面的现有技术,因为用XML编写消息的人也面临着同样的问题。他们留下了一些知识。找到这些知识的最简单的方法是搜索一些关键阶段;例如must ignoremust forward
请参阅:

事件存储中的事件也有同样类型的问题。X1 E2 F1 X的书X1 E3 F1 X涵盖了很多相同的内容(事件的表示也是消息)。

qvk1mo1f

qvk1mo1f5#

公认的答案很好用,但有一个很大的警告,那就是它是完全非类型化的。如果对象的字段发生了变化,那么编译时不会警告你正在寻找错误的字段。
因此,我认为最好强制所有字段都出现在请求主体中。因此,null意味着用户显式地将其设置为null,而如果用户遗漏了一个字段,他们将收到一个400 Bad Request,请求主体详细描述了错误。
这里有一个伟大的职位如何实现这一点:Configure Jackson to throw an exception when a field is missing
下面是我在Kotlin的例子:

data class PlacementRequestDto(
    val contentId: Int,
    @param:JsonProperty(required = true)
    val tlxPlacementId: Int?,
    val keywords: List<Int>,
    val placementAdFormats: List<Int>
)

请注意,可为空的字段被标记为必填字段。这样,用户就必须显式地将其包含在请求正文中。

ia2d9nvy

ia2d9nvy6#

您可以控制空值或null值,如下所示

public class Person{

    @JsonInclude(JsonInclude.Include.NON_NULL) 
    private BigDecimal salary;  // this will make sure salary can't be null or empty//

    private String phone;   //allow phone Number to be empty
    // same logic for other fields
}

i)当您更新重量并删除电话号码时,要求客户发送需要更新的字段以及记录标识符,即本例中的id

{
   "id" : 2,
   "weight" :78,
   "phone" : null  
}

ii)由于您要将薪金作为必填字段添加一列,因此客户应了解这一点..可能您需要重新设计合同

相关问题