Custom ObjectMapper and NamingStrategy in Spring 3 MVC - json

I'm using Spring MVC 3 and MappingJacksonHttpMessageConverter in order to get the json data with #ResponseBody. With the default config works ok but now i need to transform the camelCase fields to Pascal casing. For this purpose, i've developed a custom naming strategy:
UpperCaseNamingStrategy.java
public class UpperCaseNamingStrategy extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig config, AnnotatedField field, String defaultName){
return convert(defaultName);
}
#Override
public String nameForGetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName){
return convert(defaultName);
}
#Override
public String nameForSetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName){
return convert(defaultName);
}
public String convert(String defaultName){
char[] arr= defaultName.toCharArray();
if(arr.length != 0){
if(Character.isLowerCase(arr[0])){
arr[0] = Character.toUpperCase(arr[0]);
}
}
return new StringBuilder().append(arr).toString();
}
}
I set my custom strategy to the objectMapper and i set the objectMapper in the converter. These are the beans:
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper">
<property name="propertyNamingStrategy" ref="namingStrategy"/>
</bean>
<bean id="namingStrategy" class="es.unican.meteo.util.UpperCaseNamingStrategy"></bean>
The beans are registered properly because i can see it in the log but when i request the json data the behaviour is the same and the converter method is not called. Do I need more configs?

Following changes are suggested as compared to what I did in my project:
Change mapper bean class to "com.fasterxml.jackson.databind.ObjectMapper". I am using Spring 4.3
add #JsonProperty annotation to the property of class which is being serielized/deseralized
Create default constructors in class which is being serielized/deseralized
Best of Luck!

Related

Spring 3.0 exception converting String to java.util.Date on POST

