
请问 spring web 只能用 @RequestBody 来接受 json 格式参数吗? 而且好像必须是用对象来接收
比如下面的情况就会报错 json 参数为
{ "taskId":123 .... } controller 为
@PostMapping("/info") public void info(@RequestBody Long taskId) { System.out.println("taskId:" + taskId); // TODO } 错误提示
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.Long` from Object value (token `JsonToken.START_OBJECT`)] 但是只要把 taskId 包装到一个对象中就可以正常接收,然而这样会不会太麻烦了,例如我又要接收一个 userId 那又得创建一个对象,spring web 不考虑自己封装的情况下(因为感觉这种场景应该很多才对,可能是我没掌握)有什么办法可以像 @RequestParam (只要定义需要接受的参数类型和名称就可以)来接收 json 参数
补充一下 这个问题的前提是请求方发送的是json对象格式数据如下
{ "key1":"value1", "key2":[], "key3":1231 ... }, 看了大家的回答总结下,使用map对象获取是可以的如下
@PostMapping("/info") public void info(@RequestBody Map<String, Object> map) { Long taskId = null; try { taskId = Long.parseLong(map.get("taskId").toString()); } catch (Exception ignored) { } if (taskId != null) { System.out.println("taskId:" + taskId); } // TODO } 不过奇怪为什么spring web可以接受map,而不提供直接在参数列表声明类型和名称的方式如下
@PostMapping("/info") public void info(@RequestBody Long taskId) { if (taskId != null) { System.out.println("taskId:" + taskId); } // TODO } 这样方便很多哇,看来想要优雅就只能自己自定义注解实现了。
@PostMapping("/schemeUserList") public ApiResult<PageResult<?>> schemeUserList(@SingleParam @NotNull Long schemeId, @ComboParam @Valid UserSearchParam userSearchParm, @ComboParam @Valid PageParam pageParam) // TODO } 1 baolinliu442k 202 天前 map |
2 hidemyself 202 天前 @RequestBody Long taskId 这种写法,期望前端怎么传给你? |
3 lisongeee 202 天前 呃,你没理解 JsonToken.START_OBJECT , 这错误不是很明显吗? 你的请求体不能是 {,你直接传 123 不就行了? |
4 JsonNode 202 天前 RequestParam("taskId") 就行 |
5 lisongeee 202 天前 合法的 json 示例 {} [] 123 "str" null true flase |
6 lychee930224 202 天前 userId 和 taskId 不能在一个对象里吗 |
7 dode 202 天前 PostMapping 也可以接收 RequestParam 参数 |
8 JsonNode 202 天前 不过需要前端把参数当 param 传,而不是 body |
9 zerphyr 202 天前 既然定义了 Content-Type 为 json,那客户端和服务端都应该使用 json 格式,不存在其他方式 其次 json 格式有 ES6 解构,并不麻烦吧 |
10 myderr 202 天前 |
11 carrotliang 202 天前 我理解你一个方法里接收的参数应该是确定的,新建一个 VO 对象,里面又有 userId 也有 taskId 。 如果不确定就用 Map 接收,get("taskId") |
12 liumao 202 天前 map 一把梭 |
13 coollight56 OP @myderr #10 谢谢 试了一下这样确实可以,但是我司前端一般不会这样传,都是 key value 的键值对,而且其中可能包含别的后端不要的键值对,可能用来做前端的某些判断逻辑,都一起传给后端了 T.T |
14 vincentWdp 202 天前 首先, http 传输的内容全都是字节, 请求体的编码在 header content-encoding 里, 大概率是 utf-8, 有可能是 ascii, gbk... 等. 其次, 请求体的类型在 header content-type 里有写, 有的是 json, 有的是 stream, 有的是 form, 有的是 html... 再次, spring 框架肯定是符合 http 协议标准的, 如果你用 spring,就得和它一样遵守 http 协议标准. 最后, 再说你的疑惑, 如果前端不能改或者不想改, 你就和前端定好, 请求体用 application/json 类型, 然后后端用 map 接收. |
15 zbw0414 202 天前 你这是查询请求(/info)吧, 如果是查询请求 应该用 @GetMapping, 请求参数放到 query_string 中, 前端发起请求类似 /info?taskId=123 后端 controller 类似 @GetMapping("/info") public void info(@RequestParam Long taskId) { System.out.println("taskId:" + taskId); // TODO } |
16 zeww 202 天前 可以试试 @PathVariable |
17 coollight56 OP @carrotliang #11 谢谢 用 Map 确实可以解决,只不过可能还要转换不同参数的类型,如果能像 @RequestParam 一样在参数列表声明参数类型和参数名称,然后 spring web 自动转换那就更好,看来是只能自己封装了 |
18 coollight56 OP @lisongeee #5 谢谢,涨知识了哈哈,确实可以直接传值,不过这里的场景请求方是用的 json 对象例如这样 { "key1":"value1", "key2":[], "key3":1231 ... },所以我需要针对 json 对象做处理 |
19 leion8310 201 天前 要用 POST + JSON ,就老老实实把 taskId 封转在 Class 里,如 ```java public Class Task{ String taskId; String taskName; .. } ``` @RequestBody 注解只是帮你把实体对象反序列化成 JSON ,而不是帮你转类型... |
20 niubilewodev 201 天前 这样 @RequestBody 就有歧义了。 |
21 xiaohupro 201 天前 最好定义一个对应的参数类,例如:xxxParam.java ,一般项目中我就会这样定义,方便后期维护,而且这样定义了之后 post 和 get 都能用 |
22 ZeroDu 201 天前 spring 的做法是正确的。op 缺少一些理解。 |
23 Kaiv2 201 天前 不推荐使用 Map ,定义一个对象,有 lombok 和 AI 定义一个对象花不了多少时间。代码更安全也更好维护。 |
24 bitmin 201 天前 a = { "taskId":123 .... } request body 是 a 而不是 taskId 你想直接取 taskId 看看 @RequestBody 有没有参数设置,类似 python fastapi 里 task_id: int = Body(..., embed=True) 指定 embed=True @RequestBody 没有这样的功能可以自己自定义一个注解例如 @EmbedRequestBody ,如果用了文档生成的库或者插件,可能会不识别自定义的注解,还得去做兼容 |
25 NickX 201 天前 @RequestBody 就是用来接收 application/json 的类型,你非得让他接收表单,明显是用法问题。用 @RequestParam 吧 |
26 hailiang88 201 天前 把 post 改成 get ,参数使用 @PathValue 的格式或许更符合 rest 规范吧 |
27 coollight56 OP @bitmin #24 是的 看了下别的人的处理方法都是自定义一个注解 ,文档生成确实就没法兼容了 https://blog.csdn.net/llwutong/article/details/116273360 |
28 ikas 201 天前 创建一个专门用于接收 Id 的参数类型算了. record IdParam<T>(T id){} |
29 ikas 201 天前 我们是统一了前端传递的参数名 |
30 coollight56 OP @ikas #29 嗯嗯 这也是一种方法 |
31 xuanbg 201 天前 你要是觉得定义一个对象很麻烦,那也可以用万能的 Map 来接收的。只是 key 改了名字就比较麻烦。 |
32 itechify PRO OP 刚刚入行? content type 的含义就是客户端和服务端数据交互的格式定义协议。json 代表客户端按照 json 格式传参,相对的,服务端就得按照 json 格式解析 |
33 Hstar 201 天前 楼主脑洞有点大, 经验有点少. 建议你听大家的, 就把 taskId 封在一个 Class 里. 然后你记得收藏一下这个帖子, 等五年十年后你再回来看自己的发言和思路, 有惊喜! |
34 iv8d 201 天前 via Android 你直接写数字到 data 就行了 |
35 cenbiq 201 天前 看了半天也没看懂 OP 的问题在哪,我想几乎所有语言/框架对 POST 请求的处理都是类似 fun info(queryParam1: Int, queryParam2: String?, body: InfoRequestBodyJsonMappingObject): InfoResponseContentJsonMappingObject 这种形式吧,应该不存在任何疑问? |
36 cenbiq 201 天前 @cenbiq 依据 OP 的需求 InfoRequestBodyJsonMappingObject(taskId: Long, taskName: String, userId: Long) 这样存在任何疑问吗 |
37 cenbiq 201 天前 @cenbiq 如果你的框架支持,应该还能这么做 fun info(task: TaskPartOfInfoBodyObject, user: UserPartOfInfoBodyObject); 数据类型定义是 TaskPartOfInfoBodyObject(taskId: Long, taskName: String); UserPartOfInfoBodyObject(userId: Long, ...)。如果 OP 是想要从 body 的一个 json 内分出两组请求数据方便处理的话 |
38 shangfabao 200 天前 你要是按照参数方式获取,注解都不对啊,肯定取不到 |
39 coollight56 OP @cenbiq #37 @cenbiq #36 是的 就是想要把单个 json 对象里的属性拆分开来在 controller 方法参数列表上接收,我这几天已经看到别的大佬的实现,就是自己写一个注解然后处理,spring web 也提供了这方面的扩展,同时也实现了组合参数的接收,如下 ```java public ApiResult<PageResult<?>> schemeUserList(@SingleParam @NotNull Long schemeId, @ComboParam @Valid UserSearchParam userSearchParam, @ComboParam @Valid PageParam pageParam) { // TODO } ``` 这样真的很方便,不过大家讨论的更多是全部用 json 来传参符不符合规范。确实不知道怎么样才规范,好像都是学习前人的代码风格,然后想在此基础上优化而已。 |
40 coollight56 OP @coollight56 #39 显示不友好 写到补充里了 |
41 IFallowed 200 天前 我权且不论为啥一个查询接口使用 post ,估计这也不是 op 能决定的,而是 op 公司的传统。 根据 op 补充说明,请求发送方的数据格式不还是一个对象吗,为什么接收方要修改数据格式?我还没想到有什么场景需要这样做? |
42 IFallowed 200 天前 另外,我看下来大家的回复也不是再说 json 传参符不符合规范啊? json 本就是现在主流的数据传输类型,和规范有什么关系? |
43 IFallowed 200 天前 op 最后自定义了一个 @ComboParam ,你最终的处理不还是把请求参数封装在一个对象里面吗? 发送方以对象格式封装参数发给你,你在参数解析器里再把请求参数解析拆分后,最后又封装到了对象里,这样有什么意义?就为了把 schemeId 单独拿出来?又或者像你在 13 楼所表述的:请求发送方发送的参数包含你不需要的数据? 那解决思路不应该是再定义一个统一的前置的 Interceptor ,根据方法的入参去过滤掉请求体里不需要的数据吗? 当然,更推荐的应该是在你们的前后端交互规范里确立:前端应在不同的业务场景调用不同或者相同的接口仅传递必要的参数,而不能一窝打包交给服务端 |
44 IFallowed 200 天前 最后,如果 op 的公司没有说必须使用 post,这里只要换成 get 请求方式,都不需要 op 再自定义注解,就能达到 op 的要求: @GetMapping("/schemeUserList") public ApiResult<PageResult<?>> schemeUserList(@NotNull Long schemeId, @Valid UserSearchParam userSearchParm, @Valid PageParam pageParam) // TODO } |
45 coollight56 OP @IFallowed #41 并没有修改请求发送方的数据格式,而是从请求方的数据里( json 对象)提取我所需的参数,比如一个查询接口请求方发送的 json 对象为 { "name": "", "startTime": 1746892800000, "endTime": 1746979200000, "pageSize": 10, "pageNumber": 1 } 可以看到里面有查询相关的参数,也有分页相关的参数,如果用 @RequestBody 需要定义一个完整的对象来接收全部参数,后端开发都知道分页这个功能是很独立的只需要页码和条数就可以,如果我能通过 @ComboParam PageParam 选择性接收分页相关的参数,那么就不需要在每个分页查询的对象上都定义页码和条数 封装出来的对象 PageParam 在整个系统共用 |
46 IFallowed 199 天前 @coollight56 1. 推荐你能改成 get 请求,就改成 get 请求,是你想要的效果的最优解。 2. 把分页参数使用父类或者泛型包装。 3.分页参数不要放在 body ,改用 param 。post 不仅能接受请求体的数据,也能接受请求的 url 上的参数,使用这方式也能将分页参数和业务参数隔离。 |