Spring boot jackson - deserializes Json with root name - json

I have the below Json
{
"user": {
"name": "Ram",
"age": 27
}
}
which I want to de-serialize into an instance of the class
public class User {
private String name;
private int age;
// getters & setters
}
For this, I have used #JsonRootName on class name and something like below
#Configuration
public class JacksonConfig {
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToEnable(DeserializationFeature.UNWRAP_ROOT_VALUE);
return builder;
}
}
But it did not work as expected. If I send something like below, it worked.
{
"name": "Ram",
"age": 27
}
But I want to get the json de-serialized with root name. Can any one please suggest?
I want to spring boot way of doing this.

#JsonRootName is a good start. Use this annotation on User class and then enable UNWRAP_ROOT_VALUE deserialization feature by adding:
spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true
to your application.properties.
Read more about customizing Jackson mapper in Spring Boot Reference

Using ObjectMapper you can resolve this issue easily. Here's what you have to do :
- Annotate User class as given below
#JsonRootName("user")
public class User {
private String name;
private int age;
// getters & setters
}
Create CustomJsonMapper class
public class CustomJsonMapper extends ObjectMapper {
private DeserializationFeature deserializationFeature;
public void setDeserializationFeature (DeserializationFeature deserializationFeature) {
this.deserializationFeature = deserializationFeature;
enable(this.deserializationFeature);
}
}
Equivalent Spring configuration
<bean id="objectMapper" class=" com.cognizant.tranzform.notification.constant.CustomJsonMapper">
<property name="deserializationFeature" ref="deserializationFeature"/>
</bean>
<bean id="deserializationFeature" class="com.fasterxml.jackson.databind.DeserializationFeature"
factory-method="valueOf">
<constructor-arg>
<value>UNWRAP_ROOT_VALUE</value>
</constructor-arg>
</bean>
Using following code you can test
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
ObjectMapper objectMapper = (ObjectMapper) context
.getBean("objectMapper");
String json = "{\"user\":{ \"name\": \"Ram\",\"age\": 27}}";
User user = objectMapper.readValue(json, User.class);

Related

How to combine #JsonView with #JsonProperty?

I have a DTO class that should serve json via a spring-mvc #RestController.
I want to provide different version/views on the same object. Especially, there are fields that are only used in VERSION_1 of the api, and some only in VERSION_2.
Problem: I could add #JsonView for this, but my goal is also to rename those fields. Some fields should actually replace the same name from previous versions.
Example:
public class Person {
#JsonView(View.Version_1.class)
#JsonProperty("name")
private String name; //eg only the firstname
#JsonView(View.Version_2.class)
#JsonProperty("name")
private NameDTO namedto; //now changing to first+last name
static class NameDTO {
private String firstname;
private String lastname;
}
}
#RestController
public class MyServlet {
#GetMapping("/person/{id}")
#JsonView(View.Version_1.class)
public PersonDTO person1(int id) {
//...
}
#GetMapping("/person_new/{id}")
#JsonView(View.Version_2.class)
public PersonDTO person2(int id) {
//...
}
}
So, depending on the view/version, you would get the same json field firstname, but with different content.
In this example, using V1 would give:
{"name": "john"}
Whereas using V2 should result in:
{"name": {"firstname": "john", "lastname": "doe"}}
BUT not with he code above, as jackson complains:
com.fasterxml.jackson.databind.JsonMappingException: Conflicting
getter definitions for property "name".
Is that possible at all?
I found a way using:
https://github.com/jonpeterson/spring-webmvc-model-versioning
Basic idea is to add a custom VersionedModelConverter that is applied on #VersionedModelConverter annotated webservice response classes.
#Configuration
#Import(VersionedModelResponseBodyAdvice.class)
public class SpringMvcVersioningConfiguration {
//register in jackson. spring-boot automatically registers any module beans
#Bean
public Model versioningModel() {
return new VersioningModule();
}
}
#GetMapping
#VersionedResponseBody(defaultVersion = "2.0")
public Person person() {
}
#JsonVersionedModel(currentVersion = "3.0" toPastConverterClass = PersonConverter.class)
public class Person {
}
public class PersonConverter implements VersionedModelConverter {
#Override
public ObjectNode convert(ObjectNode modelData, String modelVersion, String targetModelVersion, JsonNodeFactory nodeFactory) {
Double modelv = Double.valueOf(modelVersion);
Double targetv = Double.valueOf(targetVersion);
//todo if-else based on model version
Object node = modelData.remove("fieldname");
//node.change...
modelData.set("fieldname_renamed", node);
}
}

JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value

I am new to Spring Data REST project and I am trying to create my first RESTful service. The task is simple, but I am stuck.
I want to perform CRUD operations on a user data stored in an embedded database using RESTful API.
But I cannot figure out how to make the Spring framework process the birthData as "1999-12-15" and store it as a LocalDate. The #JsonFormat annotation does not help.
At present I get the error:
HTTP/1.1 400
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 24 Aug 2017 13:36:51 GMT
Connection: close
{"cause":{"cause":null,"message":"Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n
at [Source: org.apache.catalina.connector.CoyoteInputStream#4ee2a60e;
line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"},
"message":"JSON parse error: Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value ('1999-10-10'); nested exception is com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n
at [Source: org.apache.catalina.connector.CoyoteInputStream#4ee2a60e; line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"}
How to make it work, so that client calls like:
curl -i -X POST -H "Content-Type:application/json" -d "{ \"firstName\" : \"John\", \"lastName\" : \"Johnson\", \"birthDate\" : \"1999-10-10\", \"email\" : \"john#example.com\" }" http://localhost:8080/users
will actually store the entity into the database.
Below is the information about the classes.
The user class:
package ru.zavanton.entities;
import com.fasterxml.jackson.annotation.JsonFormat;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDate;
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
private String email;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
The UserRepository class:
package ru.zavanton.repositories;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import ru.zavanton.entities.User;
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
User findByEmail(#Param("email") String email);
}
Application class:
package ru.zavanton;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You need jackson dependency for this serialization and deserialization.
Add this dependency:
Gradle:
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4")
Maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
After that, You need to tell Jackson ObjectMapper to use JavaTimeModule.
To do that,
Autowire ObjectMapper in the main class and register JavaTimeModule to it.
import javax.annotation.PostConstruct;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
#SpringBootApplication
public class MockEmployeeApplication {
#Autowired
private ObjectMapper objectMapper;
public static void main(String[] args) {
SpringApplication.run(MockEmployeeApplication.class, args);
}
#PostConstruct
public void setUp() {
objectMapper.registerModule(new JavaTimeModule());
}
}
After that,
Your LocalDate and LocalDateTime should be serialized and deserialized correctly.
Spring Boot 2.2.2 / Gradle:
Gradle (build.gradle):
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
Entity (User.class):
LocalDate dateOfBirth;
Code:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
User user = mapper.readValue(json, User.class);
I had a similar issue which I solved by making two changes
Added below entry in application.yaml file
spring:
jackson:
serialization.write_dates_as_timestamps: false
Add below two annotations to the POJO's LocalDate field
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
Example
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class Customer {
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
protected LocalDate birthdate;
}
Eample request format:
{"birthdate": "2019-11-28"}
Example request format as array
{"birthdate":[2019,11,18]}
As it turns out, one should not forget to include jacson dependency into the pom file. This solved the issue for me:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Well, what I do on every project is a mix of the options above.
First, add the jsr310 dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Important detail: put this dependency on the top of your depedencies list. I already see a project where the Localdate error persists even with this dependency on the pom.xml. But changing the order of the depedency the error was gone.
On your /src/main/resources/application.yml file, setup the write-dates-as-timestamps property:
spring:
jackson:
serialization:
write-dates-as-timestamps: false
And create a ObjectMapper bean as this:
#Configuration
public class WebConfigurer {
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}
}
Following this configuration, the conversion always work on Spring Boot 1.5.x without any error.
Bonus: Spring AMQP Queue configuration
Working with Spring AMQP, pay attention if you have a new instance of Jackson2JsonMessageConverter (common thing when creating a SimpleRabbitListenerContainerFactory). You need to pass the ObjectMapper bean to it, like:
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);
Otherwise, you will receive the same error.
I have just wrestled with this for 3 hours. I credit the answer from Dherik (Bonus material about AMQP) for bringing me within striking distance of MY answer, YMMV.
I registered the JavaTimeModule in my object mapper in my SpringBootApplication like this:
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
However my Instants that were coming over the STOMP connection were still not deserialising. Then I realised I had inadvertantly created a MappingJackson2MessageConverter which creates a second ObjectMapper. So I guess the moral of the story is: Are you sure you have adjusted all your ObjectMappers? In my case I replaced the MappingJackson2MessageConverter.objectMapper with the outer version that has the JavaTimeModule registered, and all is well:
#Autowired
ObjectMapper objectMapper;
#Bean
public WebSocketStompClient webSocketStompClient(WebSocketClient webSocketClient,
StompSessionHandler stompSessionHandler) {
WebSocketStompClient webSocketStompClient = new WebSocketStompClient(webSocketClient);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(objectMapper);
webSocketStompClient.setMessageConverter(converter);
webSocketStompClient.connect("http://localhost:8080/myapp", stompSessionHandler);
return webSocketStompClient;
}

Spring boot Jackson Json Serialization for List implementation class (PagedList)

Is there any way to add serialization for list implementing class having custom attributes?
I am working on Rest service using Spring-boot 1.3. I have to return JSON response as Paged-List or Normal-List, depend on request on Controller. So, I have to keep return type of controller method as generic public List<Employee> getEmployees(int departmentId)
I am implementing list as below (using generics to use for different object lists)
public class PagedList<E> implements List<E> {
private List<E> list;
private long totalRecords; //Getter-setters are added
public PagedList(List<E> list) {
super();
this.list = list;
}
public PagedList(List<E> list, long totalRecords) {
super();
this.list = list;
this.totalRecords = totalRecords;
}
#Override
public boolean add(E element) {
return this.list.add(element);
}
//All other List abstract methods implemented same as above using this.list
}
Added JsonSerializer for same: public class PagedListSerializer extends JsonSerializer<PagedList> with serialization logic in serialize() method. Which is registered using spring-boot jackson customization :
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializerByType(PagedList.class, new PagedListSerializer());
return builder;
}
When I try to return PagedList<Employee>(list, 1000), I am not able to get following response. Its returning same as of normal list. Not executing custom serialization. How to get following paged response?
{
list : [{employeeId: "1", name: "John" }, ... ],
totalRecords : 1000
}
You probably don't need custom deserializer to get this json. Just add #JsonFormat(shape = JsonFormat.Shape.OBJECT) annotation to your class:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static class PagedList<E> implements List<E> {
#JsonProperty
private List<E> list;
#JsonProperty // no need for this if you have getter-setters
private long totalRecords;
#JsonIgnore
#Override
public boolean isEmpty() {
return false;
}
...
Here is full demo: https://gist.github.com/varren/35c4ede769499b1290f98e39a2f85589
Update after comments:
I think Spring uses Jacksons return mapper.writerFor(List.class).writeValueAsString(new MyList()); Here is demo:
#RestController
#RequestMapping(value="/")
public static class MyRestController {
private static final ObjectMapper mapper = new ObjectMapper();
//returns [] for both 0 and 1
#RequestMapping(value="test", method = RequestMethod.GET)
public List test(#RequestParam int user) {
return user == 0 ? new ArrayList(): new MyList();
}
//returns [] for 0 and expected custom {"empty": true} for 1
#RequestMapping(value="testObj", method = RequestMethod.GET)
public Object testObj(#RequestParam int user) {
return user == 0 ? new ArrayList(): new MyList();
}
// returns expected custom {"empty": true}
#RequestMapping(value="testMyList", method = RequestMethod.GET)
public MyList testMyList() {
return new MyList();
}
// returns expected custom {"empty": true}
#RequestMapping(value="testMyListMapper", method = RequestMethod.GET)
public String testMyListMapper() throws JsonProcessingException {
return mapper.writeValueAsString(new MyList());
}
// returns []
#RequestMapping(value="testMyListMapperListWriter", method = RequestMethod.GET)
public String testMyListMapperListWriter() throws JsonProcessingException {
return mapper.writerFor(List.class).writeValueAsString(new MyList());
}
}
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static class MyList extends ArrayList {}
So you have to Option 1) return Object instead of List or Option 2) register custom serialifer for List (and not for PageList) builder.serializerByType(List.class, new PagedListSerializer()); like this:
public class PagedListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List valueObj, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if (valueObj instanceof PagedList) {
PagedList value = (PagedList) valueObj;
gen.writeStartObject();
gen.writeNumberField("totalRecords", value.getTotalRecords());
gen.writeObjectField("list", value.getList());
gen.writeEndObject();
}else{
gen.writeStartArray();
for(Object obj : valueObj)
gen.writeObject(obj);
gen.writeEndArray();
}
}
}
You can Create your customObject Mapper and use your serializer there.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="custom.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Spring boot Jersey Jackson

