i currently use Google's GSON library to serialize/deserialize rest service responses.
But i have a little problem. My response object has T response attribute.
public class IninalResponse<T> {
private int httpCode;
private String description;
private T response;
private HashMap<String,String> validationErrors;
...
}
I would like to get response attribute according to object type which i specified. At this example i specified with GetAccessTokenResponse to deserialize T response attribute in the piece of code below.
public IninalResponse getAccessToken(String apikey) {
String path = "https://sandbox-api.ininal.com/v2/oauth/accesstoken";
return doPostIninal(apikey,path,null,GetAccessTokenResponse.class);
}
GSON library successfully deserializes IninalResponse object except for T response field. Gson deserializes it as LinkedTreeMap typed object.
public <T,V> IninalResponse doPostIninal(String apikey, String path,V requestBody, T response) {
RestTemplate template = restClient.getRestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION,apikey);
headers.add(HttpHeaders.DATE, "");
headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
HttpEntity<?> request = new HttpEntity<Object>(requestBody,headers);
ResponseEntity<String> accessTokenResponse = restClient.getRestTemplate().postForEntity(path,request,String.class);
IninalResponse<T> responseBody = new IninalResponse<T>();
responseBody = new Gson().fromJson(accessTokenResponse.getBody(),responseBody.getClass());
System.out.println(accessTokenResponse);
return responseBody;
}
Still i have no idea why gson could not deserialize ? What exactly am i missing ?
Due to Java's type erasure, you need to make a trick to serialize/deserialize generic types:
Type fooType = new TypeToken<IninalResponse<TheType>>() {}.getType();
gson.fromJson(accessTokenResponse.getBody(), fooType);
where TheType is the type you passed in during serialization (String I suppose).
Serialization goes as this:
Type fooType = new TypeToken<IninalResponse<String>>() {}.getType(); // I assume it was a String here.
gson.toJson(someString, fooType);
Related
I tried to create a generic method to read the response json but I don't like to have a generic object with many hashmaps... I don't want to specify the type. I want to get the content for Country, City, etc...
AwsProxyResponse response = get("/countries");
List<Country> countryList = (List<Country>) jsonStringToObject(response.getBody()).get("countries"); // doesn't work
protected Object jsonStringToObject(String jsonString) throws IOException {
JsonObject jsonData = new JsonObject(jsonString);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonData.toString().getBytes(), Object.class);
}
What is the proper way to get the Response data and then fetch what I need (e.g. content, totalElements, etc...)
I am developing an application using Spring Boot (with Java).
This application has to call several external services and each of these services requires a complicated body (in json or xml) (this input can vary! The fields I pass to it are not required so sometimes I might even pass a subset of these fields). These are examples of inputs that services can receive:
{
"field1": "string",
"field2": "string",
"field3": "string",
"field4": 0,
}
<input>
<input1>my_string</input1>
<input2>my_string</input2>
</input>
I use RestTemplate to make HTTP calls. This is an example. I use a Java String to model the HTTP body (but it has the big defect that it is not editable but hard-coded!):
String Jsonbody = "{\r\n"
+ " \"field1\": \""+myString1+"\"\r\n"
+ " \"field2\": \""+myString2+"\"\r\n"
+ "}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<String>(Jsonbody, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MyResponseClass> response = restTemplate.postForEntity(url, request, MyResponseClass.class);
It is very bad to have a body hard-coded like this in the JsonBody variable. What is the way to have an object in which I dynamically insert strings and which automatically creates a JSON object (which I can then convert to a string to put in the .postForEntity method)? The same issue for XML input types.
There are multiple libraries as Gson or Jackson that actually allow you to work with JSON as they are Java Objects to finally serialize them as a body.
If you want to give it a try, you would just need to include Gson as dependency:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
Then you'd have to create a POJO for your POST body:
public class JsonBody {
private List<String> fields = new LinkedList<>();
void addField(String field) {
fields.add(field);
}
}
And feel free to add as many String as you want that it will be parsed afterwards.
JsonBody jsonBody = new JsonBody();
Gson gson = new Gson();
jsonBody.addField(myString1);
jsonBody.addField(myString2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<String>(gson.toJson(jsonBody), headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MyResponseClass> response = restTemplate.postForEntity(url, request, MyResponseClass.class);
This would set your JSON to:
{
fields: [
contentMyString1,
contentMyString2
]
}
Taking the previous example into account, you can just modify and format your JSON as you want in the form of a POJO and use the Gson::toJson(String) function from Gson.
Of course if you just create a POJO as:
public class JsonBody {
private String field1;
private String field2;
private String field3;
private Integer field4;
}
Then the output would be the same one than you have in your initial example.
Use the ObjectMapper class like this ...
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
objectMapper.writeValue(new File("target/car.json"), car);
The output will be
{"color":"yellow","type":"renault"}
Here's some more data on the topic https://www.baeldung.com/jackson-object-mapper-tutorial
Same approach is used for mapping objects into XML (https://www.baeldung.com/jackson-xml-serialization-and-deserialization)
I have a controller class that looks like:
#RequestMapping(value="/enter/two-factor/blacklist", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody String getBlacklist(int days) {
List<Honey> honey = honeyService.fetchAllPings(days);
List<Locate> locations = honeyService.parseDistinctLocations(honey);
return GeneralUtil.convertToJson(locations);
}
The 'GeneralUtil.convertToJson()' method returns a pretty-print string with this code:
public static String convertToJson(List<Locate> locations){
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = "";
try {
json = gson.toJson(new Blacklist(locations));
} catch (Exception e){
e.printStackTrace();
}
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(json);
String prettyJsonString = gson.toJson(je);
System.out.println(prettyJsonString);
return prettyJsonString;
}
However, when the page renders, the JSON is not pretty-printed. What am I missing?
You're overlooking the fact that #ResponseBody with a String return type will cause a StringHttpMessageConverter to write the value returned to the HTTP response body. This HttpMessageConverter will produce a content-type of text/plain. I believe browsers don't render new line characters or trim whitespace between them. Your pretty printing gets lost.
What you should do is register your own GsonHttpMessageConverter or MappingJackson2HttpMessageConverter which is set up to do pretty printing. Then change your handler method's return type to List<Locate> and return locations directly. These HttpMessageConverter implementations produce application/json which browsers should render literally.
(Gson and ObjectMapper instances are thread safe. There's absolutely no reason to reinstantiate those classes for every request.)
Update 02/05/2018 (about 4 years later)...I tested this again as people have been upvoting my question/answer and Sotirios Delimanolis is correct that I should not have to write the code in my answer to make this work. I used basically the same RestTemplate/REST service setup as shown in my question with the REST service having a confirmed response content type of application/json and RestTemplate was able to process the response with no issues into a Map.
I'm invoking a rest service that returns JSON like this:
{
"some.key" : "some value",
"another.key" : "another value"
}
I would like to think that I can invoke this service with a java.util.Map as the response type but that's not working for me. I get this exception:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map]
Should I just specify String as the response type and convert the JSON to a Map?
Edit I
Here's my restTemplate call:
private Map<String, String> getBuildInfo(String buildUrl) {
return restTemplate.getForObject(buildUrl, Map.class);
}
Here's how I'm setting up the restTemplate:
#PostConstruct
public void initialize() {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new ClientHttpRequestInterceptor() {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
requestWrapper.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
return execution.execute(requestWrapper, body);
}
});
restTemplate.setInterceptors(interceptors);
}
Edit II
Full error message:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [application/octet-stream]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:108) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:549) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:502) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:239) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
at idexx.ordering.services.AwsServersServiceImpl.getBuildInfo(AwsServersServiceImpl.java:96) ~[classes/:na]
RestTemplate has a method named exchange that takes an instance of ParameterizedTypeReference as parameter.
To make a GET request that returns a java.util.Map, just create an instance of an anonym class that inherits from ParameterizedTypeReference.
ParameterizedTypeReference<Map<String, String>> responseType =
new ParameterizedTypeReference<>() {};
You can then invoke the exchange method:
RequestEntity<Void> request = RequestEntity.get("http://example.com/foo")
.accept(MediaType.APPLICATION_JSON).build();
Map<String, String> jsonDictionary = restTemplate.exchange(request, responseType).getBody();
As I had previously noted, your error message is showing us that you are receiving application/octet-stream as a Content-Type.
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [application/octet-stream]
As such, Jackson's MappingJackson2HttpMessageConverter cannot parse the content (it's expecting application/json).
Original answer:
Assuming your HTTP response's Content-Type is application/json and you have have Jackson 1 or 2 on the classpath, a RestTemplate can deserialize JSON like you have into a java.util.Map just fine.
With the error you are getting, which you haven't shown in full, either you've registered custom HttpMessageConverter objects which overwrite the defaults ones, or you don't have Jackson on your classpath and the MappingJackson2HttpMessageConverter isn't registered (which would do the deserialization) or you aren't receiving application/json.
I think you can achieve what you're aiming for simply using the RestTemplate and specifying a JsonNode as the response type.
ResponseEntity<JsonNode> response =
restTemplate.exchange(url, HttpMethod.GET, entity, JsonNode.class);
JsonNode map = response.getBody();
String someValue = map.get("someValue").asText();
Update 02/05/2018 (about 4 years later)...I tested this again as people have been upvoting my question/answer and Sotirios Delimanolis is correct that I should not have to write the code in my answer to make this work. I used basically the same RestTemplate/REST service setup as shown in my question with the REST service having a confirmed response content type of application/json and RestTemplate was able to process the response with no issues into a Map.
I ended up getting the contents as a String and then converting them to a Map like this:
String json = restTemplate.getForObject(buildUrl, String.class);
Map<String,String> map = new HashMap<String,String>();
ObjectMapper mapper = new ObjectMapper();
try {
//convert JSON string to Map
map = mapper.readValue(json, new TypeReference<HashMap<String,String>>(){});
} catch (Exception e) {
logger.info("Exception converting {} to map", json, e);
}
return map;
I know its old, but just for other people that may visit this topic:
If you want to register some additional converters with RestTemplateBuilder you also have to explicitly register default ones
#Bean
public RestTemplateBuilder builder() {
return new RestTemplateBuilder()
.defaultMessageConverters()
.additionalMessageConverters(halMessageConverter());
}
private HttpMessageConverter halMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jackson2HalModule());
TypeConstrainedMappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
halConverter.setSupportedMediaTypes(Collections.singletonList(MediaTypes.HAL_JSON));
halConverter.setObjectMapper(objectMapper);
return halConverter;
}
This worked 100% for me
in client
Map<String, Object> mapRespuesta = new HashMap<>();
mapRespuesta.put("mensaje", "Process completed successfully");
mapRespuesta.put("idResponse", id);
return new ResponseEntity<Map<String, Object>>(mapRespuesta, HttpStatus.OK);
in which it makes the connection
ResponseEntity<Map> result = restTemplate.postForEntity(url, params, Map.class);
String id = result.getBody().get("idResponse").toString();
#GetMapping(value = "getSunny/{userId}")
public Map<String, SunnyVO> getSunny(#PathVariable int sunnyId) {
Map<String, SunnyVO> newObj = new HashMap<String, SunnyVO>();
final String url = "http://localhost:8085/Sunny/getSunny/{sunnyId}";
RestTemplate restTemplate = new RestTemplate();
newObj = restTemplate.getForObject(url, Map.class, sunnyId);
return newObj;
}
It is working for me ...
Now I'm learning how to use Gson library to set and get data from webservice in Json format, but its best practices and strategies are a bit dark for me so I will be very delightful if somebody would explain more about it.
I've created an Entity class to get response entity from server:
public class Response
{
#SerializedName("Type")
public String Type;
#SerializedName("result")
public String result;
}
and in AsyncTask class I've used:
Response _Response = new Response();
try
{
String _url = Global.Url_Request ;
Map<String, String> Params = new HashMap<String, String>();
Params.put("PhoneNumber", this.User_PhoneNumber);
String json = new GsonBuilder().create().toJson(Params, Map.class);
HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(_url);
httpPost.setEntity(new StringEntity(json));
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
HttpResponse getResponse = httpclient.execute(httpPost);
HttpEntity returnEntity = getResponse.getEntity();
is = returnEntity.getContent();
Gson gson = new Gson();
Reader reader = new InputStreamReader(is);
_Response = gson.fromJson(reader, Response.class);
}
catch (Exception e)
{
_Response.Type= "Error";
_Response.result= "Data Is Wrong";
}
return _Response;
It works fine with creating an Entity Object for every different http POST call, but my questions are:
What is the best practice for handling webservices with different response objects?
How can I handle this situation: if data sent ok then return specific Jsonarray; if not, return a Response object to detect something is wrong. Should I use Custom typeAdapter?(sample code would be great)
If webservice returns an empty response gson.fromJson would throw an **IllegalStateException: Expected a string but was BEGIN_OBJECT** how can i prevent this?
Thanks in advance
1. What is the best practice for handling webservices with different response objects?
I think that this depends on the kind of control you have. If you code also the webservice, you could create a big container object that has may fields. Each of these fields is one of the possible responses you can pass between client and server. If you have not control on what the server can reply, and it can differ a lot, JsonParser is your best friend. You can use it to snoop inside JSON and decide the right strategy to handle the response.
Let's do an example for case one. I declare these classes:
public static class GenericResponse{
public ServerException exception;
public StandardResponse1 responseType1;
public StandardResponse2 responseType2;
#Override
public String toString() {
return "GenericResponse [exception=" + exception + ", responseType1=" + responseType1 + ", responseType2=" + responseType2 + "]";
}
}
public static class ServerException{
public int error;
public String message;
}
public static class StandardResponse1{
public List<Integer> list;
public Date now;
}
With this kind of classes, I can parse:
{"responseType1":{"list":[1,2],"now":"Nov 25, 2013 9:26:51 PM"}}
or
{"exception":{"error":-1,"message":"Don\u0027t do this at home"}}
For example, if I get from server the second type of response, this code:
GenericResponse out = g.fromJson(fromServerStream, GenericResponse.class);
System.out.println(out);
will return me:
GenericResponse [exception=stackoverflow.questions.Q20187804$ServerException#1e9d085, responseType1=null, responseType2=null]
All you have to do is to check your fields to see what actually the server replied.
Case two. You cannot control the JSON, so the server can reply
[13,17]
or
{"error":-1,"message":"Don\u0027t do this at home"}
In this case you cannot pass directly the class type to Gson as before, but you have to check things. I would solve this problem with a JsonParser.
Gson g = new Gson();
JsonParser jp = new JsonParser();
JsonElement o = jp.parse(s);
if (o.isJsonArray()){
List<Integer> list = (List) g.fromJson(o, listType1);
System.out.print(list);
}
else{
ServerException e = g.fromJson(s, ServerException.class);
System.out.print(e);
}
Using JsonObject/JsonArray and so on, is what happens inside a TypeAdapter. In the adapter you start with the JsonElement that is already parsed. There are many good example of it on SO, this for example.
How can I handle this situation: if data sent ok then return specific Jsonarray; if not, return a Response object to detect something is wrong. Should I use Custom typeAdapter?(sample code would be great)
Do you mean you want to parse this kind of response? Examples of point 1 show this.
If webservice returns an empty response gson.fromJson would throw an **IllegalStateException: Expected a string but was BEGIN_OBJECT how can i prevent this?**
JsonParser/TypeAdapter is again the solution. You can check if JsonElement is null or if is a primitive type (String, Integer, Boolean) and deal it.