Newbie developer here. I am trying to make a call to a public API. The API receives the name of a drink as a string and returns information and recipe for that name. The response from the API looks like this:
{
"drinks":[
{
"id": ...
"name": ...
"recipe": ...
"category": ...
"alcoholic": ...
... many other fields ...
},
{
...
}
...
]
}
I am only interested in name, recipe and category. I have a domain class for this purpose that looks like this
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class Drink {
#JsonProperty("name")
private String name;
#JsonProperty("category")
private String category;
#JsonProperty("recipe")
private String recipe;
}
I also implemented a client to call the endpoint using restTemplate. Here is the call that client makes:
ResponseEntity<List<Drink>> response = restTemplate.exchange(
url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Drink>>() {
});
My goal is to call the API, get the response and only the fields that I want and store it in a list of Drink. However when I try to run the app locally and make a call I am getting this error:
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.ArrayList<Drink>` from Object value (token `JsonToken.START_OBJECT`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList<Drink>` from Object value (token `JsonToken.START_OBJECT`)
When I use ResponseEntity<String> instead, it works but returns the whole json as a string, which does not seem like a good approach. How can I get this approach to work?
The problem is mismatch between json structure and object structure. The object you deserialize into must represent correctly the json. It's an object with a field drinks, which is an array of objects(drinks in your case). Correct java class would be:
public class Wrapper {
private List<Drink> drinks;
//getters and setters
#Override
public String toString() {
return "Wrapper{" +
"drinks=" + drinks +
'}';
}
}
Other option would be to write custom deserializer, which can extract drinks from the tree before deserializing directly into a list.]
Edit: Added toString() override for debugging purposes.
Related
Here is an example DTO
#Getter
#Setter
public class TestDto {
private Long id;
private String name;
private String sex;
}
Say I have this object stored on the server:
{"id":1, "name": "alex", "sex": "M"}
How can I send a request that only updates the "name" portion of the object?
Perhaps I could send this:
{"id":1, "name":"adam"}
Such that the object will change to this:
{"id":1, "name": "adam", "sex": "M"}
I also need the ability to set a field to null
(i.e. clear the contents of a field).
In this case I would like to send
{"id":1, "name":"adam", "sex":null}
To have the stored DTO change to
{"id":1, "name": "adam", "sex":null}
How can I do this using java, spring boot, etc.?
I know the way to use:
#PutMapping
public TestDto update(Map<String, Object>map){ ... }
but I also need to some validation such that if I pass
{"id":"1AA" ... } I get a serialization exception.
Ps.Find first step of this magic ->
1.Before path TestDto throu Rest - need to clear Type like this
Object body = testDto;
if will help you to get an Object with field what you want on server and then you'll be able to detect list of fieds to update
Instead of attempting to detect absent vs null value,
consider defining an update object that includes a list of fields to be updated.
Such an object might look like this:
#Getter
#Setter
public class UpdateObject
{
private long id; // ID of the object to be updated.
private TestDto updates; // an object that contains the new values for the fields.
private List<String> updateFields; // a list of fields to be updated.
}
Here is some Json
{
"id": 1,
"updates":
{
"name": "blem",
"sex": null
},
"updateFields": ["name", "sex"]
}
if i understood right you just send request to the server with different fields. With #ModelAttribute annotation you can send your body in json format.
if you send only one/two field or how you want {"id":1, "name":"adam"}, due to spring data jpa you can update your model in db. (in this case your field sex will be null and you need to create some manipulation for checking it kind of Mapstruct - convert your dto in other model plus checking null/not null fields).
Better create default value for sex field if you want to saving not M and not FM values. null bad practice, in the future it will be bad joke for you.
#Getter
public Enum Sex {
private MALE,
private FEMALE,
private DEFAULT
}
Ok guys finally fount how to do this
1.Client side - > path your testDto as Object, not as TestDto.class
Object payLoad = testDto;
template.postForObject("url", payload);
2.Server side - >
#RestController
#RequestMapping("/test")
public class TestController {
private final Map<Long, TestDto> cash = new HashMap<>();
private final ObjectMapper mapper = new ObjectMapper();
#PostMapping
public TestDto create(#RequestBody TestDto dto) {
return cash.computeIfAbsent(dto.getId(), v -> dto);
}
#PutMapping("/{id}")
public TestDto update(#PathVariable Long id, #RequestBody String json) throws JsonProcessingException {
val oldValue = cash.get(id);
mapper.readerForUpdating(oldValue).readValue(json);
return cash.put(oldValue.getId(), oldValue);
}
}
this hint let you update only field that client really changed
How to show conversion error along with other validation errors when creating REST API?
Spring Boot
Spring MVC
REST Controller
#Data
#AllArgsConstructor
class DTO
{
#NotNull
#Min(1)
private Integer id;
}
#RestController
class ExampleController
{
#PostMapping("/")
public void createEndpoint(#Valid #RequestBody DTO dto) {
return "{\"message\": \"OK\"}";
}
}
When I make request to this endpoint,
POST /
{
"id: "abrakadabra"
}
I would like to get something like this
{
"errors": {
"id": [
{ code: "invalid_format", message: "Field must contain only digits" }
]
}
}
What I actually get?
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `int` from String "abrakadabra": not a valid `int` value;
nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `int` from String "abrakadabra": not a valid `int` value
at [Source: (PushbackInputStream); line: 2, column: 24] (through reference chain: com.mylid.back.dtos.CreateCompanyDto["pricing_plan_id"])]
I know that I can create custom annotation for validation.
BUT the problem is that the process does not reach validation. It fails on deserialization step.
[Possible way]
There is one dirty trick, to change Integer type in DTO to String. Create custom annotation to check if String contains only digits. Then manually map this String from DTO to Integer in Entity. Then save to database.
What are other ways how to solve this problem?
It is very weird, that there is a few topics on Google and StackOverFlow for this particular problem. And on almost every page the accepted answer is "it is not our problem, API clients should pass integer".
In PHP we can easily do it with 'integer' validator, almost every framework has it, and it will not prevent other fields from validation if I pass "abracadabra" instead of "1".
What about implementing a custom type?
For example (the code below is crap, missing null handling and so on - just wanted to show you the idea)
public class DTOId {
final Integer value;
private DTOId(String value) {
Assert.hasText(value, "Value for DTOId should not not be null or empty!");
// Validation that it is a integer, min value and so on
this.value = Integer.valueOf(value);
}
#JsonCreator
public static DTOId of(final String value) {
return new DTOId(value);
}
#JsonValue
public String toString() {
return value.toString();
}
}
Then you could use
#Data
#AllArgsConstructor
class DTO
{
#NotNull
private DTOId id;
}
I have a simple Spring Boot project in which a scheduler periodically consumes a RESTful API and converts the incoming JSON file.
The JSON file is actually an array of Objects with some Keys and Values:
[
{"CoID":1,"CoName":"کشاورزی و دامپروری مگسال","CoNameEnglish":"MagsalAgriculture & Animal Husbandry Co.","CompanySymbol":"MAGS","CoTSESymbol":"زمگسا","GroupID":1,"GroupName":"كشاورزی و دامپروری","IndustryID":1,"IndustryName":"كشاورزی، دامپروری و خدمات وابسته به آن","InstCode":"5054819322815158","TseCIsinCode":"IRO1MAGS0006","TseSIsinCode":"IRO1MAGS0001","MarketID":1,"MarketName":"بورس"},
{"CoID":2,"CoName":"ذغالسنگ نگین طبس","CoNameEnglish":"Negin Tabas Lignite Co.","CompanySymbol":"TBAS","CoTSESymbol":"کطبس","GroupID":2,"GroupName":"استخراج و انبار ذغال سنگ سخت","IndustryID":2,"IndustryName":"استخراج ذغال سنگ","InstCode":"8977369674477111","TseCIsinCode":"IRO1TBAS0004","TseSIsinCode":"IRO1TBAS0001","MarketID":1,"MarketName":"بورس"},{"CoID":3,"CoName":"معدنی و صنعتی چادرملو","CoNameEnglish":"Chadormalu Mining & Industrial Co.","CompanySymbol":"CHML","CoTSESymbol":"کچاد","GroupID":3,"GroupName":"استخراج سنگ معدن های فلزی آهنی","IndustryID":3,"IndustryName":"استخراج كانه های فلزی","InstCode":"18027801615184692","TseCIsinCode":"IRO1CHML0000","TseSIsinCode":"IRO1CHML0001","MarketID":1,"MarketName":"بورس"}
...
]
I have a class called Company with similar fields to one of objects in the array within the JSON file:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Company {
private int CoID;
private String CoName;
private String CoNameEnglish;
private String CompanySymbl;
private String CoTSESymbl;
private int GroupID;
private String GroupName;
private int IndustryID;
private String IndustryName;
private String IndustryCode;
private String TseCIsinCode;
private String TseSIsinCode;
private int MarketID;
private String MarketName;
// And proper getters, setters and constructor //
I also created a wrapping class called CompanyList:
public class CompanyList {
private ArrayList<Company> companyList;
public ArrayList<Company> getCompanyList() {
return companyList;
}
public void setCompanyList(ArrayList<Company> companyList) {
this.companyList = companyList;
}
public CompanyList() {
}
#Override
public String toString() {
return "CompanyList [companyList=" + companyList + "]";
}
}
I have tried three different ways to fulfill this requirement:
First:
Object[] forNow = restTemplate.getForObject("somewhere", Object[].class);
List<Object> cp= Arrays.asList(forNow);
This one works properly.
Second:
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Company>> response = restTemplate.exchange(
"somewhere",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Company>>(){});
List<Company> companies = response.getBody();
log.info(companies.toString());
This one is compiled successfully but returns null and 0 in all fields.
Third:
CompanyList cp = restTemplate.getForObject("somewhere", CompanyList.class);
log.info(cp.getCompanyList().toString());
This one raises an exception:
Error while extracting response for type [class ir.pisys.rest.CompanyList] and content type [application/json;charset=utf-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of ir.pisys.rest.CompanyList out of START_ARRAY token;
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of ir.pisys.rest.CompanyList out of START_ARRAY token
So I have some questions here:
1- Is the first approach an optimized one? (Compared to others)
2- How can I fix the two other approaches?
The second and third approaches should work fine.
You need to check your json response structure.
You could use following jsons for tests (they work with your code):
Second approach:
[{"tseCIsinCode":null,"tseSIsinCode":null,"coName":"n1","industryID":0,"coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryName":null,"industryCode":null,"marketID":0,"marketName":null},{"tseCIsinCode":null,"tseSIsinCode":null,"coName":"n2","industryID":0,"coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryName":null,"industryCode":null,"marketID":0,"marketName":null}]
Third:
{"companyList":[{"coName":"n1","coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryID":0,"industryName":null,"industryCode":null,"tseCIsinCode":null,"tseSIsinCode":null,"marketID":0,"marketName":null},{"coName":"n2","coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryID":0,"industryName":null,"industryCode":null,"tseCIsinCode":null,"tseSIsinCode":null,"marketID":0,"marketName":null}]}
Update:
Second approach fix:
Change your json fields name - "CoName" -> "coName", "CoID" -> "coID" and so on. After that changes it will work pirfectly.
Third approach fix:
Wrap your json with "{\"companyList\":[...]
And change fields name as for second approach
Second Update
If you can't change json from response. You could use mapping in your Company class
#JsonProperty("CoName")
private String CoName;
Need to map multiple types of JSON responses to a single POJO so that I can compare the different objects to provide insight about the differences.
I had tried mapping the first response to the POJO and parsed the second response to populate the defined POJO:
class XXX {
#JsonProperty("accountHolder")
private String accountHolder;
#JsonProperty("routingNumber")
private String routingNumber;
#JsonProperty("balance")
private List<Balance> balance;
#JsonProperty("accountName")
private String accountName;
#JsonProperty("bankTransferCodeType")
private String bankTransferCodeType;
#JsonProperty("individualInformation")
private IndividualInformation individualInformation;
#JsonProperty("acctType")
private String acctType;
#JsonProperty("transactionList")
private TransactionList transactionList;
#JsonProperty("accountNumber")
private String accountNumber;
#JsonProperty("uniqueId")
private String uniqueId;
#JsonProperty("bankNetID")
private String bankNetID;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
}
First response:
[
{
"ACCOUNT_NAME": "",
"ACCOUNT_NUMBER": "",
"AVAILABLE_BALANCE": null,
"CURRENT_BALANCE": "",
"FULL_ACCOUNT_NUMBER": null,
}
]
Second response:
"bankAccount": [
{
"accountName": "",
"accountNumber": "",
"routingNumber": "",
"fullAccountNumber": "",
"bankTransferCodeType": "",
"acctType": "",
"transactionList": {
"transaction": [
{
"amount": {
"curCode": "",
"content": ""
}
],
"oldestTxnDate": ""
},
"uniqueId":
}
}
Expecting a generic way to map the different structured JSON entities to single POJO.
How to map multiple JSON responses to a single Java POJO?
As both responses seem to be completely different from each other, with nothing in common, I would refrain from attempting to use a single class for reading both responses.
Expecting a generic way to map the different structured JSONs to single POJO.
You could parse both responses as a Map<String, Object> and then map the values to a common class.
You could create separated classes for mapping each response. It will allow you to decouple them and evolve them as you need. You also can use use mapping frameworks such as MapStruct for reducing the boilerplate code when mapping from one object to another.
It doesn’t seems to have any generic way. But you can do this:
Create multiple domain classes for each response type
Create a single standard domain class
Create mapper for each response class to map that to standard domain
class. You can use MapStruct reference here
I would suggest using Jackson Json Views. Here is an example for the same :
Example
public class Views {
public class Global {
}
public class Internal extends Global {
}
}
class XXX {
#JsonView(Views.Global.class)
#JsonProperty("accountHolder")
private String accountHolder;
#JsonView(Views.Internal.class)
#JsonProperty("routingNumber")
private String routingNumber;
}
Hope it helps.
What I did is I created a MyResponse model containing basically all response fields from the JSON response you expect to get.
MyResponse has c-tor or receiving these fields or setters allowing setting them.
Then I created some kind of service class MyService that can issue multiple requests and gets responses back.
Then you just do something like this in some kind of manager class or whatever you call it:
MyService mySer = new MyService();
MyResponse myRes = new MyResponse(
mySer.getDetails(),
mySer.getPicture(),
mySer.getSomethingElse()
);
These calls (getDetails, getPicture...) send requests to end point and return responses which are then just mapped into the the fields of MyResponse class constructor. This happens by the framework so MyResponse has annotations #XmlRootElement and #XmlAccessorType of type FIELD to ensure that happens.
If for whatever reason, you dont want to create response containing result of getPicture for example, you just assign null to that imput parameter.
I suggest to use #JsonProperty("") and #JsonAlias("").
class XXX {
#JsonAlias("accountName")
#JsonProperty("ACCOUNT_NAME")
private String name;
#JsonAlias("routingNumber")
#JsonProperty("ROUTING_NUMBER")
private String routing;}
I hope it helps.
I'm passing a JSON object from a PUT request to my server. The request itself works, however the fields in the JSON which have an underscore (snake_case) seem to bi ignored. The request outputs the received data to see what comes out, and the value with the underscore converts to camelCase, and doesn't get parsed. Here's the class:
Public User{
private int id;
private String name;
private int some_value;
}
The JSON object I pass to the PUT request:
{ "id":1, "name":John, "some_value":5 }
The PUT method only returns what MOXy caught in this case
#PUT
#Path("user")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public User addUser(User user){
return user;
}
And the output is:
{ "id":1, "name":John, "someValue":0 }
Notice how "some_value" changed to "someValue" and didn't get the actual value updated. Any idea on why this is happening?
MOXy follows Java Bean conventions by default, which suggest camel case. If you don't want to (or can't) use camel case, you can add an annotation to the field:
#XmlElement(name = "some_value")
private int some_value;
If you don't want to annotate all your fields, use an XMLNameTransformer.