I want deserialize a Object but json data is a string and the element of my object is not a string but a enum (of string).
My Object (want deserialize):
public enum ResponseStatus {
SUCCESS, ERROR, WARNING, NO_ACCESS
}
public class SessionResponse extends OperationResponse {
#ApiModelProperty(required = true, value = "")
private SessionItem item;
public SessionResponse() {
super();
}
public SessionItem getItem() {
return item;
}
public void setItem(SessionItem item) {
this.item = item;
}
}
public class OperationResponse {
#ApiModelProperty(required = true)
private ResponseStatus operationStatus;
private String operationMessage;
public ResponseStatus getOperationStatus() {
return operationStatus;
}
public void setOperationStatus(ResponseStatus operationStatus) {
this.operationStatus = operationStatus;
}
public String getOperationMessage() {
return operationMessage;
}
public void setOperationMessage(String operationMessage) {
this.operationMessage = operationMessage;
}
}
I use this web client:
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Content-Type", "application/json");
HttpEntity<Login> request = new HttpEntity<>(new Login("demo", "demo"), headers);
ResponseEntity<String> response = new TestRestTemplate().postForEntity("http://localhost:" + port + "/session", request, String.class);
System.out.println(response.getBody());
console:
{
"operationStatus" : "SUCCESS",
"operationMessage" : "Login Success",
"item" : {
"token" : "eyJhbGciOiJIUzI",
"userId" : "demo",
"firstName" : "Zinédine",
"lastName" : "Zidane",
"email" : "zzidane#mail.com",
"roles" : [ ]
}
}
But if I use
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Content-Type", "application/json");
HttpEntity<Login> request = new HttpEntity<>(new Login("demo", "demo"), headers);
ResponseEntity<SessionResponse> response = new TestRestTemplate().postForEntity("http://localhost:" + port + "/session", request, SessionResponse.class);
System.out.println(response.getBody());
trace:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.xxx.xxx.model.session.SessionResponse]
EDIT 1
if I add gson deserializer just after, is it OK
Gson gson = new Gson();
SessionResponse s = gson.fromJson(response.getBody(), SessionResponse.class);
System.out.println(s.getItem().getLastName());
is it possoble to use gson directly in TestRestTemplate().postForEntity ?
Related
Thank you for clicking here.
I have an JSON REST API (providing by Directus CMS). All API responses contains a json object with a "data" attribute containing what I want.
{
"data": {
"id": 1,
"status": "published",
"sort": null,
"user_created": "5a91c184-908d-465e-a7d5-4b648029bbe0",
"date_created": "2022-04-26T09:43:37.000Z",
"user_updated": "5a91c184-908d-465e-a7d5-4b648029bbe0",
"date_updated": "2022-05-30T14:23:50.000Z",
"Titre": "Réseaux Sociaux",
"Description": "Retrouvez les dernières news en direct sur nos réseaux sociaux!",
"Lien": "https://www.instagram.com/univlorraine/",
"ImageArrierePlan": "f23ffd53-7244-4439-a8cf-41bd0fd3aa72",
"Erreur_Bloc": null
}
}
This data attribute can be a object or a list of objects depending the request.
I have a Java Spring application with a service consuming the API. I'm using RestTemplate with exchange method.
public Object callAPI(String url, HttpMethod httpMethod, Object body, MultiValueMap<String, String> headers, Class<?> classe) {
final RestTemplate rt = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
rt.setRequestFactory(requestFactory);
final HttpEntity<?> request = new HttpEntity<>(body, headers);
final ResponseEntity<?> response = rt.exchange(url, httpMethod, request, classe);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
}
else return response.getStatusCode();
} catch (final Exception e) {
System.out.println(e);
return null;
}
}
In the exchange method I pass an existing class to directly link response data with the provided class.
The probleme is that I have this data attribute which prevents me from linking the data.
Does anyone have a solution to this probleme please?
----UPDATE----
Thanks to the response of AlbiKai, I created a generic Wrapper class :
public class Wrapper<T> {
private T data;
public void set(T data) {
this.data = data;
}
public T get() {
return data;
}
}
I then tried to put this Wrapper in the exchange :
public <classe> Object callAPI(String url, HttpMethod httpMethod, Object body, MultiValueMap<String, String> headers, Class<?> classe) {
final RestTemplate rt = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
rt.setRequestFactory(requestFactory);
final HttpEntity<?> request = new HttpEntity<>(body, headers);
final ResponseEntity<?> response = rt.exchange(url, httpMethod, request, Wrapper<classe>.class);
But I get the error "Cannot select from parameterized type" on the Wrapper :/
You can create a wrapper class that match the json response : an object with only one attribute named "data" type of desire final class (or a list) and use it in the exchange method.
public class wrapper {
YourClass data;
}
I gave up with the Wrapper etc...
I just pass a String class and work with it in my controllers to delete this "data" property and map the string with a class.
Service :
public String callAPI(String url, HttpMethod httpMethod, Object body, MultiValueMap<String, String> headers) {
final RestTemplate rt = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
rt.setRequestFactory(requestFactory);
final HttpEntity<?> request = new HttpEntity<>(body, headers);
final ResponseEntity<String> response = rt.exchange(url, httpMethod, request, String.class);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
}
else return response.getStatusCode().toString();
} catch (final Exception e) {
System.out.println(e);
return null;
}
}
One controller :
public List<BlocInformation> getBlocInformation() {
String url = "http://localhost:8055/items/bloc_information/?fields=*,Erreur_Bloc.*";
final RestAPIService blocService = new RestAPIService();
String response = blocService.callAPI(url, HttpMethod.GET, null, null);
if (response != null) {
String result = response.substring(8, response.length() - 1);
ObjectMapper mapper = new ObjectMapper();
List<BlocInformation> blocInformationList = null;
try {
blocInformationList = Arrays.asList(mapper.readValue(result, BlocInformation[].class));
} catch (IOException e) {
e.printStackTrace();
}
return blocInformationList;
}
return null;
}
Please help the beginner.
When sending a request to the requestBody json I get:
key: "{
"grant_type" : "client_credentials",
"client_id" : "OC_CLIENT_ID",
"client_secret" : "OC_CLIENT_SECRET"
}"
value: ""
and it is required that the requestBody looks like this
{
key:"grant_type"
value: "client_credentials"
}
{
key:"client_id"
value: "OC_CLIENT_ID"
}
{
key:"client_secret"
value: "OC_CLIENT_SECRET"
}
The server sent for some reason not a set of parameters, but simply stuck json into the name of the first parameter.
The code is:
#Path("/")
public interface OAuth2RequestService {
#POST
AccessTokenRecord create(#HeaderParam(value = "Content-type") String type,
#FormParam(value = "grant_type") String grantType,
#FormParam(value = "client_id") String clientId,
#FormParam(value = "client_secret") String clientSecret);
}
#Override
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public OAuth2Interceptor getAccessTokenInterceptor(Account account,
Boolean isGeneralEpaService) {
if (openAmIsEnabled(account)) {
final FeignOptions<OAuth2RequestService> options =
new FeignOptions<>(getAccessTokenUrl(account, isGeneralEpaService));
final AccessTokenRecord accessTokenRecord = workerRestService(options)
.create(HEADER_TYPE, CLIENT_CREDENTIALS, getClientId(isGeneralEpaService),
getClientSecret(isGeneralEpaService));
logger.infof("OAuth2 access token retrieval succeeded.");
return new OAuth2Interceptor(accessTokenRecord);
}
final AccessTokenRecord accessTokenRecord = new AccessTokenRecord();
accessTokenRecord.setAccessToken(getOsDefaultAccessToken(account));
accessTokenRecord.setTokenType(TOKEN_TYPE);
return new OAuth2Interceptor(accessTokenRecord);
}
private OAuth2RequestService workerRestService(
final FeignOptions<OAuth2RequestService> options) {
final Request.Options requestOptions =
new Request.Options(options.getConnectionTimeOut(), options.getReadTimeOut());
return Feign.builder().options(requestOptions).client(new OkHttpClient())
.contract(new JAXRSContract()).encoder(new JacksonEncoder())
.decoder(new JacksonDecoder()).decode404()
.target(OAuth2RequestService.class, options.getHostUrl());
}
I have tried several options with #QueueParam #FormParam
I have an entity class like below:
public class InputData {
byte[] nameBytes;
InputType inputType;
InputType outputType;
String inputName;
Description desc;
}
Here is my rest controller:
#PostMapping(path = "/submitData", consumes = "application/json")
public HttpStatus callDataService(#RequestBody Map<String, String> json) {
Gson gson = new GsonBuilder().create();
InputData inputData = gson.fromJson(json.get("inputData"), InputData.class);
Report report = dataService.getReport(inputData);
//return HttpStatus.OK;
}
I have two questions:
How can I send the report as well as Http Status back as a response?
How to send the data to controller?
I have created the following test case:
#Test
public void testController() throws JSONException {
Gson gson = new Gson();
Description desc = new Description();
desc.setMinimumValidSize(512);
File file = new File("src/test/resources/sampleDocuments/test_1.pdf");
byte[] byteArray = { 'P', 'A', 'N', 'K', 'A', 'J' };
JSONObject inputSample = new JSONObject();
inputSample.put("nameBytes", byteArray);
inputSample.put("inputType", ImageType.PDF);
inputSample.put("outputType", ImageType.TIFF);
inputSample.put("inputName", "ABCDEF");
inputSample.put("desc", desc);
String result = invokeRest(fileInputSample.toString(),"/submitData", HttpMethod.POST);
assertEquals("200", result);
}
private String invokeRest(String basicParams, String inputImageType, String
outputImageType, String options, String url, HttpMethod httpMethod) {
String testUrl = "http://localhost:" + port + url;
Map<String, Object> body = new HashMap<>();
body.put("fileInput", basicParams);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity(body, headers);
String result = "";
ResponseEntity<String> response = restTemplate.exchange(testUrl, httpMethod, entity, String.class);
if (response.getStatusCode() == HttpStatus.OK) {
result = response.getBody();
} else {
result = response.getStatusCode().toString();
}
return result;
}
When I run this the test case failed and I was able to pin point the issue :
Expected BEGIN_OBJECT but was STRING at line 1 column 13 path $.desc
So I am guessing I am not sending this values in right way
For Description POJO is below:
public class Description {
private static final int DPI = 300;
private Ctype c = CType.NONE;
private ColorType color = DEFAULT_COLOR;
private int dpi = DPI;
}
public enum CType {
NONE, GROUPA,GROUPB,GROUPB_B,GROUPD
}
public enum ColorType {
RGB, GREY;
}
Here is the values that is being send:
{"desc":"org.restservice.Description#1213ffbc”,
"outputType":"TIFF","inputType":"PDF","nameBytes":"src/test/resources/sampleDocuments/test_16.pdf","inputName":"98111"}
How can I send that as Object if I am sending a Map of <String, String> in body? Is there any other way to send that object to controller?
To return the status and also the object you can try to do it like this:
#PostMapping(path = "/submitData", consumes = "application/json")
public ResponseEntity<Report> callDataService(#RequestBody Map<String, String> json) {
Gson gson = new GsonBuilder().create();
InputData inputData = gson.fromJson(json.get("inputData"), InputData.class);
Report report = dataService.getReport(inputData);
return ResponseEntity.ok(report);
}
I am trying to read json request and then creating response. Request belongs to "Request.java" and Response belongs to "WsResponse.java". WsResponse.java is linked to contacts.java. When using postman appropriate response is achieved. but when trying to call method through client call, it is giving exception
"Exception in thread "main" org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.PushbackInputStream#44c8afef; line: 1, column: 14] (through reference chain: com.websystique.springmvc.model.Request["contacts"]->java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.PushbackInputStream#44c8afef; line: 1, column: 14] (through reference chain: com.websystique.springmvc.model.Request["contacts"]->java.util.ArrayList[0])
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:227)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:212)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:622)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:380)
at com.websystique.springmvc.Test.main(Test.java:49)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.PushbackInputStream#44c8afef; line: 1, column: 14] (through reference chain: com.websystique.springmvc.model.Request["contacts"]->java.util.ArrayList[0])
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.deser.std.StdDeserializer._parseString(StdDeserializer.java:810)
at com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer.deserialize(StringCollectionDeserializer.java:190)
at com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer.deserialize(StringCollectionDeserializer.java:161)
at com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer.deserialize(StringCollectionDeserializer.java:19)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:523)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95)
at com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap.findDeserializeAndSet(BeanPropertyMap.java:285)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:335)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:142)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3562)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2662)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:224)
... 6 more"
Below classes are given:
public class Request {
#JsonProperty
private String blocking;
#JsonProperty
private String module;
#JsonProperty("contacts")
private List<String> contacts;
public Request(){
}
public String getBlocking() {
return blocking;
}
public void setBlocking(String blocking) {
this.blocking = blocking;
}
public String getModule() {
return module;
}
public void setModule(String module) {
this.module = module;
}
public List<String> getContacts() {
return contacts;
}
public void setContacts(List<String> contacts) {
this.contacts = contacts;
}
#Override
public String toString() {
return "Request [blocking=" + blocking + ", module=" + module + ", contacts=" + contacts + "]";
}
}
public class WsResponse {
#JsonProperty("contacts")
private List<contacts> contacts;
public WsResponse() {
}
public List<contacts> getContacts() {
return contacts;
}
public void setContacts(List<contacts> contacts) {
this.contacts = contacts;
}
#Override
public String toString() {
return "[contacts:" + contacts + "]";
}
public class contacts {
private String input;
private String status;
private String wa_id;
public contacts() {
}
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getWa_id() {
return wa_id;
}
public void setWa_id(String wa_id) {
this.wa_id = wa_id;
}
#Override
public String toString() {
return "{input=" + input + ", status=" + status + ", wa_id=" + wa_id + "}";
}
}
public class Test {
public static void main(String[] args) throws JSONException {
final String uriForPost = "http://localhost:8080/Spring4MVCCRUDRestService/getContactDetails/";
ArrayList<String> contactList = new ArrayList<String>();
contactList.add("9315551000");
contactList.add("9315551002");
contactList.add("931-555-1005");
contactList.add("031-555-1005");
Request req = new Request();
req.setBlocking("wait");
req.setModule("Model_A");
req.setContacts(contactList);
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(uriForPost, req, Request.class);
}
}
Here my request JSON:
{
"blocking": "wait",
"module":"Model_A",
"contacts": [
"7855222121",
"988542d1222",
"9029674116",
"5252525252",
"213123123123"
]
}
Response (after calling it from Postman):
{
"contacts": [
{
"input": "7855222121",
"status": "valid",
"wa_id": "7855222121"
},
{
"input": "988542d1222",
"status": "valid",
"wa_id": "988542d1222"
},
{
"input": "9029674116",
"status": "valid",
"wa_id": "9029674116"
},
{
"input": "5252525252",
"status": "valid",
"wa_id": "5252525252"
},
{
"input": "213123123123",
"status": "valid",
"wa_id": "213123123123"
}
]
}
Tried below solutions which got on Stackoverflow and other sites available:
1. using #JsonProperty on fields
2. Passing JSON object instead of Request class object
JSONObject json = new JSONObject();
json.put("blocking", "wait");
json.put("module", "Model_A");
ArrayList<String> contactList = new ArrayList<String>();
contactList.add("6315551000");
contactList.add("6315551002");
contactList.add("631-555-1005");
json.put("contacts", contactList);
3.Adding below code for convertor
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
//messageConverters.add(new MappingJacksonHttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
4.
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM));
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
Any hint or clue will be helpful.
Thanks
There is a typo (I think) in your code, you are expecting response as Request.class, Instead you have to map it to WsResponse.class
// wrong mapping of response to Request.class
restTemplate.postForObject(uriForPost, req, Request.class);
// correct mapping
WsResponse res = restTemplate.postForObject(uriForPost, req, WsResponse.class);
Exception arises due to, contacts field which is present in request and response, but in request it is array of string and in response it is array of object (contact class), so when jackson tries to deserilize response json into Request#contacts, it finds a start object token { but it was expecting a string.
I have API which returns JSON in this format:
[
{ "shrtName": "abc", "validFrom": "2016-10-23", "name": "aaa", "version": 1 },
{ "shrtName": "def", "validFrom": "2016-11-20", "name": "bbb", "version": 1 },
{ "shrtName": "ghi", "validFrom": "2016-11-22", "name": "ccc", "version": 1 }
]
I have this code which reads API and returns it as a String. But I want to read this API and map it into the Java POJO class.
public String downloadAPI(){
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("API-Key", "4444444-3333-2222-1111-88888888");
HttpEntity<?> requestEntity = new HttpEntity<Object>(headers);
String URL = "https://aaaaaaa.io/api/v1/aaaaaaaaa?date=2015-04-04;
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
ResponseEntity<String> response = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, String.class);
return response.getBody();
}
My questions:
1) Format of POJO?
2) Changes in my method (return type POJO instead of String)
Your JSON is an array that's why []
Create POJO
public class MyPOJO {
private String shrtName;
private Date validFrom;
private String name;
private int version;
}
Remove message converter and refactor restTemplate exchange method to
ResponseEntity<MyPOJO[].class> response = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, MyPOJO[].class);
This is generic function that I use for GET requests
public <T> T getRequestAndCheckStatus(final String url, final Class<T> returnTypeClass,
final List<MediaType> mediaTypes,
final Map<String, String> headerParams,
final Map<String, Object> queryParams) throws Exception {
final HttpHeaders headers = new HttpHeaders();
headers.setAccept(mediaTypes);
setHeaderParamsIfExists(headers, headerParams);
final HttpEntity<String> requestEntity = new HttpEntity<>(headers);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url);
setQueryParamsIfExists(uriBuilder, queryParams);
final ResponseEntity<T> entity = restTemplate
.exchange(getUrl(uriBuilder),
HttpMethod.GET,
requestEntity,
returnTypeClass);
Assert.assertEquals(HttpStatus.OK, entity.getStatusCode());
return entity.getBody();
}
private void setHeaderParamsIfExists(HttpHeaders headers, Map<String, String> headerParams) {
if(headerParams != null && !headerParams.isEmpty())
headerParams.entrySet()
.forEach(entry -> headers.set(entry.getKey(), entry.getValue()));
}
private void setQueryParamsIfExists(UriComponentsBuilder uriBuilder, Map<String, Object> queryParams) {
if(queryParams != null && !queryParams.isEmpty())
queryParams.entrySet()
.forEach(entry -> uriBuilder.queryParam(entry.getKey(), entry.getValue()));
}
private URI getUrl(UriComponentsBuilder uriBuilder) {
return uriBuilder.build().encode().toUri();
}
In your case you would call it by
getRequestAndCheckStatus("https://aaaaaaa.io/api/v1/aaaaaaaaa", MyPOJO[].class,
Collections.singletonList(MediaType.APPLICATION_JSON_UTF8),
new HashMap<String, String>(){{ put("API-Key", "4444444-3333-2222-1111-88888888"); }}),
new HashMap<String, Object>(){{ put("Date", "2015-04-04"); }});
Additionaly, for Date I recommend to use long and then in controller parse it to Date. I see that you use https protocol, have you configured certificate ?
Create a pojo with those atrributes and use jackson for convert from json String to your pojo.
public class MapClass {
private String shrtName;
private Date validFrom;
private String name;
private int version;
}