V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
coollight56
V2EX  ›  程序员

关于 Java spring web 接收 Content-Type=application/json 请求参数的问题

  •  
  •   coollight56 · 25 天前 · 2915 次点击

    请问 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 参数

    第 1 条附言  ·  25 天前

    补充一下 这个问题的前提是请求方发送的是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
    }
    

    这样方便很多哇,看来想要优雅就只能自己自定义注解实现了。

    第 2 条附言  ·  24 天前
    
    @PostMapping("/schemeUserList")
    public ApiResult<PageResult<?>> schemeUserList(@SingleParam @NotNull Long schemeId,
                                                   @ComboParam @Valid UserSearchParam userSearchParm,
                                                   @ComboParam @Valid PageParam pageParam)
            // TODO
    }
    
    
    46 条回复    2025-05-14 15:18:26 +08:00
    baolinliu442k
        1
    baolinliu442k  
       25 天前
    map
    hidemyself
        2
    hidemyself  
       25 天前
    @RequestBody Long taskId
    这种写法,期望前端怎么传给你?
    lisongeee
        3
    lisongeee  
       25 天前
    呃,你没理解 JsonToken.START_OBJECT , 这错误不是很明显吗?

    你的请求体不能是 {,你直接传 123 不就行了?
    lovedoing
        4
    lovedoing  
       25 天前
    RequestParam("taskId") 就行
    lisongeee
        5
    lisongeee  
       25 天前   ❤️ 4
    合法的 json 示例
    {}
    []
    123
    "str"
    null
    true
    flase
    lychee930224
        6
    lychee930224  
       25 天前
    userId 和 taskId 不能在一个对象里吗
    dode
        7
    dode  
       25 天前
    PostMapping 也可以接收 RequestParam 参数
    lovedoing
        8
    lovedoing  
       25 天前
    不过需要前端把参数当 param 传,而不是 body
    zerphyr
        9
    zerphyr  
       25 天前
    既然定义了 Content-Type 为 json,那客户端和服务端都应该使用 json 格式,不存在其他方式
    其次 json 格式有 ES6 解构,并不麻烦吧
    myderr
        10
    myderr  
       25 天前
    试试直接这样,如果是字符串类型,还得加双引号
    carrotliang
        11
    carrotliang  
       25 天前
    我理解你一个方法里接收的参数应该是确定的,新建一个 VO 对象,里面又有 userId 也有 taskId 。

    如果不确定就用 Map 接收,get("taskId")
    liumao
        12
    liumao  
       25 天前
    map 一把梭
    coollight56
        13
    coollight56  
    OP
       25 天前
    @myderr #10 谢谢 试了一下这样确实可以,但是我司前端一般不会这样传,都是 key value 的键值对,而且其中可能包含别的后端不要的键值对,可能用来做前端的某些判断逻辑,都一起传给后端了 T.T
    vincentWdp
        14
    vincentWdp  
       25 天前
    首先, http 传输的内容全都是字节, 请求体的编码在 header content-encoding 里, 大概率是 utf-8, 有可能是 ascii, gbk... 等.
    其次, 请求体的类型在 header content-type 里有写, 有的是 json, 有的是 stream, 有的是 form, 有的是 html...
    再次, spring 框架肯定是符合 http 协议标准的, 如果你用 spring,就得和它一样遵守 http 协议标准.
    最后, 再说你的疑惑, 如果前端不能改或者不想改, 你就和前端定好, 请求体用 application/json 类型, 然后后端用 map 接收.
    zbw0414
        15
    zbw0414  
       25 天前
    你这是查询请求(/info)吧, 如果是查询请求 应该用 @GetMapping, 请求参数放到 query_string 中,
    前端发起请求类似 /info?taskId=123
    后端 controller 类似
    @GetMapping("/info")
    public void info(@RequestParam Long taskId) {
    System.out.println("taskId:" + taskId);
    // TODO


    }
    zeww
        16
    zeww  
       25 天前
    可以试试 @PathVariable
    coollight56
        17
    coollight56  
    OP
       25 天前
    @carrotliang #11 谢谢 用 Map 确实可以解决,只不过可能还要转换不同参数的类型,如果能像 @RequestParam 一样在参数列表声明参数类型和参数名称,然后 spring web 自动转换那就更好,看来是只能自己封装了
    coollight56
        18
    coollight56  
    OP
       25 天前
    @lisongeee #5 谢谢,涨知识了哈哈,确实可以直接传值,不过这里的场景请求方是用的 json 对象例如这样
    {
    "key1":"value1",
    "key2":[],
    "key3":1231
    ...
    },所以我需要针对 json 对象做处理
    leion8310
        19
    leion8310  
       25 天前
    要用 POST + JSON ,就老老实实把 taskId 封转在 Class 里,如

    ```java
    public Class Task{
    String taskId;
    String taskName;
    ..
    }
    ```

    @RequestBody 注解只是帮你把实体对象反序列化成 JSON ,而不是帮你转类型...
    niubilewodev
        20
    niubilewodev  
       25 天前
    这样 @RequestBody 就有歧义了。
    xiaohupro
        21
    xiaohupro  
       25 天前
    最好定义一个对应的参数类,例如:xxxParam.java ,一般项目中我就会这样定义,方便后期维护,而且这样定义了之后 post 和 get 都能用
    ZeroDu
        22
    ZeroDu  
       25 天前
    spring 的做法是正确的。op 缺少一些理解。
    Kaiv2
        23
    Kaiv2  
       25 天前
    不推荐使用 Map ,定义一个对象,有 lombok 和 AI 定义一个对象花不了多少时间。代码更安全也更好维护。
    bitmin
        24
    bitmin  
       25 天前
    a = {
    "taskId":123
    ....
    }

    request body 是 a 而不是 taskId

    你想直接取 taskId 看看 @RequestBody 有没有参数设置,类似 python fastapi 里 task_id: int = Body(..., embed=True) 指定 embed=True

    @RequestBody 没有这样的功能可以自己自定义一个注解例如 @EmbedRequestBody ,如果用了文档生成的库或者插件,可能会不识别自定义的注解,还得去做兼容
    NickX
        25
    NickX  
       25 天前
    @RequestBody 就是用来接收 application/json 的类型,你非得让他接收表单,明显是用法问题。用 @RequestParam
    hailiang88
        26
    hailiang88  
       25 天前
    把 post 改成 get ,参数使用 @PathValue 的格式或许更符合 rest 规范吧
    coollight56
        27
    coollight56  
    OP
       25 天前
    @bitmin #24 是的 看了下别的人的处理方法都是自定义一个注解 ,文档生成确实就没法兼容了
    https://blog.csdn.net/llwutong/article/details/116273360
    ikas
        28
    ikas  
       25 天前
    创建一个专门用于接收 Id 的参数类型算了.
    record IdParam<T>(T id){}
    ikas
        29
    ikas  
       25 天前
    我们是统一了前端传递的参数名
    coollight56
        30
    coollight56  
    OP
       25 天前
    @ikas #29 嗯嗯 这也是一种方法 👍
    xuanbg
        31
    xuanbg  
       25 天前
    你要是觉得定义一个对象很麻烦,那也可以用万能的 Map 来接收的。只是 key 改了名字就比较麻烦。
    oneisall8955
        32
    oneisall8955  
       25 天前
    OP 刚刚入行? content type 的含义就是客户端和服务端数据交互的格式定义协议。json 代表客户端按照 json 格式传参,相对的,服务端就得按照 json 格式解析
    Hstar
        33
    Hstar  
       25 天前
    楼主脑洞有点大, 经验有点少. 建议你听大家的, 就把 taskId 封在一个 Class 里.
    然后你记得收藏一下这个帖子, 等五年十年后你再回来看自己的发言和思路, 有惊喜!
    iv8d
        34
    iv8d  
       25 天前 via Android
    你直接写数字到 data 就行了
    cenbiq
        35
    cenbiq  
       24 天前
    看了半天也没看懂 OP 的问题在哪,我想几乎所有语言/框架对 POST 请求的处理都是类似 fun info(queryParam1: Int, queryParam2: String?, body: InfoRequestBodyJsonMappingObject): InfoResponseContentJsonMappingObject 这种形式吧,应该不存在任何疑问?
    cenbiq
        36
    cenbiq  
       24 天前
    @cenbiq 依据 OP 的需求 InfoRequestBodyJsonMappingObject(taskId: Long, taskName: String, userId: Long) 这样存在任何疑问吗
    cenbiq
        37
    cenbiq  
       24 天前
    @cenbiq 如果你的框架支持,应该还能这么做 fun info(task: TaskPartOfInfoBodyObject, user: UserPartOfInfoBodyObject); 数据类型定义是 TaskPartOfInfoBodyObject(taskId: Long, taskName: String); UserPartOfInfoBodyObject(userId: Long, ...)。如果 OP 是想要从 body 的一个 json 内分出两组请求数据方便处理的话
    shangfabao
        38
    shangfabao  
       24 天前
    你要是按照参数方式获取,注解都不对啊,肯定取不到
    coollight56
        39
    coollight56  
    OP
       24 天前
    @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 来传参符不符合规范。确实不知道怎么样才规范,好像都是学习前人的代码风格,然后想在此基础上优化而已。
    coollight56
        40
    coollight56  
    OP
       24 天前
    @coollight56 #39 显示不友好 写到补充里了
    IFallowed
        41
    IFallowed  
       23 天前
    我权且不论为啥一个查询接口使用 post ,估计这也不是 op 能决定的,而是 op 公司的传统。
    根据 op 补充说明,请求发送方的数据格式不还是一个对象吗,为什么接收方要修改数据格式?我还没想到有什么场景需要这样做?
    IFallowed
        42
    IFallowed  
       23 天前
    另外,我看下来大家的回复也不是再说 json 传参符不符合规范啊? json 本就是现在主流的数据传输类型,和规范有什么关系?
    IFallowed
        43
    IFallowed  
       23 天前
    op 最后自定义了一个 @ComboParam ,你最终的处理不还是把请求参数封装在一个对象里面吗?
    发送方以对象格式封装参数发给你,你在参数解析器里再把请求参数解析拆分后,最后又封装到了对象里,这样有什么意义?就为了把 schemeId 单独拿出来?又或者像你在 13 楼所表述的:请求发送方发送的参数包含你不需要的数据?
    那解决思路不应该是再定义一个统一的前置的 Interceptor ,根据方法的入参去过滤掉请求体里不需要的数据吗?
    当然,更推荐的应该是在你们的前后端交互规范里确立:前端应在不同的业务场景调用不同或者相同的接口仅传递必要的参数,而不能一窝打包交给服务端
    IFallowed
        44
    IFallowed  
       23 天前
    最后,如果 op 的公司没有说必须使用 post,这里只要换成 get 请求方式,都不需要 op 再自定义注解,就能达到 op 的要求:
    @GetMapping("/schemeUserList")
    public ApiResult<PageResult<?>> schemeUserList(@NotNull Long schemeId,
    @Valid UserSearchParam userSearchParm,
    @Valid PageParam pageParam)
    // TODO
    }
    coollight56
        45
    coollight56  
    OP
       23 天前
    @IFallowed #41 并没有修改请求发送方的数据格式,而是从请求方的数据里( json 对象)提取我所需的参数,比如一个查询接口请求方发送的 json 对象为
    {
    "name": "",
    "startTime": 1746892800000,
    "endTime": 1746979200000,

    "pageSize": 10,
    "pageNumber": 1
    }
    可以看到里面有查询相关的参数,也有分页相关的参数,如果用 @RequestBody 需要定义一个完整的对象来接收全部参数,后端开发都知道分页这个功能是很独立的只需要页码和条数就可以,如果我能通过 @ComboParam PageParam 选择性接收分页相关的参数,那么就不需要在每个分页查询的对象上都定义页码和条数 封装出来的对象 PageParam 在整个系统共用
    IFallowed
        46
    IFallowed  
       23 天前
    @coollight56
    1. 推荐你能改成 get 请求,就改成 get 请求,是你想要的效果的最优解。
    2. 把分页参数使用父类或者泛型包装。
    3.分页参数不要放在 body ,改用 param 。post 不仅能接受请求体的数据,也能接受请求的 url 上的参数,使用这方式也能将分页参数和业务参数隔离。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1387 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 17:17 · PVG 01:17 · LAX 10:17 · JFK 13:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.