Spring REST with both JSON and XML - java

I want to provide one comprehensive REST API with support for both JSON and XML.
The domain model is of complex type and we note that to produce friendly JSON and XML on the same model using MappingJacksonHttpMessageConverter and JaxbMarshaller respectively tends to give either readable XML or readable JSON 1).
What's the best way to proceed?
1) Due to how objects such as maps, root tags and relations are modelled differently in json than in xml, the objects to serialize needs to be designed differently to get both tidy json and tidy xml. Utilities such as jaxb annotations only goes that far.
I can think of a few candidates
1) Create both a json and xml controller/model
public class Controller {
public Foo foo() {
return new Foo();
}
}
public class XmlController extends Controller {
#Override
public XmlFoo foo() {
return new new XmlFoo(super.foo());
}
}
public class JsonController extends Controller {
#Override
public JsonFoo foo() {
return new JsonFoo(super.foo());
}
}
Given a model object Foo create a JsonFoo and XmlFoo
2) Write a custom message converter
I tried this and it turned out to be a bit complicated since the view must know how to resolve e.g., a Foo to a JsonFoo to be able to serialize it into a readable format.
3) Let each model object serialize itself, e.g.,
public class Foo {
public String serialize(Serializer s) {
return s.serialize(this);
}
}
Based on some arbitration parameter let the controller inject the correct serializer
new Foo(new FooJsonSerializer());
new Foo(new FooXmlSerializer());

I'm doing this in a current project without using a ContentNegotiatingViewResolver. For one method in my controller:
#RequestMapping(value = "/test", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<BasicResponse> getBasicResponse() {
return new HttpEntity<BasicResponse>(new BasicResponse());
}
I can receive the following output based on the Accept request header.
Accept: application/xml (requires JAXB2 on the classpath)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<basicResponse>
<errors>
<message>test1</message>
<message>test2</message>
</errors>
</basicResponse>
Accept: application/json (requires Jackson on the classpath)
{
"errors" : ["test1", "test2"]
}
My response object is simple and uses normal annotations:
package org.mypackage.response;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class BasicResponse {
#XmlElementWrapper(name = "errors")
#XmlElement(name = "message")
private List<String> errors = new ArrayList<String>();
public BasicResponse() {
this.errors.add("test1");
this.errors.add("test2");
}
public List<String> getErrors() {
return errors;
}
}
The SpringSource spring-mvc-showcase project is also a helpful resource. I think they separate the conversions for different methods, but I am definitely doing this for one method.
I can't quite tell by your question...but if you're looking to serialize the output more than that, #chrylis is correct in that a custom serializer would be your next move. But everything I've ran into (which can get pretty complex, with nested objects in my response) converts perfectly to valid XML or JSON.

You should use the ContentNegotiatingViewResolver.
There is an issue in that a collection of POJOs are not mapped correctly with some XML marshallers. XStream has solutions for this (Moxy too?).
Here's a place to start:
http://blog.springsource.org/2013/06/03/content-negotiation-using-views/
Basically, you use a MappingJacksonView and a similar one for XML, which is a "fake" view that uses Jackson (or an XML marshaller) to marshall your POJO(s) to the correct format.
The server will send back the correct type based on one of:
the HTTP Accept header
a "filetype extension", such as ".json"
a querystring parameter, such as "format=json"

As far as omitting fields, you cans use annotations #JsonIgnore(for Jackson) and/or #XStreamOmitField(for XStream).
Did you try this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
headers ={"Accept=application/json,application/xml"},
produces={"application/json", "application/xml"})

Related

In the context of SpringMVC, how to have web services that provide different JSON representation of a same class?