I'm hoping someone can help me since I have been banging my head against a wall for a couple of days on a issue which seems straightforward and which has been documented in other threads on the web.
I am using Smart GWT client (3.0) in conjunction with Spring 3.1 server and using JSON to communicate (with Jackson API 1.9).
The issue is that when I attempt to save a date from my SmartGWT client and it is sent to the server I get the following exception:
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'comment' on field 'dateAdded': rejected value [2012-06-27T10:57:47+0100]; codes [typeMismatch.comment.dateAdded,typeMismatch.dateAdded,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [comment.dateAdded,dateAdded]; arguments []; default message [dateAdded]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'dateAdded'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type java.util.Date for value '2012-06-27T10:57:47+0100'; nested exception is java.lang.IllegalArgumentException]
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:110)
I have seen this issue in a few other posts, but most relate to not having formatted the Date in the correct format, but I have tried various formats:
- yyyy-MM-dd
- yyyy-MM-dd'T'HH:mm:ssZ
- yyyyMMddHHmmssZ (as per suggestion here: http://code.google.com/p/usersapi/issues/detail?id=8)
So in my code I have done the following:
Configured a CustomObjectMapper:
`
public class CustomObjectMapper extends ObjectMapper {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
public CustomObjectMapper() {
super();
configure(Feature.WRITE_DATES_AS_TIMESTAMPS, false);
setDateFormat(formatter);
getDeserializationConfig().setDateFormat(formatter);
}
}
`
Spring app context thusly:
`
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
<property name="supportedMediaTypes" value="application/json" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<context:component-scan base-package="com.jpmorgan.creditriskreporting.server" />
<bean id="marshallingConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml"/>
</bean>
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
<bean id="jacksonObjectMapper" class="com.jpmorgan.creditriskreporting.server.util.CustomObjectMapper" />
<!-- Client -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<ref bean="marshallingConverter" />
<ref bean="jsonConverter" />
</list>
</property>
</bean>
`
Bean object:
`
import java.util.Date;
#JsonAutoDetect
public class Comment {
private int id;
private String comment;
private Date dateAdded;
public Comment() {}
public Comment(int id) {
this.id = id;
}
...
//#JsonSerialize(using=JsonDateSerializer.class) -- I had previously tried to use these custom Date serializer class
public Date getDateAdded() {
return dateAdded;
}
//#JsonDeserialize(using=JsonDateDeserializer.class)
public void setDateAdded(Date dateAdded) {
this.dateAdded = dateAdded;
}
`
EDIT:
Controller Class
This may be where the issue lies, since when I use #RequestBody it works from my Integration tests, however, my Abstract RestDataSource in SmartGWT only works with #ModelAttribute, so I'm not sure how to proceed.
#RequestMapping(value="/", method=RequestMethod.POST)
public #ResponseBody Comment createNewComment2(#ModelAttribute Comment comment) {
log.info("calling createComment with comment: {}", comment);
comment.setDateAdded(new Date());
Comment added = commentDao.create(comment);
log.info("created comment: {}", added);
return commentDao.get(comment);
}
So I can fetch data from the server and the date is displayed in SmartGWT fine. It's only when I do the add data that I get the issue. From Smart GWT Developer Console:
{
"dataSource":"CommentDS",
"operationType":"add",
"componentId":"isc_DynamicForm_1",
"data":{
"userAdded":"sharper",
"dateAdded":"2012-06-27T10:57:47+0100",
"comment":"sample"
},
"callback":{
"target":[DynamicForm ID:isc_DynamicForm_1],
"methodName":"saveEditorReply"
},
"showPrompt":true,
"prompt":"Saving form...",
"oldValues":{
},
"clientContext":{
},
"requestId":"CommentDS$6272"
}
Any help with this is hugely appreciated.
Cheers,
Steve
I found out the issue thanks to http://vkubushyn.wordpress.com/2011/05/31/smart-gwt-restful-spring-mvc
Had to use Spring's InitBinder
#InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
You should add DateFormat into your model.
#DateTimeFormat(pattern = "dd.MM.yyyy")
private Date beginDate;
#DateTimeFormat(pattern = "dd.MM.yyyy")
private Date endDate;
as a function parameter
void functionName** (#RequestParam("beginDate") #DateTimeFormat(pattern = "dd.MM.yyyy")Date beginDate, #RequestParam("endDate") #DateTimeFormat(pattern = "dd.MM.yyyy")Date endDate)
I might be wrong, but as far as I remember the Z stands for timezone in ISOwhoknowswhatformat. And that's 4 chars wide, so I would try this:
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZ");
By the way: if this is the issue you should've catched it in your unit tests. You do have unit test for CustomObjectMapper don't you? :P

Spring #ResponseBody Jackson JsonSerializer with JodaTime

I have below Serializer for JodaTime handling:
public class JodaDateTimeJsonSerializer extends JsonSerializer<DateTime> {
private static final String dateFormat = ("MM/dd/yyyy");
#Override
public void serialize(DateTime date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = DateTimeFormat.forPattern(dateFormat).print(date);
gen.writeString(formattedDate);
}
}
Then, on each model objects, I do this:
#JsonSerialize(using=JodaDateTimeJsonSerializer.class )
public DateTime getEffectiveDate() {
return effectiveDate;
}
With above settings, #ResponseBody and Jackson Mapper sure works. However, I don't like the idea where I keep writing #JsonSerialize. What I need is a solution without the #JsonSerialize on model objects. Is it possible to write this configuration somewhere in spring xml as a one configuration?
Appreciate your help.
Although you can put an annotation for each date field, is better to do a global configuration for your object mapper. If you use jackson you can configure your spring as follow:
<bean id="jacksonObjectMapper" class="com.company.CustomObjectMapper" />
<bean id="jacksonSerializationConfig" class="org.codehaus.jackson.map.SerializationConfig"
factory-bean="jacksonObjectMapper" factory-method="getSerializationConfig" >
</bean>
For CustomObjectMapper:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
configure(Feature.WRITE_DATES_AS_TIMESTAMPS, false);
setDateFormat(new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)"));
}
}
Of course, SimpleDateFormat can use any format you need.
#Moesio pretty much got it. Here's my config:
<!-- Configures the #Controller programming model -->
<mvc:annotation-driven/>
<!-- Instantiation of the Default serializer in order to configure it -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterConfigurer" init-method="init">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="My Custom ObjectMapper"/>
<bean id="jacksonSerializationConfig" class="org.codehaus.jackson.map.SerializationConfig"
factory-bean="jacksonObjectMapper" factory-method="getSerializationConfig" />
The bit that got me is that <mvc:annotation-driven/> makes its own AnnotationMethodHandler and ignores the one you make manually. I got the BeanPostProcessing idea from http://scottfrederick.blogspot.com/2011/03/customizing-spring-3-mvcannotation.html to configure the one that gets used, and voilĂ ! Works like a charm.
Same using JavaConfig of Spring 3:
#Configuration
#ComponentScan()
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
{
converters.add(0, jsonConverter());
}
#Bean
public MappingJacksonHttpMessageConverter jsonConverter()
{
final MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(new CustomObjectMapper());
return converter;
}
}
If you are using Spring Boot, try this in application.yml :
spring:
jackson:
date-format: yyyy-MM-dd
time-zone: Asia/Shanghai
joda-date-time-format: yyyy-MM-dd
If you simply have the Jackson JARs on your classpath, and return a #ResponseBody, Spring will automatically convert the Model object to JSON. You don't need to annotate anything in the Model to get this to work.

Can #JsonTypeInfo be used with Collections?

