I'm using RestTemplate to interact with several REST services, some of them accept/return JSON and some XML. To that end, I've added Jackson's dataformat-xml module as a dependency (along with the JAXB annotations module). RestTemplate automatically includes MappingJackson2XmlHttpMessageConverter (done in the RestTemplate constructor).
This creates a situation where some objects that are used as the request parameter in calls to
RestTemplate.postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)`
get serlialized as XML and the content-type of the request is set to application/xml. For example:
MyObject request = ...;
String url = ...;
MyResponseObject response = restTemplate.postForObject(url, request, MyResponseObject.class);
RestTemplate tries to serialize MyObject to XML and sets the request media type to application/xml.
Problem is, many of the services we call don't accept XML (they expect JSON). So now that I have MappingJackson2XmlHttpMessageConverter on the classpath, it's taking precedence over the JSON converter which makes the calls to JSON services fail.
I suppose I could change my calling code to pass an HttpEntity with the media type explicitly set to JSON instead of my simple data object, but that's kind of ugly (boilerplate) and would mean changing quite a few service calling code.
Is there a way to either
A) change the priority of the MessageConverters so that the standard Jackons (JSON) one takes priority over MappingJackson2XmlHttpMessageConverter
or
B) Prevent MappingJackson2XmlHttpMessageConverter from claiming that it can serialize the objects I don't want it to
?
I can see two options :
Create a RestTemplate with the HttpMessageConverter you want, in the order you want them to be used (check HttpEntityRequestCallback.doWithRequest they are used in the order they are in the list and the first matching converter will be used
As you suggested, using an HttpEntity and setting the Content-Type header to the mime type you want to get.
I think using a helper to create an HttpEntity with your object and the correct Content-Type header would be safer :
public class HttpEntityHelper {
public static <T> HttpEntity<T> jsonHttpEntity(T body) {
MultiValueMap<String, String> headers = new LinkedMultiValueMap();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return new HttpEntity(body, headers);
}
}
Related
I am new to Springboot reactive
I was asked to call the following endpoint and return todays weather data only:
https://api.weather.gov/gridpoints/MLB/33,70/forecast
I believe I need to use something like this...
WebClient.create().get()
.uri("https://api.weather.gov/gridpoints/MLB/33,70/forecast")
.retrieve()
.bodyToMono(WeatherClass.class)
.block();
Do I need to map out an entire java object to match the JSON at the endpoint? is there an easy way to perhaps just grab the a certain piece of the JSON?
How would I handle something like the #context annotation in the JSON.
The WebClient in spring boot automatically uses Jackson's ObjectMapper to unmarshall json to a java object when the content type of the response is application/json. So there is no need to pull in any additional libraries or have to write any specific unmarshalling code, unless you want to use an alternate json-to-java library.
When using Jackson, you don't need to map every field in the json to your java object. You can annotate your java class with #JsonIgnoreProperties to inform jackson to ignore any properties that may appear in the json but do not have a matching field in your java object.
An example WeatherClass in which you want only the #context and forecastGenerator unmarshalled would look something like this
#JsonIgnoreProperties
public class WeatherClass {
private final List<Object> context;
private final WeatherProperties weatherProperties;
public WeatherClass(#JsonProperty("#context") List<Object> context,
#JsonProperty("properties") WeatherProperties weatherProperties) {
this.context = context;
this.weatherProperties = weatherProperties;
}
private class WeatherProperties {
private final String forecastGenerator;
private WeatherProperties(#JsonProperty("forecastGenerator") String forecastGenerator) {
this.forecastGenerator = forecastGenerator;
}
}
}
Note
#context seems to be an array that can contain multiple types (both objects and strings in your example). I've used Object to work around this but obviously isn't the most graceful solution but should be adequate to demonstrate how Jackson works
Alternatively, you can unmarshall the response to a JsonNode, which you can then use to traverse the structure of the json without converting it to a java object. For example
String forecastGenerator = WebClient.create().get()
.uri("https://api.weather.gov/gridpoints/MLB/33,70/forecast")
.retrieve()
.bodyToMono(JsonNode.class)
.block().get("properties").get("forecastGenerator").toString()
There are many other annotations provided by Jackson that can used to define how the unmarshaller functions. Too many to cover here. See Jackson Deserialisation Annotations
I am using Spring Boot 1.5.18 with ReactJs and I am trying to conditionally update an entity using an eTag.
I have added the following config and Spring Boot is generating the etags in the response headers.
#Bean
public Filter shallowEtagHeaderFilter() {
return new ShallowEtagHeaderFilter();
}
Now I want to check in my controller method if the etags match before updating the entity. My controller method is as follows:
#RequestMapping(method = POST, value = "/assignments/{id}/edit")
public #ResponseBody
ResponseEntity<?> editStaffAssignment(#RequestBody Resource<AssignmentDTO> dtoResource,
#PathVariable Long id,
PersistentEntityResourceAssembler resourceAssembler, Pageable pageable) {
Assignment assignment = assignHandler.updateAssignment(dtoResource.getContent(), id);
return new ResponseEntity<>(resourceAssembler.toResource(assignment), HttpStatus.OK);
}
I have included the etag header in the Axios request from the reactjs client and I can extract it in the controller method but how do I generate the current etag value on the server side programatically?
The ShallowEtagHeaderFilter calculates the ETag value based on the serialized response body, so it's not easily possible to calculate it upfront (i.e. before the response body is actually sent).
If I understand you correctly you want to employ an optimistic locking mechanism. If that's the case I'd suggest to drop the ShallowEtagHeaderFilter and instead calculate the ETag manually in the first place, like this:
String etag = calculateETag(); // TODO
return ResponseEntity.ok()
.eTag(etag)
.body(resourceAssembler.toResource(assignment));
If using JPA you could re-use the #Version field as an ETag value here.
A RestTemplate request I am making gives a JSON request of a large data type. For example it responds with a channel object, a user object, and a preferences object.
I only need the information that relates to a channel object. Is there any way to have the responseType only deal with the channel object even though the response contains more than that?
I am only asking because I feel like it is pointless to create DTOs that contain fields that I don't care about and will never use.
You can create a pojo with channel object and mark pojo to ignore unknown properties ,
#JsonIgnoreProperties(ignoreUnknown = true)
public class Myclass {
Channel channel;
...
}
In rest template ,
restTemplate.exchange(url, HttpMethod.POST, entity, Myclass .class);
In Spring, why can't we simply use a request object to carry data around in the application? Why did we switch over to using a model object? Also I read on a popular Spring book that it is still a request object (even in a Spring application) that does most of the hardwork of carrying data. Does it mean that the models we create in Spring are somehow linked to a request object underneath the hood?
In Spring, why can't we simply use a request object to carry data
around in the application?
You can use HttpServletRequest or even WebRequest as method argument and then inspect those two in order to extract the data out of it. For example, if you want to get the value of a query string named page:
#RequestMapping("/users")
public void listUsers(HttpServletRequest request) {
String page = request.getParameter("page");
...
}
Why did we switch over to using a model object?
You can simply read from a HttpServletRequest and finally write to a HttpServletResponse in any controller method but spring also provides nicer abstractions for handling typical web scenarios. For example if your page parameter is a required int parameter with the default value of 1, you can simply write:
#RequestMapping("/users")
public void listUsers(#RequestParam(defaultValue = "1") Integer page) {
...
}
Spring automatically does all the string to int conversion and populating with default values for you. Or suppose you want to automatically validate your json or xml Request Body and return validation errors. With these higher abstractions you will define a model object that contains all the validation logic in it and simply add #Valid annotation before its reference it method arguments, that's it!
#RequestMappting(value = "/users", method = POST)
public void addNewUser(#RequestBody #Valid User user) {
// automatically validates the user
// and send validation error, if user wasn't valid
}
Of course you can do the same by inspecting HttpServletRequest but it would be so cumbersome.
Does it mean that the models we create in Spring are somehow linked to
a request object underneath the hood?
They're not necessarily link to them but usually these model objects will be populated from request parameters.
For example, spring convert the request body to the method argument by using an HttpMessageConverter. HttpMessageConverter is responsible for converting from the HTTP request message to an object and converting from an object to the HTTP response body. For instance FormHttpMessageConverter converts form data to/from a MultiValueMap<String, String>. This class has a read method that reads the request body and converts it to a MultiValueMap<String, String>. This method looks like this:
public MultiValueMap<String, String> read(Class<? extends MultiValueMap<String, ?>> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = (contentType.getCharSet() != null ? contentType.getCharSet() : this.charset);
String body = StreamUtils.copyToString(inputMessage.getBody(), charset);
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(pairs.length);
for (String pair : pairs) {
int idx = pair.indexOf('=');
if (idx == -1) {
result.add(URLDecoder.decode(pair, charset.name()), null);
}
else {
String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
result.add(name, value);
}
}
return result;
}
By String body = StreamUtils.copyToString(inputMessage.getBody(), charset);, it actually reads the request body and converts it to a String.
I have spring controller:
#RequestMapping(value = "/add", method = RequestMethod.POST,
consumes = "application/json")
public #ResponseBody ResponseDto<Job> add(User user) {
...
}
I can POST the object like this with APACHE HTTP CLIENT:
HttpPost post = new HttpPost(url);
List nameValuePairs = new ArrayList();
nameValuePairs.add(new BasicNameValuePair("name", "xxx"));
post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = client.execute(post);
In controller I get user with name "xxx"
Now I want to create User object and post it to the server,
I tried to use with GSON object like this :
User user = new User();
user.setName("yyy");
Gson gson = new Gson();
String json = gson.toJson(user);
HttpClient client = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(json.toString(), HTTP.UTF_8);
entity.setContentType("application/json");
httpPost.setEntity(entity);
HttpResponse response = client.execute(httpPost);
But in this way I get in server User object with null fields...
How can I solve it ?
Ok a few things you're missing:
Make sure you are serializing and deserializing User to json the same way on the client and server.
Make sure to have jackson libraries on the classpath if you want to use spring built-in jackson support (and preferably use that on the client as well) or include apropriate HttpMessageConverter for Gson. You can use GsonHttpMessageConverter from spring-android for that.
Annotate your request handler method parameter with #RequestBody.
In case of using jackson, as #ararog mentioned, make sure that you specifically exclude fields that can be ingored or annotate the whole User class with #JsonIgnoreProperties(ignoreUnknown = true)
As far as I know, Spring MVC uses Jackson for JSON parsing and serialization/deserialization, jackson usually expects the for a JSON content which has data for all class properties, except those who are marked with JSON ignore, like below:
public class User {
private String login;
private String name;
#JsonIgnoreProperty
private String password;
... getters/setters...
}
So, if you create a instance of User an set only the user name and send this data to server, Jackson will try to deserialize the content to another User object on server side, during the deserialization process he will consider the two mandatory properties login and name, since only name is filled the deserialization is finished and a null reference is returned to the controller.
You have two options:
As an test, set a fake value in all the other properties and send the user data again
Create a Jackson mix-in and add anotations to ignored properties.