I have a data class, something like this:
public class Person {
private String name;
private Long code;
// corresponding getters and setters
}
I want to write two web services that provide two different JSON representation of Person. For example, one of them provide {"name":"foo"} but the other one {"name":"foo", "code":"123"}.
As a more complicated scenario, suppose that Person has a reference property, for example address. Address has its own properties as well and I expect that both of my web services consider this property but each of which do this in their own manner.
How should my SpringMVC views be like?
Please note that I'm new to SpringMVC. So give me a sample code beside your answer, please.
UPDATE 1: After few days, all answers push me to solve the problem in controllers or by annotating the data classes. But I want to do this in views, without any more java codes. Can I do it in JSP files or thymeleaf templates or even in .properties files?
UPDATE 2: I found json-taglib. But somehow it is left out of new upgrades. Is there any similar solution?
You're using Spring-MVC so Jackson is in charge of JSON serialize and deserializing.
In this case, you can use #JsonInclude(Include.NON_NULL) to ignore null field during serialization.
public class Person {
#JsonInclude(Include.NON_NULL)
private String name;
#JsonInclude(Include.NON_NULL)
private Long code;
// corresponding getters and setters
}
If your name or code is null then it is excluded from output JSON
So if you pass code as null, your ouput JSON will look like {"name":"foo"}
When creating JSon with Spring MVC the "view renderer", by default, is Jackson. There is no need to use things like JSP or other view technology. What you want to do, is to tell Jackson how to render an object for a given situation. Multiple options are available, but I would suggest to use projections.
An example:
#RestController
#RequestMapping(value = "person")
public class PersonController {
private final ProjectionFactory projectionFactory;
public PersonController(ProjectionFactory projectionFactory) {
this.projectionFactory = projectionFactory;
}
#GetMapping("...")
public PersonBase getPerson(..., #RequestParam(value = "view", required = false, defaultValue = "base") String view) {
...
if(view.equals("extended")) {
return projectionFactory.createProjection(PersonExtended.class, person);
} else {
return projectionFactory.createProjection(PersonBase.class, person);
}
}
}
public interface PersonBase {
String getName();
}
public interface PersonExtended extends PersonBase {
Long getCode;
}
The view layer of your application are the projection classes (put then in one package, the view package if you wish).
A Controller can choose what view to render, or you could make the result dynamic as in the example.
Your question on how to render the address could be solved with another projection like this:
public interface PersonWithAddress extends PersonExtended {
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreetName();
String getZipcode();
...
}
You can look for dynamic filtering of fields using MappingJacksonValue.
The filter allows you to serialize fields that meet custom criteria.
You can check my sample code here:
package com.github.tddiaz.jsonresponsefiltering;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import jdk.nashorn.internal.objects.annotations.Getter;
import lombok.Data;
import lombok.NonNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#RestController
class Controller {
#GetMapping("/filter")
public ResponseEntity filter() {
final Response response = new Response("value1", "value2", "value3");
//ignore field3; will only return values of field1 and field2
final SimpleBeanPropertyFilter beanPropertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
final FilterProvider filterProvider = new SimpleFilterProvider().addFilter("responseFilter", beanPropertyFilter);
final MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(response);
mappingJacksonValue.setFilters(filterProvider);
return ResponseEntity.ok(mappingJacksonValue);
}
}
#JsonFilter("responseFilter")
#Data
class Response {
#NonNull
private String field1, field2, field3;
}
}
Use Projection for expose in diferent manners your json, and ResourceProcessor if you need to add more info to this model through projection, e.g another DB table perhaps.
Based on your use case, just call the controller of your choice from the jsp/js page ...For e.g. Let's say Admin is the user then call AdminController else call User Controller ...this can be done using a simple if/else condition...you can also look into Proxy Design Pattern but that depends on the use case
I recommend you to use JSON.stringify(value[, replacer[, space]]) function on frontend. I have given an example below. You have a write a custom function according to your requirements on the specific view.
Eg. The below example ignores null values. Here I have written editperson function which removes null values.
The function has two input parameters i.e. key and value. Write your logic according to the keys and values which you want to remove or change.
var springperson = { "name":"foo","code":null }
console.log(springperson); // person recieved from spring
function editjson(key, value){
if (value !== null) return value
}
var editperson = JSON.stringify(springperson, editjson); // String representation of person
var personjson=JSON.parse(editperson); // JSON object representation of person
console.log(personjson); // person as required by the view
Comment if you have any issues.

extra data while parsing boolean value in json by using Jackson

When I'm parsing boolean value in JSON by using Jackson, I not only get my expected data, but also an extra key-value data. I want to deserialize the JSON into Java Beans and then serialize it into a String again after processing it. The extra data is in the finally result.Here is my JSON data:
{"is_charging": true}
But I get this after I parse it and then serialize it:
{"is_charging": true, "charging": true}
And here is my Java bean:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
public class Data {
#JsonProperty("is_charging")
public boolean isCharging;
}
However, if I don't use the #JsonProperty, it can not deserialize the "is_charging" and deserialize it as false by default.
How can I solve this? Thanks!
It is the lombok.Getter and lombok.Setter annotations that cause the issue.
public class Data {
#JsonProperty("is_charging")
public boolean isCharging;
}
objectMapper.writeValueAsString(new Data());
Works as expected.
The problem occurs when the #Getter and #Setter annotations are added.
I don't have experience with this lombok library but as far as I understand it creates getter and setter methods for you.
By configuring objectMapper you can disable auto detecting of getter and setter methods so only fields can be serialized and deserialized.
#Getter
#Setter
public class Data {
#JsonProperty("is_charging;")
public boolean isCharging;
}
public static void main(String... args) throws JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
objectMapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
Data data = objectMapper.readValue("{\"is_charging\": true}", Data.class);
System.out.print(objectMapper.writeValueAsString(data));
}
Outputs:
{"is_charging":true}
Note that only objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false); is required in your case. Others are provided for reference in case you need them.
It is possible by changing the attribute name from isCharging to charging
#Getter
#Setter
public class Data {
#JsonProperty("is_charging")
public boolean charging;
}
Result:
{"is_charging": true}
AUTO_DETECT_IS_GETTERS is a mapper feature that determines whether "is getter" methods are automatically detected based on standard Bean naming convention or not. If yes, then all public zero-argument methods that start with prefix "is", and whose return type is boolean are considered as "is getters". If disabled, only methods explicitly annotated are considered getters.
By default the feature is enabled. You can disable it while configuring your object mapper. Use,
disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
which is method in ObjectMapper class

