RestTemplate: Can not deserialize instance of OBJECT out of START_OBJECT token - java

I'm trying out the RestTemplate stuff from spring. I'm trying to read in this JSON data: JSON Data. The data is a a key value pair in which the key is "geonames" and the value is an array of "geoname" objects.
I have a Geoname class to handle the input. This class also has getters and setters in it. I then have an app class that just runs a main method to invoke a RestTemplate object:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Geoname {
private String name;
private long lat;
private long lng;
private String countrycode;
}
App.java
public class App
{
public static void main( String[] args )
{
String jsonUrl = "http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo";
RestTemplate template = new RestTemplate();
ResponseEntity<Geoname[]> entity = template.getForEntity(jsonUrl, Geoname[].class);
List<Geoname> data = Arrays.asList(entity.getBody());
System.out.print("Success!");
}
}
This is my error output:
Exception in thread "main" org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of com.declan.Geoname[] out of START_OBJECT token
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#54fc3ac5; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.declan.Geoname[] out of START_OBJECT token
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#54fc3ac5; line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:208)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:200)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:96)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:812)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:796)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:576)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:529)
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:261)
at com.declan.App.main(App.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.declan.Geoname[] out of START_OBJECT token
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream#54fc3ac5; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:835)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:831)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.handleNonArray(ObjectArrayDeserializer.java:232)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:139)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:17)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3560)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2660)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:205)
... 13 more
I've tried this setup, i've tried using getForObject like on the spring documentation, I've tried searching here on stackoverflow and using the common answer of mapping to a list. I've even tried creating a Geonames class that contained just an array or Geoname objects but that didn't work either...Same error all the time. Perhaps, I'm not reading the Json correctly but if someone could lend me a pair of eyes I'd be grateful. :D
Cheers!
EDIT
Okay, I now have this new class and it now makes the GET request for the JSON data. However, after a debug, the ResponseEntity body has the array set to null. Do I need to instantiate the array in Geonames manually?
#JsonIgnoreProperties(ignoreUnknown = true)
public class Geonames {
#JsonProperty("geonames")
Geoname[] geonames;
public void setGeonames(Geonames[] geonames) {
this.geonames = geonames;
}
public void getGeonames() {
return geonames;
}
}

Resolved. Turns out that the null was because the JSON link had a max hits per hour limit. By creating my own account on the site, the api gave me my own limits. So the response body then populated with data.

Related

Issue with serializing JSON from a rest call

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.

HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `int` from String

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;
}

Map nested json to a pojo with raw json value

I have a nested json pojo where the nested part of json is tagged with #JsonRawValue. I am trying it to map with rest template, but I am getting the error
JSON parse error: Cannot deserialize instance of java.lang.String out of START_OBJECT token;
The nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException.
This is what my response object looks like:
import com.fasterxml.jackson.annotation.JsonRawValue;
public class ResponseDTO {
private String Id;
private String text;
#JsonRawValue
private String explanation;
//getters and setters;
}
where explanation is a json mapped to a string. This works fine with postman, swagger, and I see the explanation as json in the response.
But when I am testing it using Rest Template:
ResponseEntity<ResponseDTO> resonseEntity = restTemplate.exchange(URI, HttpMethod.POST, requestEntity, ResponseDTO.class);
I see this exception:
org.springframework.web.client.RestClientException: Error while extracting
response for type [class com.**.ResponseDTO] and content type
[application/json;charset=utf-8]; nested exception is
org.springframework.http.converter.HttpMessageNotReadableException: JSON
parse error: Cannot deserialize instance of java.lang.String out of
START_OBJECT token; nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 604] (through
reference chain: com.****.ResponseDTO["explanation"])
Jackson is telling you that it can't insert an Object (in the error log) inside a String.
The #JsonRawValue is used during serialization of objects to JSON format. It is a way to indicate that the String field is to be sent as-is. In other words, the purpose is to tell Jackson that the String is a valid JSON and should be sent without escaping or quoting.
What you can do instead is provide Jackson with a custom method for it to set the field value. Using JsonNode as the argument will force Jackson to pass the "raw" value. From there you can get the string representation:
public class ResponseDTO {
private String Id;
private String text;
private String explanation;
//getters and setters;
#JsonProperty("explanation")
private void unpackExplanation(JsonNode explanation) {
this.explanation = explanation.toString();
}
}

Java Spring JSON parse error: Cannot deserialize instance out of START_ARRAY token

I have a method with a restTemplate call like this:
restTemplate.getForObject(apiUrl ,Someclass.class);
Someclass.class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Imp implements Serializable {
#JsonProperty("Id")
private String Id;
#JsonProperty("ReportId")
private String ReportId;
#JsonProperty("Title")
private String Title;
#JsonProperty("Name")
private String Name;
#JsonProperty("Uri")
private String Uri;
}
The API returns an array, and the error i'm receiving is:
org.springframework.web.client.RestClientException: Error while extracting response for type [class ...] and content type [application/json;charset=utf-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of com... out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of com... out of START_ARRAY token
Which restTempalte method shoud i use to get proper api response?, or where is the problem?.thanks!
You said the API returns an array.
But your line of code restTemplate.getForObject(apiUrl ,Someclass.class);
will work only for a single Someclass object.
You should use new ParameterizedTypeReference<List<Someclass.class>> along with the exchange method.
Refer to the below link
Get list of JSON objects with Spring RestTemplate

JsonMappingException: Can not deserialize instance of java.lang.Integer out of START_OBJECT token

I wanted to write a small and simple REST service using Spring Boot.
Here is the REST service code:
#Async
#RequestMapping(value = "/getuser", method = POST, consumes = "application/json", produces = "application/json")
public #ResponseBody Record getRecord(#RequestBody Integer userId) {
Record result = null;
// Omitted logic
return result;
}
The JSON object I sent is the following:
{
"userId": 3
}
And here is the exception I got:
WARN 964 --- [ XNIO-2 task-7]
.w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP
message:
org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document: Can not deserialize instance of
java.lang.Integer out of START_OBJECT token at [Source:
java.io.PushbackInputStream#12e7333c; line: 1, column: 1]; nested
exception is com.fasterxml.jackson.databind.JsonMappingException: Can
not deserialize instance of java.lang.Integer out of START_OBJECT
token at [Source: java.io.PushbackInputStream#12e7333c; line: 1,
column: 1]
Obviously Jackson can not deserialize the passed JSON into an Integer. If you insist to send a JSON representation of a User through the request body, you should encapsulate the userId in another bean like the following:
public class User {
private Integer userId;
// getters and setters
}
Then use that bean as your handler method argument:
#RequestMapping(...)
public #ResponseBody Record getRecord(#RequestBody User user) { ... }
If you don't like the overhead of creating another bean, you could pass the userId as part of Path Variable, e.g. /getuser/15. In order to do that:
#RequestMapping(value = "/getuser/{userId}", method = POST, produces = "application/json")
public #ResponseBody Record getRecord(#PathVariable Integer userId) { ... }
Since you no longer send a JSON in the request body, you should remove that consumes attribute.
Perhaps you are trying to send a request with JSON text in its body from a Postman client or something similar like this:
{
"userId": 3
}
This cannot be deserialized by Jackson since this is not an Integer (it seems to be, but it isn't). An Integer object from java.lang Integer is a little more complex.
For your Postman request to work, simply put (without curly braces { }):
3

Categories

Resources