I have a question related to the Jackson configuration on my Spring boot project
As described on spring boot blog
I try to customize my Object serialization.
After added a new config bean in my config
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return builder;
}
When I try to output an instance of my class User the json result is not in CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
Class User {
private String firstName = "Joe Blow";
public String getFirstName() {
return firstName;
}
}
json output is :
{
"firstName": "Joe Blow"
}
and not
{
"first_name": "Joe Blow"
}
Maybe I need to register something in my Jersey config to activate my custom obejctMapper Config
#Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("my.package);
}
}
Thanks
The general way to configure the ObjectMapper for JAX-RS/Jersey applications is use a ContextResolver. For example
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
It should be picked up with the package scan, or you can explicitly register it, if it's not within the package scope
public JerseyConfig() {
register(new ObjectMapperContextResolver());
// Or if there's is an injection required
// register it as a .class instead of instance
}
The ContextResolver is called during the marshalling and unmarshalling. The class/type being serialzed or deserialized into will be passed to the getContext method. So you could even use more than one mapper for different types, or even more use cases.
UPDATE
Starting from Spring Boot 1.4, you can just create an ObjectMapper Spring bean, and Spring Boot will create the ContextResolver for you, and use your ObjectMapper
// in your `#Configuration` file.
#Bean
public ObjectMapper mapper() {}