JAXB: Intercept during unmarshalling?

I've got a typical web service using JAX-RS and JAXB, and upon unmarshalling I would like to know which setters were explicitly called by JAXB. This effectively lets me know which elements were included in the document provided by the caller.
I know I can probably solve this with an XmlAdapter, but I have a lot of classes in a number of different packages, and I don't want to create adapters for each and every one of them. Nor do I want to put hooks into each and every setter. I would like a general solution if possible. Note that all of my classes are setup to use getters and setters; none of them use fields for the access type.
My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there's anything that can be leveraged from any of those, that's all the better. Our original thought was we could dynamically create a factory class (akin to what #XmlType supports) that would return a proxy object that would intercept the setters. We thought we could make this happen using the MetadataSource concept in MOXy, but that does not seem to be possible.
Anyone have any ideas?
My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there's
anything that can be leveraged from any of those, that's all the
better.
Create your own EclipseLink AttributeAccessor
MOXy (which is a component of EclipseLink) leverages a class called AttributeAccessor to do operations with fields and properties. You could wrap this class to capture all the information that you need.
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.mappings.AttributeAccessor;
public class MyAttributeAccessor extends AttributeAccessor {
private AttributeAccessor attributeAccessor;
public MyAttributeAccessor(AttributeAccessor attributeAccessor) {
this.attributeAccessor = attributeAccessor;
}
#Override
public Object getAttributeValueFromObject(Object domainObject)
throws DescriptorException {
return attributeAccessor.getAttributeValueFromObject(domainObject);
}
#Override
public void setAttributeValueInObject(Object domainObject, Object value)
throws DescriptorException {
System.out.println("Thread: " + Thread.currentThread().getId() + " - Set value: " + value + " on property: " + attributeAccessor.getAttributeName() + " for object: " + domainObject);
attributeAccessor.setAttributeValueInObject(domainObject, value);
}
}
Tell MOXy to use your AttributeAccessor
We can leverage a SessionEventListener to access the underlying metadata to specify your implementation of AttributeAccessor. This is passed in as a property when creating the JAXBContext.
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() {
#Override
public void postLogin(SessionEvent event) {
Project project = event.getSession().getProject();
for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
for(DatabaseMapping mapping : descriptor.getMappings()) {
mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor()));
}
}
super.preLogin(event);
}
});
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Leverage a JAX-RS ContextResolver when Creating the JAXBContext
Since you are in a JAX-RS environment you can leverage a ContextResolver to control how the JAXBContext is created.
http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html
Standalone Example
Java Model (Foo)
Below is a sample class where we will use field access (no setters).
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
private String bar;
private String baz;
}
Demo
import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.sessions.*;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() {
#Override
public void postLogin(SessionEvent event) {
Project project = event.getSession().getProject();
for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
for(DatabaseMapping mapping : descriptor.getMappings()) {
mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor()));
}
}
super.preLogin(event);
}
});
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<foo><bar>Hello World</bar></foo>");
Foo foo = (Foo) unmarshaller.unmarshal(xml);
}
}
Output
Thread: 1 - Set value: Hello World on property: bar for object: forum21044956.Foo#37e47e38
UPDATE
So this works, but I have a few issues. First, the domainObject is
always logging as 0 in my system. Not sure why that's occurring.
I have not idea why that is occuring, may need to check the toString() for the object you are logging.
Second, I am not able to tell if the property in question is on the
top-level item that is being unmarshalled or on a sub-element. That's
actually quite annoying.
You will need to beef up the logic here. Based on the objects being set you should be able to do what you want.
Third, your solution is per JAXBContext, but I don't know if I really
want to create a new context for every request. Isn't that bad from an
overhead perspective?
You can cache the created JAXBContext to prevent rebuilding it.

