I'm developing a REST webservice in spring MVC. I need to change how jackson 2 serialize mongodb objectids. I'm not sure of what to do because I found partial documentation for jackson 2, what I did is to create a custom serializer:
public class ObjectIdSerializer extends JsonSerializer<ObjectId> {
#Override
public void serialize(ObjectId value, JsonGenerator jsonGen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jsonGen.writeString(value.toString());
}
}
Create a ObjectMapper
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("ObjectIdmodule");
module.addSerializer(ObjectId.class, new ObjectIdSerializer());
this.registerModule(module);
}
}
and then register the mapper
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="my.package.CustomObjectMapper"></bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
My CustomConverter is never called. I think the CustomObjectMapper definition is wrong,I adapted it from some code for jackson 1.x
In my controllers I'm using #ResponseBody.
Where am I doing wrong? Thanks
You should annotate corresponding model field with #JsonSerialize annontation. In your case it may be:
public class MyMongoModel{
#JsonSerialize(using=ObjectIdSerializer.class)
private ObjectId id;
}
But in my opinion, it should better don't use entity models as VOs. Better way is to have different models and map between them.
You can find my example project here (I used date serialization with Spring 3 and Jackson 2 as example).
How I would do this is:
Create an annotation to declare your custom serializers:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyMessageConverter{
}
Set up component scan for this in your mvcconfiguration file
<context:include-filter expression="package.package.MyMessageConverter"
type="annotation" />
and create a class that implements HttpMessageConverter<T>.
#MyMessageConverter
public MyConverter implements HttpMessageConverter<T>{
//do everything that's required for conversion.
}
Create a class that extends AnnotationMethodHandlerAdapter implements InitializingBean.
public MyAnnotationHandler extends AnnotationMethodHandlerAdapter implements InitializingBean{
//Do the stuffs you need to configure the converters
//Scan for your beans that have your specific annotation
//get the list of already registered message converters
//I think the list may be immutable. So, create a new list, including all of the currently configured message converters and add your own.
//Then, set the list back into the "setMessageConverters" method.
}
I believe this is everything that is required for your goal.
Cheers.
There is no need to create object mapper. Add jackson-core-2.0.0.jar and jackson-annotations-2.0.0.jar to your project.
Now, add the following lines of code to your controller while handing the service:
#RequestMapping(value = "students", method = RequestMethod.POST, headers = "Accept=application/json", consumes = "application/json")
public HashMap<String, String> postStudentForm(
#RequestBody Student student, HttpServletResponse response)
Do not miss any of the annotations.
Related
I want to create my own Hibernate custom Validator and what I would like to do is to add custom logic based on some information gathered from a different context (a different Spring bean)
On my custom implementation, I have tried to both add a constructor and define the validator in a spring bean, or to use the Autowire annotation and none of them worked
Autowire example:
public class MyCustomValidator implements ConstraintValidator<CustomConstraint, String> {
#Autowired
private MyCustomChecker customChecker;
#Override
public void initialize(CustomConstraint constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (null == value) {
return true;
}
//get user authenticated properties to perform validation based on the user
AuthenticatedIdentity identity = Context.getAuthenticatedIdentity();
return customChecker.isWhitelisted(identity);
}
}
Constructor example:
public class MyCustomValidator implements ConstraintValidator<CustomConstraint, String> {
private MyCustomChecker customChecker;
public MetricDataSizeValidator(MyCustomChecker customChecker) {
this.customChecker = customChecker;
}
#Override
public void initialize(CustomConstraint constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (null == value) {
return true;
}
//get user authenticated properties to perform validation based on the user
AuthenticatedIdentity identity = Context.getAuthenticatedIdentity();
return customChecker.isWhitelisted(identity);
}
}
I've read around on the official Hibernate doc but that doesn't quite answer my question.
I am pretty sure this is a common issue when you want to validate based on some information based from a different context, however I didn't find around an answer for this.
My app is using Spring DI, where my bean is already initialized like this
<bean id="customChecker" class="com.mycomp.CustomChecker">
<constructor-arg>
<value>arg</value>
</constructor-arg>
</bean>
Is there any example around of how to achieve this?
Update
If I configure my Validator to be:
<bean id="validatorFactory" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean id="validator" factory-bean="validatorFactory"
factory-method="getValidator" />
Now I can see the validation is wired up correctly using spring beans. However, now I'm doubtful whether I'm using HibernateValidator (I think I'm not). Is there any way to achieve the same but configure HibernateValidator factory to use Spring beans?
I assume your MyCustomValidator is not recognized as a managed spring bean, so no autowiring will occur.
The easiest way to make your MyCustomValidator a spring bean is to add the #Component annotation at the class level.
You can use InitBinder in case of Spring MVC where your custom validator will automatically called by the RequestMappingHandlerAdapter.
Simply annotate your method in the controller with #InitBinder and inside the method provide your validator and provide the your validator class to WebDataBinder by calling webDataBinder.setValidator()
I am working on a Spring Boot project. I just have annotation configuration. I want to include dozer to transform Entities to DTO and DTO to Entities. I see in the dozer website, they explain i have to add the following configuration in spring xml configuration file. Since i have not xml file but annotation configuration Java class, i don't know how to translate this into Java Configuration class.
<bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper">
<property name="mappingFiles">
<list>
<value>dozer-global-configuration.xml</value>
<value>dozer-bean-mappings.xml</value>
<value>more-dozer-bean-mappings.xml</value>
</list>
</property>
</bean>
If someone could you give me an example it'll be very useful. Thanks
I think something like this should work:
#Configuration
public class YourConfiguration {
#Bean(name = "org.dozer.Mapper")
public DozerBeanMapper dozerBean() {
List<String> mappingFiles = Arrays.asList(
"dozer-global-configuration.xml",
"dozer-bean-mappings.xml",
"more-dozer-bean-mappings.xml"
);
DozerBeanMapper dozerBean = new DozerBeanMapper();
dozerBean.setMappingFiles(mappingFiles);
return dozerBean;
}
...
}
If you are using DozerBeanMapperFactoryBean instead of DozerBeanMapper you may use something like this.
#Configuration
public class MappingConfiguration {
#Bean
public DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean(#Value("classpath*:mappings/*mappings.xml") Resource[] resources) throws Exception {
final DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean = new DozerBeanMapperFactoryBean();
// Other configurations
dozerBeanMapperFactoryBean.setMappingFiles(resources);
return dozerBeanMapperFactoryBean;
}
}
This way you can import your mappings automatically. Than simple inject your Mapper and use.
#Autowired
private Mapper mapper;
Update with Dozer 5.5.1
In dozer 5.5.1, DozerBeanMapperFactoryBean is removed. So if you want to go with an updated version you need do something like below,
#Bean
public Mapper mapper(#Value(value = "classpath*:mappings/*mappings.xml") Resource[] resourceArray) throws IOException {
List<String> mappingFileUrlList = new ArrayList<>();
for (Resource resource : resourceArray) {
mappingFileUrlList.add(String.valueOf(resource.getURL()));
}
DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
dozerBeanMapper.setMappingFiles(mappingFileUrlList);
return dozerBeanMapper;
}
Now inject mapper as told above
#Autowired
private Mapper mapper;
And use like below example,
mapper.map(source_object, destination.class);
eg.
mapper.map(admin, UserDTO.class);
Just in case someone wants to avoid xml dozer file. You can use a builder directly in java. For me it's the way to go in a annotation Spring context.
See more information at mapping api dozer
#Bean
public DozerBeanMapper mapper() throws Exception {
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.addMapping(objectMappingBuilder);
return mapper;
}
BeanMappingBuilder objectMappingBuilder = new BeanMappingBuilder() {
#Override
protected void configure() {
mapping(Bean1.class, Bean2.class)
.fields("id", "id").fields("name", "name");
}
};
In my case it was more efficient (At least the first time). Didn't do any benchmark or anything.
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);
Lets say I have a list of objects like this: LinkedList<JsonAssessment> jsonAssessments....
It is returned to this kind of method:
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody
List<JsonAssessment> doSomething(....) {
.....
}
I am making AJAX call to this controller everything is working correctly as expected but I don't like the naming my JSON is returned. In firebug I am seeing:
{"LinkedList":[{"assessmentName":"........
The question is how can I rename that root element LinkedList? Is there any config I have to set?
EDIT
I do not want to use any wrapper objects.
EDIT
My ObjectMapper:
public class JsonObjectMapper extends ObjectMapper {
public JsonObjectMapper() {
super();
this.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
this.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
Spring: 3.2.4.RELEASE
Jackson: 2.1.2
EDIT
This object mapper is declared in MVC message converters:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jsonObjectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
And that is what I've tried with naming strategy:
public class JsonPropertyNamingStrategy extends PropertyNamingStrategy {
public static final MyNamingStrategy MY_NAMING_STRATEGY = new MyNamingStrategy();
private static final LinkedHashMap<String, String> PROPERTIES = new LinkedHashMap<String, String>() {{
put("LinkedList", "resultList");
}};
public static class MyNamingStrategy extends PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
if (propertyName == null) {
return null;
}
if (PROPERTIES.containsKey(propertyName)) {
return PROPERTIES.get(propertyName);
}
return propertyName;
}
}
}
I was debugging it and when it comes to translate method property names comes all except the root element. I have everything that LinkedList contains, but LinkedList is not coming to this method.
Instead of directly returning a the List, wrap it inside a ResponseEntity that will give you a response without a root element
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody ResponseEntity<List<JsonAssessment>> doSomething(....) {
.....
return new ResponseEntity(yourList);
}
That way you don't have a root element. If you still want a root element you could add it to a Map. Results in the following JSON "[{"assessmentName":"........]
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody ResponseEntity<Map<String, List<JsonAssessment>>> doSomething(....) {
.....
Map results = new HashMap();
result.put("assesments", yourlist);
return new ResponseEntity(results);
}
Should output {"assesments":[{"assessmentName":"........
Although you are stilling wrapping the objects here, it is in objects that are freely available, you don't have to add your own custom classes.
This is what we are using in a couple of our #Controllers, we are using Spring 3.2 and Jackson 2.2.
put the list in a wrapper class is a a commonly implemented strategy
If you are using Jackson Mapper you can use Annotations to define names of properties in classes by
#JsonProperty("foo")
or set the order by
#JsonPropertyOrder({"foo", "bar"})
and so on.
See further Jackson Annotations
edit:
Sorry, just saw the wrapper-comment. The only solution i saw is using a wrapper like this: How to rename root key in JSON serialization with Jackson
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!