By inconsistent I mean that variable types can differ depending on the API response. So a named variable could be an Object, a List of Objects, or bizarrely even a String. I do not and cannot control the third-party API I'm consuming.
I'm using restTemplate.exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables), and the top-level responseType is consistent. It's in the child (and descendant) objects where the types may differ.
Am I stuck with pivoting to consuming the API response as a String, and do manual parsing? Or is there a way to handle the fact that the variable types might map differently (similar to how GSON supports custom serialisation / deserialisation).
Managed to find a way through this. I did have to read the API response as a String and take it from there. General steps:
restTemplate.exchange into String response body
Setup an ObjectMapper
Configure that to accept single values as arrays, and
empty strings as null objects
Read into the POJO of your choice
Now, this won't be ideal for everyone - in a perfect world you shouldn't have to relax the JSON parsing rules at all. This is all because I'm handling a very inconsistent API.
Rough code example is as-follows (as our internal stack is pretty complex, so I've had to drag bits out from classes here and there):
String exampleEndpoint = Constants.EXAMPLE_ENDPOINT;
ResponseEntity<String> responseEntity = restTemplate.exchange(uri.toString(), HttpMethod.GET, null, String.class);
String stringResponse = responseEntity.getBody();
ExamplePOJO examplePojo = null;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
try {
examplePojo = mapper.readValue(stringResponse, ExamplePOJO.class);
} catch (JsonProcessingException | NullPointerException ne) {
// JsonProcessingException is from readValue, NPE is to catch the string response
// being null in the event you don't want to let it bubble up further
logger.error(ne.getLocalizedMessage());
}
Related
I want to use the object mapper class in java to pick certain parts of an api request object to use in another request.
Request: {"access_instrument":{"id":"12345","additional_attributes":[1123],"instrument_lifecycle_reasons":[4423],"associated_instruments":[123],"links":[]},"types":[],"states":[],"metadata":[]}
I currently have:
ObjectMapper obMapper = new ObjectMapper();
obMapper.registerModule(new JavaTimeModule());
String requestStr1 = obMapper.writeValueAsString(searchCriteria.getAccessInstrument().getId());
String requestStr2 = obMapper.writeValueAsString(searchCriteria.getAccessInstrument().getInstrumentLifecycleReasons());
String requestStr3 = obMapper.writeValueAsString(searchCriteria.getAccessInstrument().getAssociatedInstruments());
Entity<String> requestEntity = Entity.entity(requestStr1+requestStr2+requestStr3, MediaType.APPLICATION_JSON_TYPE);
However I feel this will run into issues with the api validations due to formatting, and adding spacing manually, doesn't seem efficient. Is there a way I can pick apart the data I want from the request body to then send it in the new request?
Note: the requestEntity variable is the request payload that is used in the api call
I'm making a Post request from an app where i have the below
Set<Accounts> set = populateAccounts();
ResponseEntity<Map> responseMap =
restTemplate.postForEntity("http://localhost:8080/maptest", set, Map.class);
return responseMap.getBody();
And this request then returns a Map from responseMap.getBody();
And the below is the code where i receive the post request
#PostMapping("/maptest")
public ResponseEntity<Map> mapReturn(#RequestBody Set<Accounts> accounts) {
HashMap<String, Amount> map = new HashMap<String, String>();
map.put("account1", new Amount(BigDecimal.TEN));
map.put("account2", new Amount(BigDecimal.ZERO));
return ResponseEntity.ok(map);
}
Problem is, the Map that is returned does not have amounts as BigDecimal values, and these BigDecimal values are being automatically converted to an Integer when i see them in responseMap.getbody();
Please help me understand how i can maintain them as BigDecimal values.
Also, the actual code is slightly more elaborate than the above, but i wanted to keep it simple. I absolutely want to keep the values as BigDecimal, just not sure how.
You're probably using Jackson as the marchalling library for RestTemplate.
When you read the values the ObjectMapper reads numbers as ints/doubles etc.
You can easily set the behavior for deserialization using following configuration properties.
jackson-databind DeserializationFeature
USE_BIG_DECIMAL_FOR_FLOATS
Feature that determines whether JSON floating point numbers are to be deserialized into BigDecimals if only generic type description (either Object or Number, or within untyped Map or Collection context) is available.
USE_BIG_INTEGER_FOR_INTS
Feature that determines whether JSON integral (non-floating-point) numbers are to be deserialized into BigIntegers if only generic type description (either Object or Number, or within untyped Map or Collection context) is available.
To set it you can write as described here
ObjectReader r = objectMapper.reader(MyType.class);
// enable one feature, disable another
MyType value = r
.with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.without(DeserializationFeature.WRAP_EXCEPTIONS)
.readValue(source);
or just
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
OptionTransaction transaction = mapper.readValue(jsonString, OptionTransaction.class);
You can of course write your own number deserializers and use #JsonDeserialize(using = CustomNumberDeserializer.class) annotation.
Similar technique is possible in other marchalling libraries (like Gson)
I'm using Spring Boot and RestTemplate to GET data from an in-house service. The JSON I'm getting back as the response isn't being unmarshalled into my object and I'm unable to figure out why. (I'm consuming other similar REST APIs and those are working, so I've done this before.)
The code for making the request is standard stuff:
public ChecklistResponse getCurrentStatus(String studentId) {
RestTemplate restTemplate = createRestTemplate();
HttpHeaders httpHeaders = this.createHeaders();
String url = this.createItemStatusUrl(studentId);
ResponseEntity<ChecklistResponse> responseEntity = restTemplate.exchange(url, HttpMethod.GET,
new HttpEntity<>(httpHeaders), ChecklistResponse.class);
ChecklistResponse checklistResponse = responseEntity.getBody();
return checklistResponse;
}
The entire Object tree is large, so I can't really show it here. ChecklistResponse is simple enough, though:
public class ChecklistResponse {
private HttpStatus httpStatus;
private String responseType;
private Response response;
public ChecklistResponse() {
}
... getters/setters, equals, hashCode, toString ...
}
The start of the JSON response looks like this:
{
"httpStatus": {
"code": "200",
"description": "OK"
},
"responseType": "namespaceURI=\"http://bmeta.xxx.com/student/studentChecklistV0.xsd\" element=\"studentChecklist\"",
"response": {
"studentChecklist": {
"identifier": {
I set up interceptors to verify that I'm getting a response back. I ran it with the interceptors disabled, too, so that I know it's not consuming the response (though I'm using the interceptors successfully elsewhere and the object are properly unmarshalling.)
My question is, how can I debug this problem? I've tried enabling Jackson debugging, but that yields nothing. I've set breakpoints throughout the ChecklistResponse class, but it doesn't hit any of those.
Most likely you have forgot to add public parameterless constructor, add one like this:
public ChecklistResponse(){}
you should add these constructors to all classes in the object tree.
Another thing to look at is if the issue is a result of the values in the JSON object, ex, unescaped quotation etc .. try to make a unit test with a mocked object with trivial values but none empty/null ones and see if that test can pass or not, case sensitive issues sometimes cause this to fail as well, set break points in the getters/setters, these should be called in the process, make sure they are public as well as the classes themselves, final thing I can think of is the circular reference, make sure you don't have that as well in your object as this will cause issues.
Make sure that the model object ChecklistResponse correctly represents the map for the JSON object returned by the API you are testing with.
What if a XML webservice can respond with different xml structures? Eg an <OkResponse> and an <ErrorResponse>, having completely different fields?
ResponseEntity<Response> rsp = restTemplate
.postForEntity(url, new HttpEntity<>(xml, HEADERS), OkResponse.class);
Before sending the request, I don't know which type of response will come back. If I'm using OkResponse.class, I will get a ClassCastException if an ErrorResponse is returned.
How could I handle this?
The autogenerated beans are as follows:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({
OkResponse.class,
ErrorResponse.class
})
public class AbstractResponse {
}
Use String.class
ResponseEntity<String> rsp = restTemplate
.postForEntity(url, new HttpEntity<>(xml, HEADERS), String.class);
String responseBody = (String)rsp.getBody();
Object response=mapper.readValue(responseBody, Class.forName(responseClass))
Once response body is obtained. make use of service class that you want to map and convert it using jackson mapper .Made use of reflection since the entity passed can be different/dynamic
RestTemplate uses Jackson for JSON serialization, and it supports inherited types though the #JsonTypeInfo annotation. But it requires that all responses have a common 'type' property. If there is no common property that all responses share, then I think you need to use the String approach, and use String.contains() to find a unique property to determine which response type it is.
I have a response from URL which looks like:
{"seq":1,"id":"Test1","changes":[{"rev":"1-52f5cdf008ecfbadf621c2939af7bd80"}]}
{"seq":2,"id":"Test2","changes":[{"rev":"1-8ce403a89dc5e7cb4187a16941b3fb7d"}]}
{"seq":3,"id":"Test3","changes":[{"rev":"1-52as7ddfd8ecfbadf621c2939af7bd80"}]}
{"seq":4,"id":"Test4","changes":[{"rev":"1-6yy03a89dc5e7cb45677a16941b3fb7d"}]}
If the mapped object is String, then getting all the changes feed.
ResponseEntity<String> responseEntity = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, String.class);
Whereas, if I happen to use a custom Value object, somethings like:
public class KnChanges {
private long seq;
private String id;
private List changes;
with getter and setter methods, then I'm getting only the first doc change details. Even if the KnChanges[] (array) is used, only the first change is obtained.
Can you please help as to how the JSON list structure mentioned above can be mapped to an object?
Thanks
Harsha
Some people asked for a better answer with some explaination. So here it is:
As sujim mentioned: You need to
ParameterizedTypeReference<List<KnChanges>> responseType = new ParameterizedTypeReference<List<KnChanges>>() {};
ResponseEntity<List<KnChanges>> resp = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, responseType);
List<KnChanges> list = resp.getBody();
Explaination:
The last parameter of the exchange method call defines the class that gets instantiated when the response is received. The response data will then be mapped to the resulting object. So you need a List.class in fist place. Because you expect a JSON array. Now you need to define the type of the content of that List. Here Java's type erasure throws some stones in your way. As Java removes generic type information at compile-time, you can't just define the expected List to be a List<KnChanges>.class. "Luckily" there is a hack ;) And that hack is new ParameterizedTypeReference<List<KnChanges>>() {}. Provided that object the application is able to read the generic type information at runtime. And therefore is able to map the received data to your Java objects.
As a side-note: There a several implementations of that hack. It's commonly used for dependency injection or mapper systems, where type erasure can sometimes be an issue. Also Googles Guava offers an implementation. See the code for more information. There you can also learn how it's done, if you like.
ParameterizedTypeReference<List<KnChanges>> responseType = new ParameterizedTypeReference<List<KnChanges>>() {};
ResponseEntity<List<KnChanges>> resp = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, responseType);
List<KnChanges> list = resp.getBody();