Post request with springboot json format - java

I have a product table, I have a second option table. I cannot manage to create my options for the product at the same time as I create the product. I tried to create the options individually by creating an option table and a category join table. When I send the options in json format it doesn't work. I get the bad request error and in the console:
JSON parse error: Cannot construct instance of
com.pastrycertified.cda.dto.OptionsDto (although at least one
Creator exists): no String-argument constructor/factory method to
deserialize from String value('pie'); nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of com.pastrycertified.cda.dto.OptionsDto
(although at least one Creator exists): no String-argument
constructor/factory method to deserialize from String value
('pie') at [Source:
(org.springframework.util.StreamUtils$NonClosingInputStream); line: 2,
column: 19] (through reference chain:
java.util.LinkedHashMap["typeOption"])]
Thank you for help
data
{
"typeOption": "product",
"ingredients": {
"option1": "test",
"option2":"test1"
}
}
controller option
#RestController
#RequestMapping("/options")
#RequiredArgsConstructor
public class OptionsController {
private final OptionsService optionsService;
#PostMapping("/")
public void save(
#RequestBody Map<String, OptionsDto > options
) {
return ResponseEntity.ok(optionsService.save(options));
}
}
optionService
public interface OptionsService {
Options save(OptionsDto options);
}
optionServiceImpl
#Service
#RequiredArgsConstructor
public class OptionsServiceImpl implements OptionsService {
#Override
public Options save(OptionsDto options) {
Options option = OptionsDto.toEntity(options);
option.setTypeOption(option.getTypeOption());
option.setIngredients(option.getIngredients());
return option;
}
}
optionDto
#Getter
#Setter
#AllArgsConstructor
#Builder
public class OptionsDto {
private Integer id;
private String typeOption;
private String ingredients;
private String nameCategory;
private CategoryDto category;
public static OptionsDto fromEntity(Options options) {
return OptionsDto.builder()
.id(options.getId())
.typeOption(options.getTypeOption())
.ingredients(options.getIngredients())
.nameCategory(options.getCategory().getName())
.build();
}
public static Options toEntity(OptionsDto options) {
return Options.builder()
.id(options.getId())
.typeOption(options.getTypeOption())
.ingredients(options.getIngredients())
.build();
}
}

As Jens mentioned, you need a default constructor in the OptionDto class. Also, you must decide whether ingredients is a String or a Map.
In the controller, you are asking for a Map<> but what you pass in the JSON is not a map. Your controller must be asking for an OptionsDto and not a Map.

Related

Springboot BackEnd. How to receive an Object from FrontEnd(JSON type) that has an ARRAY in it

