In my application I use Jersey REST to serialize complex objects. This works quite fine. But there are a few method which simply return an int or boolean.
Jersey can't handle primitive types (to my knowledge), probably because they're no annotated and Jersey has no default annotation for them. I worked around that by creating complex types like a RestBoolean or RestInteger, which simply hold an int or boolean value and have the appropriate annotations.
Isn't there an easier way than writing these container objects?
Have a look at Genson.It helped me a lot with a similar problem.With Genson you could use generics like int,boolean, lists and so on...Here is a quick example.
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getMagicList() {
List<Object> objList = new ArrayList<>();
stringList.add("Random String");
stringList.add(121); //int
stringList.add(1.22); //double
stringList.add(false); //bolean
return Response.status(Status.OK).entity(objList).build();
}
This will produce a valid JSON witch can be retrieved very simple like this:
Client client = Client.create();
WebResource webResource = client.resource("...path to resource...");
List objList = webResource.accept(MediaType.APPLICATION_JSON).get(ArrayList.class);
for (Object obj : objList) {
System.out.println(obj.getClass());
}
You will see that Genson will help you decode the JSON on the client side also and output the correct class for each.
Are you writing a service or a client? In the service-end of things, you would simply write a MessageBodyWriter to serialize a stream of data to a Java object for your types. In my use cases, the services I'm writing output to JSON or XML, and in XML's case, I just throw one JAXB annotation on the top of my classes and I'm done.
Have you looked at the Jersey User guide regarding this?
3.6. Adding support for new representations
Actually your best bet is to write a custom ContextResolver Provider like the following that uses natural building of JSON.
#Provider
public class YourContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = { YourSpecialBean.class };
public YourContextResolver() throws Exception {
this.context = new JSONJAXBContext(
JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (int i = 0; i < this.types.length; i++)
if (this.types[i].equals(objectType)) return context;
return null;
}
}
The only thing special here to notice is the YourSpecialBean.class in the Class[]. This defines an array of class types that this provider will resolve naturally.
Tell Jersey generate proper JSON documents (natural json). I use same class for rest app and JAXBContext resolver, found it the most clean encapsulation.
Better programmer could implement helper to iterate .class files and list appropriate classes automatically by identifying #Annotation tags. I don't know how to do it runtime in an own source code.
These two links were helpful studying this extra java jargon. I don't know why there is no Jersey parameter to make all just work out of the box.
http://jersey.java.net/nonav/documentation/latest/json.html
http://jersey.java.net/nonav/documentation/latest/chapter_deps.html
https://maven.java.net/content/repositories/releases/com/sun/jersey/jersey-archive/
http://search.maven.org/remotecontent?filepath=asm/asm/3.3.1/asm-3.3.1.jar
https://github.com/rmuller/infomas-asl/
WEB-INF/web.xml (snippet):
<servlet>
<servlet-name>RESTServlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.myapp.rest.RESTApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RESTServlet</servlet-name>
<url-pattern>/servlet/rest/*</url-pattern>
</servlet-mapping>
com.myapp.rest.RESTApplication.java
package com.myapp.rest;
import java.util.*;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.ContextResolver;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
public class RESTApplication extends Application implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types;
public RESTApplication() throws JAXBException {
// list JAXB bean types to be used for REST serialization
types = new Class[] {
com.myapp.rest.MyBean1.class,
com.myapp.rest.MyBean2.class,
};
context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
#Override
public Set<Class<?>> getClasses() {
// list JAXB resource/provider/resolver classes
Set<Class<?>> classes = new HashSet<Class<?>>();
//for(Class<?> type : types)
// classes.add(type);
classes.add(MyBeansResource.class);
classes.add(this.getClass()); // used as a ContextResolver class
return classes;
}
#Override
public JAXBContext getContext(Class<?> objectType) {
// this is called each time when rest path was called by remote client
for (Class<?> type : types) {
if (type==objectType)
return context;
}
return null;
}
}
Classes MyBean1,MyBean2 are plain java objects and MyBeansResource class is the one with #Path rest functions. There is nothing special in them expect standard jaxp #Annotations here and there. After this java jargon JSON documents have
zero or single-element List arrays are always written as json array ([] field)
primitive integers and boolean fields are written as json primitives (without quotations)
I use the following environment
Sun Java JDK1.6.x
Apache Tomcat 6.x
Jersey v1.14 libraries (jersey-archive-1.14.zip)
webapps/myapp/WEB-INF/lib folder has asm-3.3.1.jar, jackson-core-asl.jar, jersey-client.jar, jersey-core.jar, jersey-json.jar, jersey-server.jar, jersey-servlet.jar libraries
add optional annotation-detector.jar if you use infomas-asl discovery tool
jersey-archive.zip had older asm-3.1.jar file, probably works fine but chapter_deps.html links to a newer file. See link list at the top.
Edit
I found an excellent(fast, lightweight just 15KB) annotation discovery tool. See this post about how I autodiscover types at runtime and no longer need to edit RESTApplication each time new java(jaxb) bean is added.
https://github.com/rmuller/infomas-asl/issues/7
I had the same problem today and didn't give up until i found a really good suitable solution. I can not update the jersey library from 1.1.5 it is a Legacy System. My Rest Service returns a List and they should follow those rules.
Empty Lists are rendered as [] (almost impossible)
One Element Lists are rendered as [] (difficult but only mapping configuration)
Many Element Lists are rendered as [] (easy)
Start from easy to impossible.
3) nothing today normal JSON Mapping
2) Register JAXBContextResolver like the following
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class<?>> types;
private Class<?>[] ctypes = { Pojo.class }; //your pojo class
public JAXBContextResolver() throws Exception {
this.types = new HashSet<Class<?>>(Arrays.asList(ctypes));
this.context = new JSONJAXBContext(JSONConfiguration.mapped()
.rootUnwrapping(true)
.arrays("propertyName") //that should rendered as JSONArray even if the List only contain one element but doesn't handle the empty Collection case
.build()
, ctypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
1) The following approach only works for Collections$EmptyList class. May you find a way to make it general for all Collections they are empty. May code deal with EmptyList so.
#Provider
#Produces(value={MediaType.APPLICATION_JSON})
public class EmptyListWriter implements MessageBodyWriter<AbstractList> {
private static final String EMPTY_JSON_ARRAY = "[]";
#Override
public long getSize(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return EMPTY_JSON_ARRAY.length();
}
#Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.getName().equals("java.util.Collections$EmptyList");
}
#Override
public void writeTo(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> headers, OutputStream outputStream) throws IOException, WebApplicationException {
if (list.isEmpty())
outputStream.write(EMPTY_JSON_ARRAY.getBytes());
}
}
I've just discovered that returning a primitive type with Jersey is problematic. I've decided to return String instead. Maybe this is not clean, but I don't think it's too dirty. The Java client, which is written by the same author of the server most of the times, can wrap such a string return value and convert it back to int. Clients written in other languages must be aware of return types any way.
Defining RestInteger, RestBoolean may be another option, however it's more cumbersome and I see too little advantage in it to be attractive.
Or maybe am I missing something important here?
Related
Recently came upon a case where I want up-front but configurable types (long story). Typically I do this
Type t = new TypeToken<ArrayList<Integer>>() {}.getType();
I would like to place that right hand side into my spring properties file and either issue a call similar to this:
Type t = env.getProperty("type-property");
Or use #ConfigurationProperties.
I typically work with #ConfigurationProperties, but the repo I'm in does not have it available. I could make the case to have dependencies pulled in if the first way simply isn't possible, but the path of least resistance is preferable.
I have tried some variations of the following property definition with angle brackets, parentheses, and square brackets --- none worked out.
type-property=java.util.ArrayList<java.lang.Integer>
I've had a hard time getting anything useful with the kinds of search terms you're forced to use with this problem. I found this related question, but didn't have luck translating it to non-xml configuration (I cannot do xml style in this project either). link: Create a Guava TypeToken in Spring xml config?
EDIT:
Using the answer provided by #LppEdd, I used the following calls for de/serialization:
Type sampleType = new TypeToken<HashSet<ArrayList<Integer>>>() {}.getType();
// serialize via java.lang.reflect.Type.getTypeName()
String serializedType = sampleType.getTypeName();
// above string value is "java.util.HashSet<java.util.ArrayList<java.lang.Integer>>"
// deserialize via org.eclipse.jetty.util.TypeUtil.fromName(String name)
Type sampleTypeAgain = TypeUtil.fromName(serializedType);
To answer your "translating to Java" point:
#Bean
#Qualifier("IntegerArrayList")
public Type integerArrayList() {
return new TypeToken<ArrayList<Integer>>() {}.getType();
}
Usage:
#Autowired
#Qualifier("IntegerArrayList")
private Type type;
Alternatively...
I'll get you started here.
The "better" approach would be to use #ConfigurationProperties with a custom PropertySource implementation. All the Type sub-classes should be serializable, so you can store serialized data and deserialize on the fly.
The fields' names, obviously, would correspond to the Type(s) mapping keys.
class TypePropertySource extends PropertySource<TypeSource> {
private final TypeSource source;
TypePropertySource(final TypeSource source) {
super("a-name", source);
this.source = source;
}
#Override
public Object getProperty(final String name) {
try {
return (Type) source.getAndDeserialize(name);
} catch (final Exception e) {
// Recover or do nothing.
}
return null;
}
}
Edit: I really don't know if types other than String are supported for property values in Spring.
Have you considered using Spring SPEL? It almost accomplishes solution for you except due to bad support for generics you need to create separate class for each of your subclass of TypeToken.
For example with concrete class of :
import java.util.ArrayList;
import com.google.common.reflect.TypeToken;
public class MyType extends TypeToken< ArrayList<Integer> >{
}
You can have property like this:
type-property: new com.comcast.viser.ipPreference.web.MyType().getType()
And inject it like this:
#Value("#{${type-property}}")
private java.lang.reflect.Type type;
I'm building a prototype of xAPI LRS and use Java/Jersey to create a test xAPI REST service implementation, recent Jersey version is using MOXy for XML and JSON processing.
Now I'm facing the issue, according to the specification "statements" resource for POST can accept a single JSON statement or a list of statements.
I cannot handle that, because of lack of my MOXy knowledge. I tried different approaches and did not find a solution.
I found similair question here dated 2014, unfortunately it was not answered so far...
Can anyone propose a workaround I would like to keep use MOXy?
I've managed some workaround via ReaderInterceptor
#ArrayWrapper
public class ArrayWrapperInterceptor implements ReaderInterceptor {
public ArrayWrapperInterceptor() {
openingCurlyBrace = Pattern.compile("\\A\\s*\\{");
closingCurlyBrace = Pattern.compile("\\}\\s*\\Z");
}
#Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
InputStream is = context.getInputStream();
String content = "";
while (is.available() != 0) {
byte[] bytes = new byte[is.available()];
is.read(bytes);
content = content + new String(bytes);
}
if (content.length() > 0 && openingCurlyBrace.matcher(content).find() && closingCurlyBrace.matcher(content).find()) {
content = "[" + content + "]";
}
context.setInputStream(new ByteArrayInputStream(content.getBytes()));
return context.proceed();
}
private Pattern openingCurlyBrace;
private Pattern closingCurlyBrace;
}
I defined this annotation
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface ArrayWrapper {}
and put it in both places (at interceptor and at my POST resource method).
In order to make the interceptor work only with #ArrayWrapper annotation, I've added register(ArrayWrapperInterceptor.class) to my application class. Without it Jersey does not aware of it and with #Provider annotation my interceptor is global.
Maybe it's not the best solution but now it looks like it's the only one solution available to me.
Later I'll try to investigate a possibility to use some dynamic object in my resource method (like JSON object) instead of interceptor use.
I want to hook into Jackson's deserialization to optionally deserialize a different JSON document than the one provided. That seems like a really weird use case so let me explain.
I am using the Amazon SQS Extended client to put messages that are too large for SQS on S3 instead and a message that looks like this through SQS
["com.amazon.sqs.javamessaging.MessageS3Pointer",{"s3BucketName":"my-bucket","s3Key":"f5a0fa29-7f9c-4852-8bbb-53697799efe2"}]
An elastic beanstalk worker is listening to the other end of that which means that those messages are POSTed to a Jersey endpoint my application maintains. Since those messages are POSTed instead of using a SQS receiveMessage call the extended client will not fetch the message from S3 itself.
I was thinking it would be pretty clever to make a custom JsonDeserializer that would look at the message to see if it was an S3 pointer, download that file, and deserialize it. Otherwise, just deserialize the provided message. However, that isn't working out quite as smoothly as I hoped.
Here is what I have so far:
public class SQSS3Deserializer<T> extends JsonDeserializer<T> {
private static final String s3PointerHeader = "com.amazon.sqs.javamessaging.MessageS3Pointer";
private Class<T> type;
private ObjectMapper mapper = new ObjectMapper();
public SQSS3Deserializer() {
super();
type = getParameterizedTypeArgument();
}
#Override
public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
if (jp.isExpectedStartArrayToken()) {
jp.nextToken();
if (s3PointerHeader.equals(jp.getValueAsString())) {
jp.nextToken();
S3Pointer p = jp.readValueAs(S3Pointer.class);
return mapper.readValue(S3Utils.getInputStream(p.s3BucketName, p.s3Key), type);
}
}
return jp.readValueAs(type);
}
#SuppressWarnings("unchecked")
protected Class<T> getParameterizedTypeArgument() {
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
static private class S3Pointer {
public String s3BucketName;
public String s3Key;
}
}
For each POJO I want to deserialize I'll have to create an empty subclass with the correct generic specialization, for example:
public class POJOS3Deserializer extends SQSS3Deserializer<POJO> {}
I also will need to add the JsonDeserializer annotation to the class
#JsonDeserialize(using=POJOS3Deserializer.class)
public class POJO { ... }
However, doing it this way causes a stack overflow error because it will continually reenter my deserializer when it calls JsonParser.readValueAs() since readValueAs looks at the JsonDeserialize annotation.
So, I have two questions:
How do I change this to keep this fairly generic and still have Jackson do most of the heavy lifting of parsing while avoiding that recursive call?
Is there a way to remove the need to derive from SQSS3Deserializer for each POJO I want to deserialize this way?
Thanks
My need is to give information about data constraints or default values to the client app that will use the API. The schema or the ALPS generated by Spring Data Rest seems to be a good place to put this information.
But the part about documenting the API is a bit quick in the official reference documentation, and I can't find fully documented example in the community. I've tried to read the code of PersistentEntityToJsonSchemaConverter to have a insight of the offered possibilities, but the headache arrived first.
I know there is the #Description annotation that I can put on Entities and Properties that will change the title field of the schema.
I know the same fields can be modified in rest-messages.properties
Is there other fields that can be modified by annotations or configuration files ?
Putting default or constraints information in this description field really feels like not using it straight.
The question is almost old, I don't know if you have already found a solution.
Anywhere, you can build a completely custom ALPS profiling information if you build two custom converters that replace the converters used by Spring.
The first one needs to extends the converter org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter.
Here a possible implementation:
public class CustomAlpsJsonHttpMessageConverter extends AlpsJsonHttpMessageConverter {
public CustomAlpsJsonHttpMessageConverter(RootResourceInformationToAlpsDescriptorConverter converter) {
super(converter);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return super.canWrite(clazz, mediaType);
}
#Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return super.canRead(type, contextClass, mediaType);
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
return super.beforeBodyWrite(body, returnType, selectedContentType, selectedConverterType, request, response);
}
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return converterType.equals(AlpsJsonHttpMessageConverter.class)
|| converterType.equals(CustomAlpsJsonHttpMessageConverter.class);
}
}
The second one needs to extends the converter org.springframework.data.rest.webmvc.alps.RootResourceInformationToAlpsDescriptorConverter.
The RootResourceInformationToAlpsDescriptorConverter only have two public resources: the constructor and the "convert" method.
You may to overwrite every single private field/method of that class if you want to have a custom behaviour.
Pay attention that the "supports" method of your CustomAlpsJsonHttpMessageConverter will need to matches the given "converterType" with your new CustomAlpsJsonHttpMessageConverter class.
At that point you can customize the "convert" method of the class RootResourceInformationToAlpsDescriptorConverter, simply ovverriding it in your CustomRootResourceInformationToAlpsDescriptorConverter.
Finally, you have to register the two converters in the Application Context. In order to do that, you can extend the RepositoryRestMvcConfiguration class, and in your CustomRepositoryRestMvcConfiguration you will need to #Override the methods "alpsJsonHttpMessageConverter()" and "alpsConverter()".
Add also the #Bean annotation in the two ovverriding custom methods, like this:
#Bean
#Override
public AlpsJsonHttpMessageConverter alpsJsonHttpMessageConverter() {
return new CustomAlpsJsonHttpMessageConverter(alpsConverter());
}
#Bean
#Override
public RootResourceInformationToAlpsDescriptorConverter alpsConverter() {
Repositories repositories = repositories();
PersistentEntities persistentEntities = persistentEntities();
RepositoryEntityLinks entityLinks = entityLinks();
MessageSourceAccessor messageSourceAccessor = resourceDescriptionMessageSourceAccessor();
RepositoryRestConfiguration config = config();
ResourceMappings resourceMappings = resourceMappings();
return new CustomRootResourceInformationToAlpsDescriptorConverter(associationLinks(), repositories, persistentEntities,
entityLinks, messageSourceAccessor, config, objectMapper(), enumTranslator());
}
So you can have a completely custom ALPS, if you need.
I have tried this solution to build custom profiling links, and it works perfectly.
I'm building an application which uses Spring MVC 4.10 with jackson 2.3.2.
I have a Project class which has children Proposal objects and a Customer object. These Proposal objects are complex and I want to return a summarized JSON view of them. A similar situation happens with the Customer object. I'm trying to implement this with #JsonView annotations.
I wanted to ask if extending the views of the member object classes in the container object class view is the way to do this or, if not, if there is a cleaner way to implement this that I am unaware of.
Context
Before today, I was under the false impression that you could annotate your controller with multiple views and that the resulting JSON representation would be filtered accordingly.
#JsonView({Project.Extended.class, Proposal.Summary.class, Customer.Summary.class})
#Transactional
#RequestMapping(value="/project", method=RequestMethod.GET)
public #ResponseBody List<Project> findAll() {
return projectDAO.findAll();
}
Where each class had its own JsonView annotations and interfaces
e.g.:
public class Customer {
...
public interface Summary {}
public interface Normal extends Summary {}
public interface Extended extends Normal {}
}
Nevertheless, it is only the first view in the array that gets taken into account. According to https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
Only one class or interface can be specified with the #JsonView
annotation, but you can use inheritance to represent JSON View
hierarchies (if a field is part of a JSON View, it will be also part
of parent view). For example, this handler method will serialize
fields annotated with #JsonView(View.Summary.class) and
#JsonView(View.SummaryWithRecipients.class):
and the official documentation in http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview
To use it with an #ResponseBody controller method or controller
methods that return ResponseEntity, simply add the #JsonView
annotation with a class argument specifying the view class or
interface to be used:
So, I ended up extending the views of the members in the view of the container object, like this
#Entity
public class Project {
...
public static interface Extended extends Normal, Proposal.Extended {}
public static interface Normal extends Summary, Customer.Normal {}
public static interface Summary {}
}
and changed my controller to this
#JsonView(Project.Extended.class)
#Transactional
#RequestMapping(value="/project", method=RequestMethod.GET)
public #ResponseBody List<Project> findAll() {
return projectDAO.findAll();
}
This seems to do the trick, but I couldn't find documentation or discussion about this situation. Is this the intended use of JsonViews or is it kind of hackish?
Thank you in advance
-Patricio Marrone
I believe you have configured your views as necessary. The root of the issue is not Spring's #JsonView, but rather Jackson's implementation of views. As stated in Jackson's view documentation:
Only single active view per serialization; but due to inheritance of Views, can combine Views via aggregation.
So, it appears that Spring is simply passing on and adhering to the limitation set in place by Jackson 2.
I use Jersey+Jackson but issued just the same problem.
That's a trick that I'm doing for my application to let me require for several JSON Views during serialization. I bet it is also possible with Spring MVC instead of Jersey, but not 100% sure. It also does not seem to have performance issues. Maybe it is a bit complicated for your case, but if you have large object with big amount of possible views, maybe it's better than doing a lot of inheritance.
So I use the Jackson Filter approach to require several views in serialization. However, I haven't found the way to overcome the issue of putting #JsonFilter("name") above the classes to map, which does not make it so clean. But I mask it in custom annotation at least:
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonFilter(JSONUtils.JACKSON_MULTIPLE_VIEWS_FILTER_NAME)
public #interface JsonMultipleViews {}
The filter itself looks like this:
public class JsonMultipleViewsFilter extends SimpleBeanPropertyFilter {
private Collection<Class<?>> wantedViews;
public JsonMultipleViewsFilter(Collection<Class<?>> wantedViews) {
this.wantedViews = wantedViews;
}
#Override
public void serializeAsField( Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer ) throws Exception {
if( include( writer ) ) {
JsonView jsonViewAnnotation = writer.getAnnotation(JsonView.class);
// serialize the field only if there is no #JsonView annotation or, if there is one, check that at least one
// of view classes above the field fits one of required classes. if yes, serialize the field, if no - skip the field
if( jsonViewAnnotation == null || containsJsonViews(jsonViewAnnotation.value()) ) {
writer.serializeAsField( pojo, jgen, provider );
}
}
else if( !jgen.canOmitFields() ) {
// since 2.3
writer.serializeAsOmittedField( pojo, jgen, provider );
}
}
private boolean containsJsonViews(Class<?>[] viewsOfProperty) {
for (Class<?> viewOfProperty : viewsOfProperty) {
for (Class<?> wantedView : wantedViews) {
// check also subclasses of required view class
if (viewOfProperty.isAssignableFrom(wantedView)) {
return true;
}
}
}
return false;
}
#Override
protected boolean include( BeanPropertyWriter writer ) {
return true;
}
#Override
protected boolean include( PropertyWriter writer ) {
return true;
}
}
I can use this filter like this:
public static String toJson( Object object, Collection<Class<?>> jsonViewClasses) throws JsonProcessingException {
// if no json view class is provided, just map without view approach
if (jsonViewClasses.isEmpty()) {
return mapper.writeValueAsString(object);
}
// if only one json view class is provided, use out of the box jackson mechanism for handling json views
if (jsonViewClasses.size() == 1) {
return mapper.writerWithView(jsonViewClasses.iterator().next()).writeValueAsString(object);
}
// if more than one json view class is provided, uses custom filter to serialize with multiple views
JsonMultipleViewsFilter jsonMultipleViewsFilter = new JsonMultipleViewsFilter(jsonViewClasses);
return mapper.writer(new SimpleFilterProvider() // use filter approach when serializing
.setDefaultFilter(jsonMultipleViewsFilter) // set it as default filter in case of error in writing filter name
.addFilter(JACKSON_MULTIPLE_VIEWS_FILTER_NAME, jsonMultipleViewsFilter) // set custom filter for multiple views with name
.setFailOnUnknownId(false)) // if filter is unknown, don't fail, use default one
.writeValueAsString(object);
}
After that, Jersey allows us to add Jersey Filters on the point of running the application (it goes through each endpoint in each Controller in start of application and we can easily bind the Jersey filters at this moment if there is is multiple value in #JsonView annotation above the endpoint).
In Jersey filter for #JsonView annotation with multiple value above endpoint, once it's bint on startup to correct endpoints depending on annotations, we can easily override the response entity with calling that utils method
toJson(previousResponeObjectReturned, viewClassesFromAnnoation);
No reason to provide the code of Jersey Filter here since you're using Spring MVC. I just hope that it's easy to do it the same way in Spring MVC.
The Domain Object would look like this:
#JsonMultipleViews
public class Example
{
private int id;
private String name;
#JsonView(JsonViews.Extended.class)
private String extendedInfo;
#JsonView(JsonViews.Meta.class)
private Date updateDate;
public static class JsonViews {
public interface Min {}
public interface Extended extends Min {}
public interface Meta extends Min {}
//...
public interface All extends Extended, Meta {} // interfaces are needed for multiple inheritence of views
}
}
We can ommit putting Min.class in my case on those fields that are always required not depending on view. We just put Min in required views and it will serialize all fields without #JsonView annotation.
View All.class is required for me since if we have, for example, a specific set of views for each domain class (like in my case) and then we need to map a complex model consisting of several domain objects that both use views approach - some view for object one, but all views for object two, it's easier to put it above endpoint like this:
#JsonView({ObjectOneViews.SomeView.class, ObjectTwoViews.All.class})
because if we ommit ObjectTwoViews.All.class here and require for only ObjectOneViews.SomeView.class, those fields that are marked with annotation in Object Two will not be serialized.