Using Spring 3 and Jackson 1.7.6, I can serialize implementations of an abstract class and output the fully-qualified name of the class as a property called #class. This works fine when my Spring controllers return a single instance from a controller annotated with #ResponseBody.
When returning a Collection of the above types the resulting JSON changes according to which type is being serialized (fields from each subclass are present), but it does not include the #class property, which our client code needs.
How can I get this type hint into the serialized JSON when returning a collection?
//Returns complete with #class=com.package.blah
#RequestMapping("/json/getProduct.json")
public #ResponseBody Product getProduct(Integer id)
{
return service.getProduct(id);
}
//Does not include #class
#RequestMapping("/json/getProducts.json")
public #ResponseBody List<Product> getProducts()
{
return service.getProducts();
}
In order to do this you will need to configure ObjectMapper. This is not straightforward via Spring, as rather than settable properties, ObjectMapper has invokable methods that set its state (and then it stores this as a bitmask).
If you are using <mvc:annotation-driven /> you will need to replace it with the equivalent markup, which can be found in the Spring JavaDocs.
Extend ObjectMapper:
public class ConfigurableObjectMapper extends ObjectMapper
{
public ConfigurableObjectMapper()
{
this.enableDefaultTypingAsProperty(DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.Id.CLASS.getDefaultPropertyName());
}
}
Then tell Spring to use an instance of this class instead of the default implementation.
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="validator" ref="validator" />
</bean>
</property>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper">
<bean class="com.blitzgamesstudios.web.common.json.ConfigurableObjectMapper" />
</property>
</bean>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.FormHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
You can use #JsonTypeInfo with POJOs, Collections and Maps, but note that the declared value type of Collections and Maps must be one that has (or inherits) #JsonTypeInfo annotation (when using per-class #JsonTypeInfo annotation). This would not work, for example, if you have type like "Collection" -- in this case, Deejay's answer is correct, as you can force inclusion with "default typing" option.
But things should also work if you have a Collection property to serialize/deserialize, i.e.:
public class Bean {
#JsonTypeInfo(....)
public Collection<Object> listOfObjects; // does work because it's per-property annotation!
// ... also, applies to value type and not Collection type itself
}
since that will override any #JsonTypeInfo annotations value type might otherwise have
I had the problem withjava.util.Map, so I did something like:
public interface MyMap extends Map<Long, Product> {}
and
public class MyHashMap extends HashMap<Long, Product> implements MyMap {}
Found on: http://jackson-users.ning.com/forum/topics/mapper-not-include-type-information-when-serializing-object-why
Object mapper bean can enable default typing:
ObjectMapper mapper = new ObjectMapper()
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
This will give the json output as following:
[
"java.util.ArrayList",
[
{
"#class": "com.xyz.Product",
"name": "myName"
}
]
]

Spring mvc configuration JSON

I would know how works the configuration about Spring MVC rest services that returns JSON.
I have configurated the applicationContenxt.xml in this way:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
</list>
</property>
</bean>
<bean id="contentNegotiatingViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
</list>
</property>
</bean>
<bean class="com.MyController"></bean>
And this is the code of my controller:
#Controller(value="MyController")
public class MyController {
#RequestMapping(value="/getValue", method=RequestMethod.GET)
public ModelAndView getValue() {
Map model = new HashMap();
model.put("asasa", "bbbbb");
model.put("cccc", "ddddd");
return new ModelAndView("jsonView",model);
}
}
I'm missing something about xml configuration or Java code? I have always error 404 while trying to invoke this resource: http://localhost:8080/fss/MyController/getValue
Just do:
#Controller
public class HelloController {
#RequestMapping(value="/hello", method=RequestMethod.GET)
public #ResponseBody String hello(#RequestParam String name) {
return "Hi " + name;
}
}
Change the return type to an object and include jackson in the classpath for an object response.
The request need to have a application/json header for the controller to return json.
Check out http://blog.springsource.com/2010/01/25/ajax-simplifications-in-spring-3-0/
And don't forget to add jackson converter to Spring context file.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
By the way - if your method accepts JSON, then use #RequestBody annotation with incoming data type:
#RequestMapping
public #ResponseBody OutgoingClass getJsonByJson(#RequestBody IncomingClass data) {...}
You can find nice examples of JSON and Spring MVC and more https://sites.google.com/site/upida4j/example

Spring MVC POST #RequestBody don't bind to JSON string in the http request

this is my signature of the POST method of my Spring MVC controller
#RequestMapping(value="/createNewGame", method=RequestMethod.POST)
public ModelAndView createNewGame(#RequestParam(value="phoneNumber") String param,#RequestBody final SampleDTO sampleDTO) {
Map model2 = new HashMap();
model2.put("firstname", "Peter");
model2.put("secondname", "Schmitt");
return new ModelAndView("jsonView", model2);
}
instead this is the definition of the SampleDTO class:
public class SampleDTO implements Serializable{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I'm not able to execute the request for this method. I have this error from the client:
org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.supports(Ljava/lang/Class;)Z
after execute this POST request with RestClient app with these parameters:
http://localhost:8080/SpringExample5/createNewGame.json?phoneNumber=6 (POST)
Content-Type application/json (Header attribute)
{ "value": "a" } (Body)
This is also the configuration of Spring in my web app:
<bean name="/gameController.json"
class="com.alu.server.games.acquisition.controllers.GameController"/>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
<bean id="jsonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" >
<property name="objectMapper">
<ref bean="JacksonObjectMapper" />
</property>
</bean>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonHttpMessageConverter" />
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="objectMapper">
<ref bean="JacksonObjectMapper" />
</property>
</bean>
<bean id="JacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
Someone can help me in order to find the problem?
Thanks in advance !
In the setting I used I did specify the Media Type to be "application/json" both in the web service's annotation as well as the XML configuration for json message converter.
Please checkout my FAQ on the matter here for further details.