I am writing a fullstack Web selling clothes and have a problem in data transferring bw FE & BE.
My Vue application send an JSON object to Backend that look like this:
{
"customer":{"name": "abc", "phone":"01234"},
"cart":[
{"id":"1", "quantity":"2"},
{"id":"2", "quantity":"3"},
]
}
as my Vue code front End send this request:
let dataSend = { customer: this.customer, cart: this.cart, code: this.code };
await axios.post("http://localhost:3000/submitOrder", dataSend)
this.cart is an Array that hold id and quantity of each product [{id:1, quantity:2},{...}]
at the backend I write by Java springboot I write the Controller like this
#PostMapping(path="/submitOrder", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> submitNewOrder(#RequestBody Order newOrder) throws Exception {
boolean isSuccess = orderService.checkOrderAndConductReceipt(newOrder);
if(isSuccess)
return new ResponseEntity("Accept", HttpStatus.OK);
else
return new ResponseEntity("Error, denial", HttpStatus.CONFLICT);
}
my Order model look like:
#Data
public class Order {
//Customer info
private Customer customer;
//his/her cart
private Cart cart;
}
CART
public class Cart {
private List<ProductInCart> productList;
public List<ProductInCart> getProductList() {
return productList;
}
public void setProductList(List<ProductInCart> productList) {
this.productList = productList;
}
public Cart(List<ProductInCart> productList) {
this.productList = productList;
}
public Cart() {}
}
ProductInCart
#Data
public class ProductInCart {
private int id;
private int quantity;
}
But finally, my BackEnd cannot Parse the CART from Array to a List of Product.
The error look like this:
.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `com.shopClothes.BE.HW20.demo.Model.Cart` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.shopClothes.BE.HW20.demo.Model.Cart` from Array value (token `JsonToken.START_ARRAY`)
at [Source: (PushbackInputStream); line: 1, column: 109] (through reference chain: com.shopClothes.BE.HW20.demo.Model.Order["cart"])]
When I try to Json.stringify the Cart array before sending like this:
let dataSend = { customer: this.customer, cart: JSON.stringify(this.cart), code: this.code };
the Error turn to:
.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.shopClothes.BE.HW20.demo.Model.Cart` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('[{"id":2,"quantity":3},{"id":3,"quantity":2},{"id":1,"quantity":1},{"id":5,"quantity":1}]'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.shopClothes.BE.HW20.demo.Model.Cart` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('[{"id":2,"quantity":3},{"id":3,"quantity":2},{"id":1,"quantity":1},{"id":5,"quantity":1}]')
at [Source: (PushbackInputStream); line: 1, column: 109] (through reference chain: com.shopClothes.BE.HW20.demo.Model.Order["cart"])]
What can I do to fix this?
#Data
public class Order {
//Customer info
private Customer customer;
//his/her cart
private List<ProductInCart> cart;
}
You have to change the model you are getting as request body

Jackson MismatchedInputException (no String-argument constructor/factory method to deserialize from String value)

I am using SpringBoot 2.3.1-RELEASE and am trying to deserialize JSON string to a POJO containing list of objects but I keep running into this error:
Cannot construct instance of com.response.dto.RootDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('Meta')
at [Source: (String)""Meta":[{"DimensionName":"Version","DimensionId":"3b4860b9-b215-4192-bd7a-a76f377fc465","DimensionType":"Regular","Alias":"C0","AttributeId":"211d5-d91f-40ec-9668-20e0da2ae7b3","AttributeName":"Version Name","AttributeKey":"VersionKey"; line: 1, column: 1]
This is what my JSON string looks like (but with escape chars in eclipse):
{"Meta":[{"DimensionName":"Version", "DimensionId":"3b4860b9-b215-4192-bd7a-a76f377fc465, "DimensionType":"Regular","Alias":"C0","AttributeId":"211b33d5-d91f-40ec-9668-20e0da2ae7b3","AttributeName":"Version Name","AttributeKey":"VersionKey"}]}.
Here is the class I want to deserialize it to:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
public class RootDTO
{
#JsonProperty("Meta")
private List<MetaDTO> Meta;
}
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
public class MetaDTO
{
#JsonProperty("DimensionName")
private String DimensionName;
#JsonProperty("AttributeId")
private String AttributeId;
#JsonProperty("AttributeName")
private String AttributeName;
#JsonProperty("Name")
private String Name;
#JsonProperty("Alias")
private String Alias;
}
This is the code that blows up when trying to read the value:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readValue(jsonFormattedString, RootDTO.class));
I only see this issue while running Junit (version : 4.12). I see jackson-databind-2.11.0, spring-test-5.2.7.RELEASE in the stack trace. However, I debug using a call from browser or postman it works fine. I am not sure why it is looking for the string Meta when I have specified it to be a list. What could be causing this issue? Any suggestions?
Edit: Turns out that the string which was being supplied to the ObjectMapper isn't the correct one. There is this line of code
String jsonFormattedString = responseEntity.getBody().substring(1, responseEntity.getBody().lastIndexOf("\"")).replaceAll("\\\\", ""); which makes my mocked string invalid. I'll need to figure out why we are doing this though.
Change the first letter's of variable to lowwer case. And remove the JsonProperty.
As below. And auto generate the setter and getter.
private String DimensionName;
private String attributeId;
private String attributeName;
private String name;
public void setName(String name){
this.name=name;
}
.........
.........
//All setter getter
Add #JsonGetter("Meta") each getter method.
For example as below.
#JsonGetter("Meta")
public List<Meta> getMeta(){
return meta;
}

ObjectMapper's readValue method giving MismatchedInputException

I'm using the objectMapper to first serialise and deserialise an object.
I'm serialising the object here:
byte[] data = objectMapper.writeValueAsBytes(service.getServiceInfo());
client.create().withMode(CreateMode.EPHEMERAL).forPath(service.getLeaderPath(), data);
The getServiceInfo is of type: ServiceInfo.class
Here is how I'm trying to deserialise the data:
byte[] data = client.getData().forPath(service.getLeaderPath());
T serviceInfo = objectMapper.readValue(data, typeServiceInfo);
Here T is of type ServiceInfo.class and typeServiceInfo is it's class variable Class<T>
This is the ServiceInfo.class:
#Data
public class ServiceInfo {
private String name;
public ServiceInfo(String name) {
this.name = name;
}
}
Now when I run my code, I obtain a MismatchedInputException
This is the error trace I obtained:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.nutanix.categories.beans.curator.ServiceInfo` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"{"name":"2816c308-5277-4b23-bdd6-64d6f3513e16"}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1429)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1059)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3266)
at com.nutanix.categories.curators.ServiceLeaderLatch.start(ServiceLeaderLatch.java:74)
What am I doing wrong here? Any help is deeply appreciated.
PS: Please mention if I have to submit additional information regarding my query in the comments
There are two ways to solve it:
Modify the ServiceInfo bean itself and remove the constructor. Although, it will require you to update all it's declarations.
#Data
public class ServiceInfo {
private String name;
}
Or, add #JsonCreator annotation to the bean
#Data
public class ServiceInfo {
private String name;
#JsonCreator
public ServiceInfo(#JsonProperty("name") String name) {
this.name = name;
}
}
If you don't like #JsonProperty annotation, you can customize ObjectMapper
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-paranamer</artifactId>
<version>${some-version}</version>
</dependency>
And then register the module:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModules(new ParanamerModule());
Another option is leave everything as it is and add a default constructor
#Data
public class ServiceInfo {
private String name;
public ServiceInfo() {
}
public ServiceInfo(String name) {
this.name = name;
}
}

Spring #RequestBody Bad Request

I am using POSTMAN to send request's.
Configured all right there:
Content-Type application/json
Request Type POST
and in Body I have the following:
{
"token":"EAACEdEose0cBAFLc4blCYmmetEMBEZCiQQZAuvz6DlxFt0yPZCksZBWv09B71aZCeDH9zOPyzM44GRl8WA56uFZBmOiUMmSlk3USfOwRdwmXDnhlPArttzjjLzUXaTReHzHZC7ZCcFzZADwGBLRUHvTb17nagRDLpZBysdxZBxuJuojlgZDZD"
}
I POST this to a controller and get a 400 BAD Request Error:
{
"timestamp": 1475061564742,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Could not read document: Can not construct instance of at.wastun.controller.WTUserController$RegisterBody: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)\n at [Source: java.io.PushbackInputStream#a646ac6; line: 2, column: 2]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of at.wastun.controller.WTUserController$RegisterBody: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)\n at [Source: java.io.PushbackInputStream#a646ac6; line: 2, column: 2]",
"path": "/users/register"
}
The class and the Controller looks like:
#Controller
#RequestMapping("/users")
#ResponseBody
public class WTUserController {
private class RegisterBody{
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public RegisterBody(String fbToken){
this.token = fbToken;
}
}
#RequestMapping(value="/register", method = RequestMethod.POST)
public String method0(#RequestBody RegisterBody body){
return body.getToken();
}
}
Your RegisterBody class is private. So nothing outside of the code in your class can create new instances of it. Try:
public static class RegisterBody {
Or, better still, move RegisterBody into its own java file.
The error itself tells you about origin of the problem: no suitable constructor found. So you need to add default constructor into RegisterBody class. Also I'm not sure that making this class private good idea.
It was a combination of both answers #Andremoniy and #Mr Spoon
Worked after I made the class
public static class RegisterBody {
and removed the constructor and made it to an default constructor.
public static class RegisterBody{
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
Works now thanks
When Spring tries to resolve Body it creates the object with a default constructor ClassName() and then tries to fill the fields with setters. So if you want to resolve RequestBody into RegisterBody it should look like below:
private class RegisterBody{
private String token;
public RegisterBody() {};
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public RegisterBody(String fbToken){
this.token = fbToken;
}
}
Apart from that remember, that Spring implements MVC for some reason. Put the classes to Utils or Misc package if you can't classify it better but DON'T declare classes in Controllers!
Try to add an empty constructor for the class.
If you're using Lombok you can use the following annotations:
#Getter
#Setter
#AllArgsConstructor
// Must include this annotation since Spring instantiates the #ResponseBody type with no args.
#NoArgsConstructor
public class CustomRequestBody {
private String message;
}
#Slf4j
#RestController
public class ReactiveRestController {
#PostMapping("/api/test")
public Mono<CustomResponse> post(#RequestBody CustomRequestBody requestBody) {
log.info(requestBody.getMessage());
return Mono.just(new CustomResponse(200, "success"));
}
}

Spring MVC #RequestBody map Optional<Enum>

I have a rest controller with this method:
#RequestMapping(value = "", method = { RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<?> add(#Valid #RequestBody MyModel myModel, Errors errors) {
...
return new ResponseEntity<SomeObject>(someObject, HttpStatus.OK);
}
In MyModel has a field isMeetingOrSale that is enum (MeetingSaleFlag):
public enum MeetingSaleFlag {
MEETING("MEETING"),
SALE("SALE");
private final String name;
private MeetingSaleFlag(String s) { name = s; }
public boolean equalsName(String otherName) {
return (otherName == null) ? false : name.equals(otherName);
}
public String toString() { return this.name; }
}
and it can map a json that has a field "isMeetingOrSale" : "MEETING"
but the value in the json can be "isMeetingOrSale" : "" or completely missing, so in that case I want the field to be mapped to null. If I change the filed to be Optional<MeetingSaleFlag>
I got
Could not read JSON: Can not instantiate value of type [simple type,
class java.util.Optional<MeetingSaleFlag>] from String value
('MEETING'); no single-String constructor/factory method\\n at
[Source: java.io.PushbackInputStream#32b21158; line: 17, column: 18]
(through reference chain: MyModel[\"isMeetingOrSale\"]);
So the question is how can I map Optional enum from json?
Thanks to Sotirios Delimanolis's comment I was able to resolve the issue.
1) Add
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
as a dependency.
2) Reconfigure the Jackson mapper. Register:
#Bean
#Primary
public ObjectMapper jacksonObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
return mapper;
}
OR do this to register the jdk8 module
/**
* #return Jackson jdk8 module to be registered with every bean of type
* {#link ObjectMapper}
*/
#Bean
public Module jdk8JacksonModule() {
return new Jdk8Module();
}
Another way to customize Jackson is to add beans of type com.fasterxml.jackson.databind.Module to your context. They will be registered with every bean of type ObjectMapper, providing a global mechanism for contributing custom modules when you add new features to your application.
Doing this will only register the additional module and keep the built-in Jackson configuration provided by Spring Boot.
3) result
Now when the property is missing from the sent json, it's mapped to null
(This is not that great. I was expecting that it will give me an Optional and I will be able to use .isPresent()).
When it's an empty string ("isMeetingOrSale" : ""), Jackson returns an error:
Could not read JSON: Can not construct instance of
MyModel from String value '': value not
one of declared Enum instance names: [VAL1, VAL2]
which looks OK to me.
Useful links : Jackson jdk8 module, Spring MVC configure Jackson
This is an example from our codebase:
#NotNull // You probably don't want this
#JsonSerialize(using=CountrySerializer.class)
#JsonDeserialize(using=CountryDeserializer.class)
private CountryCode country;
where CountryCode is a complex enum (see nv-i18n) and these are the classes to (de)serialized from/to JSON:
public class CountrySerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(value.getAlpha3()); // Takes the Alpha3 code
}
public Class<CountryCode> handledType() { return CountryCode.class; }
}
and
public class CountryDeserializer extends JsonDeserializer<CountryCode> {
#Override
public CountryCode deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
// You can add here the check whether the field is empty/null
return CountryCode.getByCode(jp.getText());
}
}
You can easily replicate the same scenario using MeetingSaleFlag instead of CountryCode.

Categories

Resources