Spring and JacksonJson, serialising different fields with views

In a previous similar question, I asked about, how to serialise two different sets of fields using JacksonJson and Spring.
My use case is the typical Controller mapping with #ResponseBody annotation returning directly a particular object or collections of objects, that are then rendered with JacksonJson whenever the client adds application/json in the Accept header.
I had two answers, the first one suggests to return different interfaces with a different getter list, the second suggests to use Json Views.
I don't have problems to understand the first way, however, for the second, after reading the documentation on JacksonJsonViews, I don't know how to implement it with Spring.
To stay with the example, I would declare three stub classes, inside the class Views:
// View definitions:
public class Views {
public static class Public { }
public static class ExtendedPublic extends PublicView { }
public static class Internal extends ExtendedPublicView { }
}
Then I've to declare the classes mentioned:
public class PublicView { }
public class ExtendedPublicView { }
Why on earth they declare empty static classes and external empty classes, I don't know. I understand that they need a "label", but then the static members of Views would be enough. And it's not that ExtendedPublic extends Public, as it would be logical, but they are in fact totally unrelated.
And finally the bean will specify with annotation the view or list of views:
//changed other classes to String for simplicity and fixed typo
//in classname, the values are hardcoded, just for testing
public class Bean {
// Name is public
#JsonView(Views.Public.class)
String name = "just testing";
// Address semi-public
#JsonView(Views.ExtendedPublic.class)
String address = "address";
// SSN only for internal usage
#JsonView(Views.Internal.class)
String ssn = "32342342";
}
Finally in the Spring Controller, I've to think how to change the original mapping of my test bean:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() {
return new Bean();
}
It says to call:
//or, starting with 1.5, more convenient (ObjectWriter is reusable too)
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);
So I have an ObjectMapper instance coming out of nowhere and an out which is not the servlet typical PrintWriter out = response.getWriter();, but is an instance of JsonGenerator and that can't be obtained with the new operator. So I don't know how to modify the method, here is an incomplete try:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator out; //how to create?
objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean());
return ??; //what should I return?
}
So I would like to know if anybody had success using JsonView with Spring and how he/she did. The whole concept seems interesting, but the documentation seems lacking, also the example code is missing.
If it's not possible I will just use interfaces extending each others. Sorry for the long question.
Based on the answers by #igbopie and #chrislovecnm, I've put together an annotation driven solution:
#Controller
public class BookService
{
#RequestMapping("/books")
#ResponseView(SummaryView.class)
public #ResponseBody List<Book> getBookSummaries() {}
#RequestMapping("/books/{bookId}")
public #ResponseBody Book getBook(#PathVariable("bookId") Long BookId) {}
}
Where SummaryView is annotated on the Book model like so:
#Data
class Book extends BaseEntity
{
#JsonView(SummaryView.class)
private String title;
#JsonView(SummaryView.class)
private String author;
private String review;
public static interface SummaryView extends BaseView {}
}
#Data
public class BaseEntity
{
#JsonView(BaseView.class)
private Long id;
}
public interface BaseView {}
A custom HandlerMethodReturnValueHandler is then wired into Spring MVC's context to detect the #ResponseView annotation, and apply the Jackson view accordingly.
I've supplied full code over on my blog.
You need to manually wire in the MappingJacksonHttpMessageConverter. In spring 3.1 you are able to use the mvc xml tags like the following:
<mvc:annotation-driven >
<mvc:message-converter>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
It is pretty ugly to not use spring 3.1, it will save you about 20 lines of xml. The mvc:annotation tag does ALOT.
You will need to wire in the object mapper with the correct view writer. I have noticed recently the using a #Configuration class can make complicated wiring like this a lot easier. Use a #Configuration class and create a #Bean with your MappingJacksonHttpMessageConverter, and wire the reference to that bean instead of the MappingJacksonHttpMessageConverter above.
I've manage to solve the problem this way:
Create custom abstract class to contain the json response object:
public abstract AbstractJson<E>{
#JsonView(Views.Public.class)
private E responseObject;
public E getResponseObject() {
return responseObject;
}
public void setResponseObject(E responseObject) {
this.responseObject = responseObject;
}
}
Create a class for each visibility (just to mark the response):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
Change your method declaration:
#RequestMapping(value = "/bean")
#ResponseBody
public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
return new PublicJson(new Bean());
}
Create customs MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public PublicApiResponseMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(PublicJson.class)){
return true;
}
return false;
}
}
public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public ExtendedPublicJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(ExtendedPublicJson.class)){
return true;
}
return false;
}
}
public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public InternalJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(Internal.class)){
return true;
}
return false;
}
}
Add the following to your xml:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="PublicJsonMessageConverter"></bean>
<bean class="ExtendedPublicJsonMessageConverter"></bean>
<bean class="InternalJsonMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
That's it! I had to update to spring 3.1 but that's all. I use the responseObject to send more info about the json call but you can override more methods of the MessageConverter to be completely transparent. I hope someday spring include an annotation for this.
Hope this helps!