Spring POST 400 Bad Request Postman when changing class from 2 to 1 parameter

别来无恙 提交于 2020-06-28 04:04:13

问题


I have following code in my @RestController class:

@RequestMapping("api/")
@RestController
public class RecommendationsController {
@PostMapping(path = "cart")
    public List<RecommendationDTO> getCartRecommendations(@NonNull @RequestBody List<CartItemDTO> cart){
        System.out.println(cart);
        return null;
    }
}

This is the code in my CartItemDTO class:

public class CartItemDTO {

    private String productId;
    private Double quantity;


    public CartItemDTO(String productId, Double quantity) {
        this.productId = productId;
        this.quantity = quantity;
    }

    public String getProductId() {
        return productId;
    }

    public Double getQuantity(){
        return quantity;
}

And this is the request that I send with postman:

[
    {
        "productId": "20000010",
        "quantity": 5.0;
    },
    {
        "productId": "20000011",
        "quantity": 7.0;
    }
]

This is working but when I change my code like following, I get Bad Request: bad syntax

public class CartItemDTO {

    private String productId;


    public CartItemDTO(String productId) {
        this.productId = productId;
    }

    public String getProductId() {
        return productId;
    }
}

with request:

[
    {
        "productId": "20000010"
    },
    {
        "productId": "20000011"
    }
]

Someone any idea what could be wrong?


回答1:


The main issue is Jackson unable to construct instance of a DTO.
Two ways to solve this issue:
1. Specify the default constructor:
- when you specify parameterized constructor, then the Java compiler will not add default constructor.

Now your 1st request:

curl --location --request POST 'localhost:8080/cart' \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "productId": "20000010",
        "quantity": 5.0
    },
    {
        "productId": "20000011",
        "quantity": 7.0
    }
]'

Throws error:

    "timestamp": "2020-04-27T12:08:28.497+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Type definition error: [simple type, class hello.dto.CartItemDTO]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `hello.dto.CartItemDTO` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (PushbackInputStream); line: 3, column: 9] (through reference chain: java.util.ArrayList[0])",
    "path": "/cart"

on adding default constructor to DTO, everything works fine as expected.

public class CartItemDTO {

    private String productId;
    private Double quantity;

    public CartItemDTO() {
    }

    public CartItemDTO(String productId, Double quantity) {
        this.productId = productId;
        this.quantity = quantity;
    }

    public String getProductId() {
        return productId;
    }

    public Double getQuantity() {
        return quantity;
    }
}

Since OP does not have RecommendationDTO object, adding just System.out.println as output:

[hello.dto.CartItemDTO@145e35d6, hello.dto.CartItemDTO@25df553f]
  1. Only ProductId in DTO
public class CartItemDTO {
    private String productId;

    public CartItemDTO() {
    }

    public CartItemDTO(String productId, Double quantity) {
        this.productId = productId;
    }

    public String getProductId() {
        return productId;
    }
}

Request:

curl --location --request POST 'localhost:8080/cart' \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "productId": "20000010"
    },
    {
        "productId": "20000011"
    }
]'

output:

[hello.dto.CartItemDTO@42ad1f23, hello.dto.CartItemDTO@777d8eb3]
  1. Solution Two: Need to instruct Jackson to create an dto object using a constructor with instance fields as below:

change DTO to

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class CartItemDTO {

    private String productId;
    private Double quantity;

    @JsonCreator
    public CartItemDTO(@JsonProperty(value = "productId", required = true) String productId,
                       @JsonProperty(value = "quantity", required = true) Double quantity) {
        this.productId = productId;
        this.quantity = quantity;
    }

    public String getProductId() {
        return productId;
    }

    public Double getQuantity() {
        return quantity;
    }
}

OR with only productId

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class CartItemDTO {

    private String productId;

    @JsonCreator
    public CartItemDTO(@JsonProperty(value = "productId", required = true) String productId) {
        this.productId = productId;
    }

    public String getProductId() {
        return productId;
    }
}



回答2:


There seems to be an issue with fasterxml jackson, when it tries to auto-guess the JsonCreator when the constructor only has 1 argument.

The best solution that I found, is to add the @JsonCreator annotation to the constructor, so jackson won't have to guess which creator should it use.

Like so:

import com.fasterxml.jackson.annotation.JsonCreator;

public class CartItemDTO {

    private String productId;

    @JsonCreator
    public CartItemDTO( String productId) {
        this.productId = productId;
    }

    public String getProductId() {
        return productId;
    }

}

Update:

Apparently this is a known limitation in fasterxml jackson: https://github.com/FasterXML/jackson-modules-java8/issues/8



来源:https://stackoverflow.com/questions/61455098/spring-post-400-bad-request-postman-when-changing-class-from-2-to-1-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!