Using #JsonView with Spring MVC

I am using the following bean definition to make my spring app talking in JSON
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
Is it possible with this message converter bean to use the #JsonView annotation?
#JsonView is already supported in the Jackson JSON Processor from v1.4 onwards.
New Edit: Updated for Jackson 1.9.12
According to the v1.8.4 documentation the function I was using writeValueUsingView is now Deprecated Use ObjectMapper.viewWriter(java.lang.Class) instead… however that has also been Deprecated Since 1.9, use writerWithView(Class) instead! (see v1.9.9 documentation)
So here is an updated example, tested with Spring 3.2.0 and Jackson 1.9.12 which simply returns {id: 1} and not the extended {name: "name"} since it is using the .writerWithView(Views.Public.class). Switching to Views.ExtendPublic.class will result in {"id":1,"name":"name"}
package com.demo.app;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.codehaus.jackson.map.annotate.JsonView;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Controller
public class DemoController {
private final ObjectMapper objectMapper = new ObjectMapper();
#RequestMapping(value="/jsonOutput")
#ResponseBody
public String myObject(HttpServletResponse response) throws IOException {
ObjectWriter objectWriter = objectMapper.writerWithView(Views.Public.class);
return objectWriter.writeValueAsString(new MyObject());
}
public static class Views {
static class Public {}
static class ExtendPublic extends Public {}
}
public class MyObject {
#JsonView(Views.Public.class) Integer id = 1;
#JsonView(Views.ExtendPublic.class) String name = "name";
}
}
Previous Edit: You need to instantiate the ObjectMapper and write out the object using a custom view as shown here, or in this example:
Define views:
class Views {
static class Public {}
static class ExtendedPublic extends PublicView {}
...
}
public class Thing {
#JsonView(Views.Public.class) Integer id;
#JsonView(Views.ExtendPublic.class) String name;
}
Use views:
private final ObjectMapper objectMapper = new ObjectMapper();
#RequestMapping(value = "/thing/{id}")
public void getThing(#PathVariable final String id, HttpServletResponse response) {
Thing thing = new Thing();
objectMapper.writeValueUsingView(response.getWriter(), thing, Views.ExtendPublic.class);
}
If you are using Jackson >= 1.7 you might find that the #JSONFilter better suits your needs.
#JsonView annotation was not supported on Spring but this issue is solved!
Follow this
Add support for Jackson serialization views
Spring MVC now supports Jackon's serialization views for rendering
different subsets of the same POJO from different controller
methods (e.g. detailed page vs summary view).
Issue: SPR-7156
This is the SPR-7156.
Status: Resolved
Description
Jackson's JSONView annotation allows the developer to control which aspects of a method are serialiazed. With the current implementation, the Jackson view writer must be used but then the content type is not available. It would be better if as part of the RequestBody annotation, a JSONView could be specified.
Available on Spring ver >= 4.1
Thank you Spring!

Why are names returned with # in JSON using Jersey

I am using the JAXB that is part of the Jersey JAX-RS. When I request JSON for my output type, all my attribute names start with an asterisk like this,
This object;
package com.ups.crd.data.objects;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
#XmlType
public class ResponseDetails {
#XmlAttribute public String ReturnCode = "";
#XmlAttribute public String StatusMessage = "";
#XmlAttribute public String TransactionDate ="";
}
becomes this,
{"ResponseDetails":{"#transactionDate":"07-12-2010",
"#statusMessage":"Successful","#returnCode":"0"}
So, why are there # in the name?
Any properties mapped with #XmlAttribute will be prefixed with '#' in JSON. If you want to remove it simply annotated your property with #XmlElement.
Presumably this is to avoid potential name conflicts:
#XmlAttribute(name="foo") public String prop1; // maps to #foo in JSON
#XmlElement(name="foo") public String prop2; // maps to foo in JSON
If you are marshalling to both XML and JSON, and you don't need it as an attribute in the XML version then suggestion to use #XmlElement is the best way to go.
However, if it needs to be an attribute (rather than an element) in the XML version, you do have a fairly easy alternative.
You can easily setup a JSONConfiguration that turns off the insertion of the "#".
It would look something like this:
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBContextResolver() throws Exception {
this.context= new JSONJAXBContext(
JSONConfiguration
.mapped()
.attributeAsElement("StatusMessage",...)
.build(),
ResponseDetails.class);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
There are also some other alternatives document here:
http://jersey.java.net/nonav/documentation/latest/json.html
You have to set JSON_ATTRIBUTE_PREFIX in your JAXBContext configuration to "" which by default is "#":
properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "");

Categories