JSON unmarshalling using JAX-RS and MOXy - java

I'm implementing RESTFul web service using Jersey 2.22.1 with MOXY as Json Provider.
For example I have the following entity User:
public class User {
private String id;
private String email;
private Address address;
private List<Phone> phones;
// getters & setters
}
and additional classes
public class Address {
private String type;
private String value;
// getters & setters
}
public class Phone {
private String type;
private String value;
// getters & setters
}
This is my JAX-RS resource implementation:
#POST
public Response create(User user) {
// some logic
}
Now when I'm sending POST request containting following json data:
{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
it works perfectly, MOXY automatically maps this json to user object and it's fine
But I need to handle json with another level of nesting, like this:
{
"user":{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
}
As you can see there is another key called user, and I know it's not a good json structure but it's a requirement and I have to accept it as it is. Now I need to be able to handle it. For now I can see only one solution.
I can add another one class wrapper aroung User and pass it to the create method.
So it would look this:
JAX-RS resource:
#POST
public Response create(UserWrapper user) {
// some logic
}
And java class:
public class UserWrapper {
private User user;
// getters & setters
}
It's working solution but I don't really like it because I need to add one more additional class. Would like to here your suggestions how to keep my java classes as it is and be able to accept json with one more level of nesting (i mean this user key).
Thanks in advance!

May not be the answer you're looking for, but I recommend using Jackson instead of MOXy. It's a more mature JSON framework with more features, and just works better. There may be a way with MOXy, but here is the Jackson way
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.1</version>
</dependency>
<!-- You need to remember to remove MOXy -->
In a ContextResolver, configure the ObjectMapper to unwrap the root value
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper;
public MyObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
private static ObjectMapper createDefaultMapper() {
return mapper;
}
}
The value it will look for to deserialize will either be
The value in a #JsonRootName annotation, e.g. #JsonRootName("user") (on the class)
The value in a #XmlRootElement annotation, e.g. #XmlRootElment(name="user") (on the class)
If there is no annotation, then the name of the class, with the first letter lower cased.
Also not, unless you are using any MOXy specific features, making the switch to Jackson, you probably will not need to make any changes at all to your classes. Jackson also supports JAXB annotations (for the most part).
If you want the response to be wrapped, you can also use
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

Related

Jackson adds backslash in json

I'm building REST service on Jersey and using Jackson to produce JSON from java classes of my model. Model with absolutely simple values, I think this is the most typical case. But I get strange result:
[{\"name\":\"Nick\",\"role\":\"admin\",\"age\":\"32\",\"rating\":47}]
My expecting result:
[{"name":"Nick","role":"admin","age":"32","rating":47}]
My source values of fields does NOT contains any special characters. These are simple words.
There're my Java classes.
Entity:
public class User {
private String name;
private String role;
private String age;
private Integer rating;
Class of rest service:
#ServiceConfig(contextName = "myContext")
#Path("/myrest")
public class MyRestService {
private static final String JSON_CONTENT_TYPE = MediaType.APPLICATION_JSON + ";charset=UTF-8";
#Context
protected HttpServletResponse response;
#GET
#Path("/users")
#OpenTransaction
#Produces({MediaType.APPLICATION_JSON})
public String findUsers(#QueryParam("department") String department) {
response.setContentType(JSON_CONTENT_TYPE);
PDTResponse.status(response).sendStatus(Response.Status.OK.getStatusCode());
List<User> users = new ArrayList<>();
users.add(new User("Nick", "admin", "32", 47));
String jsonInString;
ObjectMapper mapper = new ObjectMapper();
try {
jsonInString = mapper.writeValueAsString(users);
} catch (JsonProcessingException ex) {
jsonInString = "thrown exception: " + ex.getMessage();
}
return jsonInString;
}
I've tried to use annotation #JsonRawValue for string properties:
#JsonRawValue
private String name;
But result in this case was:
[{\"name\":Nick,\"role\":admin,\"age\":32,\"rating\":47}]
And I expect:
[{"name":"Nick","role":"admin","age":"32","rating":47}]
It's obvious that Jackson somehow escapes the quotes in result json of response. But why does it do it, and most importantly how to avoid that? By themselves they are just strings! Without any quotes or special characters.
I use Java 7 and Jackson 2.6.1. And Postman to test result.
Any ideas for fix of my problem?
You can configure the ObjectMapper:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
String jsonUsers = mapper.writeValueAsString(users);
more info here
All strings in java have to escape quotes in them. So jsonInString should have slashes in it. When you output jsonInString though it shouldn't have the quotes. Are you looking at it in a debugger or something?
Do this.
ObjectMapper mapper = new ObjectMapper();
mapper.getFactory().setCharacterEscapes(new JsonUtil().new CustomCharacterEscapes());
ObjectWriter writer = mapper.writer();
String jsonDataObject = mapper.writeValueAsString(configMap);
public class CustomCharacterEscapes extends CharacterEscapes {
private final int[] _asciiEscapes;
public CustomCharacterEscapes() {
_asciiEscapes = standardAsciiEscapesForJSON();
//By default the ascii Escape table in jackson has " added as escape string
//overwriting that here.
_asciiEscapes['"'] = CharacterEscapes.ESCAPE_NONE;
}
#Override
public int[] getEscapeCodesForAscii() {
return _asciiEscapes;
}
#Override
public SerializableString getEscapeSequence(int i) {
return null;
}
}
If you are using Spring and the #ControllerAdvice for JSONP, then create a wrapper for the JSON string and use #JsonRawValue on the property. The JSONP #ControllerAdvice will not wrap a String response, it needs an Object.
public class JsonStringResponse {
#JsonValue
#JsonRawValue
private String value;
public JsonStringResponse(String value) {
this.value = value;
}
}
#GetMapping
public ResponseEntity<JsonStringResponse> getJson() {
String json = "{"id":2}";
return ResponseEntity.ok().body(new JsonStringResponse(json));
}
#ControllerAdvice
public class JsonpControllerAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpControllerAdvice() {
super("callback");
}
}
Response is a json object {"id":2}
If there is a callback parameter the response is callbackparameter({"id":2});
Looks like you are over complicating your JAX-RS resource class.
To use Jackson as a JSON provider for Jersey 2.x, you don't need to create an ObjectMapper instance like that. There's a better way to achieve it. Keep reading for more details.
Adding Jackson module dependencies
To use Jackson 2.x as your JSON provider you need to add jersey-media-json-jackson module to your pom.xml file:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
Registering the Jackson module
Then register the JacksonFeature in your Application / ResourceConfig subclass:
#ApplicationPath("/api")
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(JacksonFeature.class);
return classes;
}
}
#ApplicationPath("/api")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
}
}
If you don't have an Application / ResourceConfig subclass, you can register the JacksonFeature in your web.xml deployment descriptor. The specific resource, provider and feature fully-qualified class names can be provided in a comma-separated value of jersey.config.server.provider.classnames initialization parameter.
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.jackson.JacksonFeature</param-value>
</init-param>
The MessageBodyWriter provided by Jackson is JacksonJsonProvider. For more details on how to use Jackson as a JSON provider, have a look at this answer. If you need to customize the ObjectMapper, refer to this answer.
Fixing your resource class
By using the approach described above, you resource class can be as simple as:
#Path("/users")
public class MyRestService {
#GET
#Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public List<User> findUsers() {
List<User> users = new ArrayList<>();
users.add(new User("Nick", "admin", "32", 47));
return Response.ok(users).build();
}
When requesting such endpoint, it will give you the expected JSON as result.
I have also the same problem and tried different solutions, but non works. The problem is not with the mapper, but with the input to the mapper. As in your case:
jsonInString = mapper.writeValueAsString(users); 'users' is a collection. You need to convert each user to JSONObject, add it to JSONArray and then use the mapper on the array: like this
JSONArray users = new JSONArray();
for (Collection user : usersCollection) {
JSONObject user = new JSONObject(mapper.writeValueAsString(user));
users.put(user);
}
mapper.writeValueAsString(user));
I don't know why, but in my case it works doing this :
private static final String COOKIE_TEMPLATE = "{0}={1};Version={2};Domain={3};Max-Age={4};Path='/'";
response.addHeader("Set-Cookie", MessageFormat.format(COOKIE_TEMPLATE, cookie.getName(),cookie.getValue(), cookie.getVersion(), cookie.getDomain(),Integer.toString(cookie.getMaxAge())));
return ResponseEntity.ok(...);
cookie is a javax.servlet.http.Cookie, and cookie.getValue() contains a string produced by
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
If I use
response.addCookie(cookie)
I have a resulting cookie definition as JSON with backslashes.
But, if I use
response.addHeader("Set-Cookie",MessageFormat(TEMPLATE,cookie.get...))
I managed the same resulting cookie definition as JSON, but without backslashes.
In case of having several cookies, addHeader("Set-Cookie") only creates/updates the desired cookie. The other ones are maintained and won't be altered.
public class StateDate{
#JsonRawValue
Boolean state;
#JsonRawValue
String date;
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false);
try {
return mapper.writeValueAsString(this);
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
I've faced similar issue, Following configuration will help sort the issue:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, false);
For some people who still need an answer if struggling
Try adding #JsonRawValue to the field.
The #JsonRawValue annotation can instruct Jackson to serialize a property exactly as is.
Even I came across this problem today and I stumbled across this question. People have provided multiple ways of removing the backslashes, but the thing is that the problem goes down to the very essence of what we are trying to do here.
We want to return the json response of an api call, but we are returning it as a JSONString formatted in way so that it can be printed, read and understood in Java. When you print it it looks exactly the way you want it to be when you return it.
Long story short, you must return the bytes from the function, not a String. Change the return type to byte[] and return this:
new ObjectMapper().writeValueAsString(response).getBytes(StandardCharset.UTF_8);
This will give you the purest JSON you ever want to read. Mostly, people face this issue when on the other side they are reading from an InputStream and are unable to map it to the same class and it does not work. This is how you'll fix it.
It should not be a problem, just you need to parse it in javascript and use it : JSON.parse(response)

Spring Boot: Wrapping JSON response in dynamic parent objects

I have a REST API specification that talks with back-end microservices, which return the following values:
On "collections" responses (e.g. GET /users) :
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
On "single object" responses (e.g. GET /users/{userUuid}) :
{
user: {
... // {userUuid} user object}
}
}
This approach was chosen so that single responses would be extensible (for example, maybe if GET /users/{userUuid} gets an additional query parameter down the line such at ?detailedView=true we would have additional request information).
Fundamentally, I think it is an OK approach for minimizing breaking changes between API updates. However, translating this model to code is proving very arduous.
Let's say that for single responses, I have the following API model object for a single user:
public class SingleUserResource {
private MicroserviceUserModel user;
public SingleUserResource(MicroserviceUserModel user) {
this.user = user;
}
public String getName() {
return user.getName();
}
// other getters for fields we wish to expose
}
The advantage of this method is that we can expose only the fields from the internally used models for which we have public getters, but not others. Then, for collections responses I would have the following wrapper class:
public class UsersResource extends ResourceSupport {
#JsonProperty("users")
public final List<SingleUserResource> users;
public UsersResource(List<MicroserviceUserModel> users) {
// add each user as a SingleUserResource
}
}
For single object responses, we would have the following:
public class UserResource {
#JsonProperty("user")
public final SingleUserResource user;
public UserResource(SingleUserResource user) {
this.user = user;
}
}
This yields JSON responses which are formatted as per the API specification at the top of this post. The upside of this approach is that we only expose those fields that we want to expose. The heavy downside is that I have a ton of wrapper classes flying around that perform no discernible logical task aside from being read by Jackson to yield a correctly formatted response.
My questions are the following:
How can I possibly generalize this approach? Ideally, I would like to have a single BaseSingularResponse class (and maybe a BaseCollectionsResponse extends ResourceSupport class) that all my models can extend, but seeing how Jackson seems to derive the JSON keys from the object definitions, I would have to user something like Javaassist to add fields to the base response classes at Runtime - a dirty hack that I would like to stay as far away from as humanly possible.
Is there an easier way to accomplish this? Unfortunately, I may have a variable number of top-level JSON objects in the response a year from now, so I cannot use something like Jackson's SerializationConfig.Feature.WRAP_ROOT_VALUE because that wraps everything into a single root-level object (as far as I am aware).
Is there perhaps something like #JsonProperty for class-level (as opposed to just method and field level)?
There are several possibilities.
You can use a java.util.Map:
List<UserResource> userResources = new ArrayList<>();
userResources.add(new UserResource("John"));
userResources.add(new UserResource("Jane"));
userResources.add(new UserResource("Martin"));
Map<String, List<UserResource>> usersMap = new HashMap<String, List<UserResource>>();
usersMap.put("users", userResources);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(usersMap));
You can use ObjectWriter to wrap the response that you can use like below:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
result = writer.writeValueAsString(object);
Here is a proposition for generalizing this serialization.
A class to handle simple object:
public abstract class BaseSingularResponse {
private String root;
protected BaseSingularResponse(String rootName) {
this.root = rootName;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(this);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
A class to handle collection:
public abstract class BaseCollectionsResponse<T extends Collection<?>> {
private String root;
private T collection;
protected BaseCollectionsResponse(String rootName, T aCollection) {
this.root = rootName;
this.collection = aCollection;
}
public T getCollection() {
return collection;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(collection);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
And a sample application:
public class Main {
private static class UsersResource extends BaseCollectionsResponse<ArrayList<UserResource>> {
public UsersResource() {
super("users", new ArrayList<UserResource>());
}
}
private static class UserResource extends BaseSingularResponse {
private String name;
private String id = UUID.randomUUID().toString();
public UserResource(String userName) {
super("user");
this.name = userName;
}
public String getUserName() {
return this.name;
}
public String getUserId() {
return this.id;
}
}
public static void main(String[] args) throws JsonProcessingException {
UsersResource userCollection = new UsersResource();
UserResource user1 = new UserResource("John");
UserResource user2 = new UserResource("Jane");
UserResource user3 = new UserResource("Martin");
System.out.println(user1.serialize());
userCollection.getCollection().add(user1);
userCollection.getCollection().add(user2);
userCollection.getCollection().add(user3);
System.out.println(userCollection.serialize());
}
}
You can also use the Jackson annotation #JsonTypeInfo in a class level
#JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
Personally I don't mind the additional Dto classes, you only need to create them once, and there is little to no maintenance cost. And If you need to do MockMVC tests, you will most likely need the classes to deserialize your JSON responses to verify the results.
As you probably know the Spring framework handles the serialization/deserialization of objects in the HttpMessageConverter Layer, so that is the correct place to change how objects are serialized.
If you don't need to deserialize the responses, it is possible to create a generic wrapper, and a custom HttpMessageConverter (and place it before MappingJackson2HttpMessageConverter in the message converter list). Like this:
public class JSONWrapper {
public final String name;
public final Object object;
public JSONWrapper(String name, Object object) {
this.name = name;
this.object = object;
}
}
public class JSONWrapperHttpMessageConverter extends MappingJackson2HttpMessageConverter {
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// cast is safe because this is only called when supports return true.
JSONWrapper wrapper = (JSONWrapper) object;
Map<String, Object> map = new HashMap<>();
map.put(wrapper.name, wrapper.object);
super.writeInternal(map, type, outputMessage);
}
#Override
protected boolean supports(Class<?> clazz) {
return clazz.equals(JSONWrapper.class);
}
}
You then need to register the custom HttpMessageConverter in the spring configuration which extends WebMvcConfigurerAdapter by overriding configureMessageConverters(). Be aware that doing this disables the default auto detection of converters, so you will probably have to add the default yourself (check the Spring source code for WebMvcConfigurationSupport#addDefaultHttpMessageConverters() to see defaults. if you extend WebMvcConfigurationSupport instead WebMvcConfigurerAdapter you can call addDefaultHttpMessageConverters directly (Personally I prefere using WebMvcConfigurationSupport over WebMvcConfigurerAdapter if I need to customize anything, but there are some minor implications to doing this, which you can probably read about in other articles.
Jackson doesn't have a lot of support for dynamic/variable JSON structures, so any solution that accomplishes something like this is going to be pretty hacky as you mentioned. As far as I know and from what I've seen, the standard and most common method is using wrapper classes like you are currently. The wrapper classes do add up, but if you get creative with your inheretence you may be able to find some commonalities between classes and thus reduce the amount of wrapper classes. Otherwise you might be looking at writing a custom framework.
I guess you are looking for Custom Jackson Serializer. With simple code implementation same object can be serialized in different structures
some example:
https://stackoverflow.com/a/10835504/814304
http://www.davismol.net/2015/05/18/jackson-create-and-register-a-custom-json-serializer-with-stdserializer-and-simplemodule-classes/

XmlElement ignored by Jackson during serialization

i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!

Jackson JSON library: how to instantiate a class with abstract fields that can't access its concrete representation?

This is the same questions than :
Jackson JSON library: how to instantiate a class that contains abstract fields
Nevertheless its solution is not possible since my abstract class is in another project than the concrete one.
Is there a way then ?
EDIT
My architecture is as follows:
public class UserDTO {
...
private LanguageDTO lang;
}
I send that object user :
restTemplate.postForObject(this.getHttpCore().trim() + "admin/user/save/1/" + idUser, userEntity, UserDTO.class);
Then I am supposed to receive it in the function :
#RequestMapping(value = "/save/{admin}/{idUser}", method = RequestMethod.POST)
public String saveUserById(#RequestBody final UserEntity user, #PathVariable Integer idUser, #PathVariable boolean admin)
with UserEntity defined as :
public class UserEntity extends AbstractUserEntity {
...
}
public abstract class AbstractUserEntity {
...
private AbstractLanguageEntity lang;
}
I would like to know how I can specify that lang should be instantiate as LanguageEntity whereas abstract classes are in another project.
This could work assuming you can configure how the object get serialized. See the example here. Look under "1.1. Global default typing" to set the defaults to include extra information in your JSON string, basically the concrete Java type that must be used when deserializing.
Since it seems you need to do this for your Spring servlet, you would have to pass a Spring message converter as mentioned here
Then inside your custom objectMapper, you can do the necessary configuration:
public class JSONMapper extends ObjectMapper {
public JSONMapper() {
this.enableDefaultTyping();
}
}
You could probably also make it work with Mix-ins, which allow you to add annotations to classes already defined. You can see and example here. This will also need to be configured inside the objectMapper.
If you need the same functionality on your client side (REST template), you can pass the object mapper as shown here.
The easiest way to solve that issue is to add getters et setters in UserEntity but specifying a concrete class :
public LanguageEntity getLang() {
return (LanguageEntity) lang;
}
public void setLang(LanguageEntity language){
this.lang = language
}
If all that you want to achieve is to note that LanguageEntity is the implementation of AbstractLanguageEntity, you can register this mapping via module:
SimpleModule myModule = new SimpleModule())
.addAbstractTypeMapping(AbstractLanguageEntity.class,
LanguageEntity.class);
ObjectMapper mapper = new ObjectMapper()
.registerMdoule(myModule);

Filter every JAX-RS resources of an application with a single JsonView

Jackson 2.0 allows to filter JAX-RS resources with a #JsonView.
The following example shows a way to ignore people's age in a resource response.
Unfortunately, every JAX-RS method has to be annotated with #JsonView.
public class View {
public static class Public {}
public static class Private {}
}
public class People {
#JsonView(View.Public) String name;
#JsonView(View.Private) int age;
}
#Path("/people")
public class PeopleResource {
#GET
#JsonView(View.Public)
public List<People> get() {
return peoples.get();
}
#GET
#Path("/id")
#JsonView(View.Public)
public People get(#PathParam("id") int id) {
return people.get(id);
}
}
The only way that I've found to globally filter an application resources is by using a MixIn.
public class PeopleMixIn {
#JsonIgnore int age;
}
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper = new ObjectMapper();
public ObjectMapperProvider() {
mapper.addMixInAnnotations(People.class, PeopleMixIn.class);
}
public ObjectMapper getContext(Class<?> type) { return mapper; }
}
Is there a way to configure Jackson's ObjectMapper with a JsonView? Or applying a filter to every method of a resource/application?
You could sub-class Jackson JAX-RS provider, which is piece of code that handles Resource annotations. Or you can use StreamingOutput to manually use ObjectWriter, where you can specify view to use on per-call basis.
With 2.0, the goal is to reduce state that ObjectMapper has, since changing that state can not be done in thread-safe way, whereas ObjectWriter is fully thread-safe due to immutable nature. But JAX-RS providers do not yet use ObjectWriter / ObjectReader.
Another idea: you could file a Request For Enhancement (RFE) for Jackson JAX-RS provider, to add such feature ("setDefaultView()"?). This would seem like the best solution for your use case? And implementation should be quite simple, so this could go in 2.1.0 release that should be out in a few weeks.

Categories