Skip to content

Commit 5fb6d6d

Browse files
committed
Allow Jackson builder modules configuration to be customized
Modules (well-known or user provided) registration is now performed first in order to allow their configuration to be customized by more specific ones like custom serializers or deserializers. Issue: SPR-12634
1 parent 6e10f7c commit 5fb6d6d

File tree

5 files changed

+157
-30
lines changed

5 files changed

+157
-30
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ project("spring-web") {
697697
testCompile("org.apache.taglibs:taglibs-standard-jstlel:1.2.1") {
698698
exclude group: "org.apache.taglibs", module: "taglibs-standard-spec"
699699
}
700+
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jackson2Version}")
700701
testRuntime("com.sun.mail:javax.mail:1.5.2")
701702
}
702703

spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,12 @@ public Jackson2ObjectMapperBuilder modules(List<Module> modules) {
389389
}
390390

391391
/**
392-
* Specify one or more modules by class (or class name in XML),
393-
* to be registered with the {@link ObjectMapper}.
394-
* <p>Modules specified here will be registered in combination with
392+
* Specify one or more modules by class to be registered with
393+
* the {@link ObjectMapper}.
394+
* <p>Modules specified here will be registered after
395395
* Spring's autodetection of JSR-310 and Joda-Time, or Jackson's
396-
* finding of modules (see {@link #findModulesViaServiceLoader}).
396+
* finding of modules (see {@link #findModulesViaServiceLoader}),
397+
* allowing to eventually override their configuration.
397398
* <p>Specify either this or {@link #modules}, not both.
398399
* @see com.fasterxml.jackson.databind.Module
399400
*/
@@ -481,6 +482,29 @@ public <T extends ObjectMapper> T build() {
481482
public void configure(ObjectMapper objectMapper) {
482483
Assert.notNull(objectMapper, "ObjectMapper must not be null");
483484

485+
if (this.modules != null) {
486+
// Complete list of modules given
487+
for (Module module : this.modules) {
488+
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
489+
objectMapper.registerModule(module);
490+
}
491+
}
492+
else {
493+
// Combination of modules by class presence in the classpath and class names specified
494+
if (this.findModulesViaServiceLoader) {
495+
// Jackson 2.2+
496+
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
497+
}
498+
else {
499+
registerWellKnownModulesIfAvailable(objectMapper);
500+
}
501+
if (this.modulesToInstall != null) {
502+
for (Class<? extends Module> module : this.modulesToInstall) {
503+
objectMapper.registerModule(BeanUtils.instantiate(module));
504+
}
505+
}
506+
}
507+
484508
if (this.dateFormat != null) {
485509
objectMapper.setDateFormat(this.dateFormat);
486510
}
@@ -511,29 +535,6 @@ public void configure(ObjectMapper objectMapper) {
511535
configureFeature(objectMapper, feature, this.features.get(feature));
512536
}
513537

514-
if (this.modules != null) {
515-
// Complete list of modules given
516-
for (Module module : this.modules) {
517-
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
518-
objectMapper.registerModule(module);
519-
}
520-
}
521-
else {
522-
// Combination of modules by class names specified and class presence in the classpath
523-
if (this.modulesToInstall != null) {
524-
for (Class<? extends Module> module : this.modulesToInstall) {
525-
objectMapper.registerModule(BeanUtils.instantiate(module));
526-
}
527-
}
528-
if (this.findModulesViaServiceLoader) {
529-
// Jackson 2.2+
530-
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
531-
}
532-
else {
533-
registerWellKnownModulesIfAvailable(objectMapper);
534-
}
535-
}
536-
537538
if (this.propertyNamingStrategy != null) {
538539
objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
539540
}

spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,11 +346,12 @@ public void setModules(List<Module> modules) {
346346
}
347347

348348
/**
349-
* Specify one or more modules by class (or class name in XML),
349+
* Specify one or more modules by class (or class name in XML)
350350
* to be registered with the {@link ObjectMapper}.
351-
* <p>Modules specified here will be registered in combination with
351+
* <p>Modules specified here will be registered after
352352
* Spring's autodetection of JSR-310 and Joda-Time, or Jackson's
353-
* finding of modules (see {@link #setFindModulesViaServiceLoader}).
353+
* finding of modules (see {@link #setFindModulesViaServiceLoader}),
354+
* allowing to eventually override their configuration.
354355
* <p>Specify either this or {@link #setModules}, not both.
355356
* @since 4.0.1
356357
* @see com.fasterxml.jackson.databind.Module

spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.http.converter.json;
1818

19+
import java.io.UnsupportedEncodingException;
1920
import java.text.SimpleDateFormat;
21+
import java.util.ArrayList;
2022
import java.util.Arrays;
2123
import java.util.Collections;
2224
import java.util.Date;
@@ -28,6 +30,8 @@
2830
import com.fasterxml.jackson.annotation.JsonInclude;
2931
import com.fasterxml.jackson.core.JsonGenerator;
3032
import com.fasterxml.jackson.core.JsonParser;
33+
import com.fasterxml.jackson.core.JsonProcessingException;
34+
import com.fasterxml.jackson.core.Version;
3135
import com.fasterxml.jackson.databind.DeserializationFeature;
3236
import com.fasterxml.jackson.databind.JsonDeserializer;
3337
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -44,12 +48,17 @@
4448
import com.fasterxml.jackson.databind.deser.std.DateDeserializers;
4549
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
4650
import com.fasterxml.jackson.databind.module.SimpleModule;
51+
import com.fasterxml.jackson.databind.module.SimpleSerializers;
4752
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
4853
import com.fasterxml.jackson.databind.ser.Serializers;
4954
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
5055
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
5156
import com.fasterxml.jackson.databind.type.SimpleType;
5257
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
58+
import com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaDateFormat;
59+
import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer;
60+
import org.joda.time.DateTime;
61+
import org.joda.time.format.DateTimeFormat;
5362
import org.junit.Test;
5463

5564
import org.springframework.beans.FatalBeanException;
@@ -211,6 +220,32 @@ public void setModules() {
211220
assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1);
212221
}
213222

223+
@Test
224+
public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException {
225+
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
226+
DateTime dateTime = DateTime.parse("2011-12-03T10:15:30");
227+
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
228+
}
229+
230+
@Test // SPR-12634
231+
public void customizeDefaultModules() throws JsonProcessingException, UnsupportedEncodingException {
232+
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
233+
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
234+
.modulesToInstall(CustomModule.class).build();
235+
DateTime dateTime = DateTime.parse("2011-12-03T10:15:30");
236+
assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
237+
}
238+
239+
@Test // SPR-12634
240+
public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException {
241+
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
242+
.serializerByType(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC())))
243+
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
244+
DateTime dateTime = DateTime.parse("2011-12-03T10:15:30");
245+
assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
246+
}
247+
248+
214249
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
215250
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
216251
}
@@ -231,6 +266,7 @@ public void propertyNamingStrategy() {
231266
public void serializerByType() {
232267
JsonSerializer<Number> serializer = new NumberSerializer();
233268
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
269+
.modules(new ArrayList<>()) // Disable well-known modules detection
234270
.serializerByType(Boolean.class, serializer).build();
235271
assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers());
236272
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
@@ -241,6 +277,7 @@ public void serializerByType() {
241277
public void deserializerByType() throws JsonMappingException {
242278
JsonDeserializer<Date> deserializer = new DateDeserializers.DateDeserializer();
243279
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
280+
.modules(new ArrayList<>()) // Disable well-known modules detection
244281
.deserializerByType(Date.class, deserializer).build();
245282
assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers());
246283
Deserializers deserializers = getDeserializerFactoryConfig(objectMapper).deserializers().iterator().next();
@@ -284,6 +321,7 @@ public void completeSetup() throws JsonMappingException {
284321
JsonSerializer<Number> serializer2 = new NumberSerializer();
285322

286323
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json()
324+
.modules(new ArrayList<>()) // Disable well-known modules detection
287325
.serializers(serializer1)
288326
.serializersByType(Collections.<Class<?>, JsonSerializer<?>>singletonMap(Boolean.class, serializer2))
289327
.deserializersByType(deserializerMap)
@@ -346,4 +384,25 @@ public void createXmlMapper() {
346384
assertTrue(xmlObjectMapper.getClass().isAssignableFrom(XmlMapper.class));
347385
}
348386

387+
388+
public static class CustomModule extends Module {
389+
390+
@Override
391+
public String getModuleName() {
392+
return this.getClass().getSimpleName();
393+
}
394+
395+
@Override
396+
public Version version() {
397+
return Version.unknownVersion();
398+
}
399+
400+
@Override
401+
public void setupModule(SetupContext context) {
402+
SimpleSerializers serializers = new SimpleSerializers();
403+
serializers.addSerializer(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC())));
404+
context.addSerializers(serializers);
405+
}
406+
}
407+
349408
}

spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.http.converter.json;
1818

19+
import java.io.UnsupportedEncodingException;
1920
import java.text.SimpleDateFormat;
21+
import java.util.ArrayList;
2022
import java.util.Arrays;
2123
import java.util.Collections;
2224
import java.util.Date;
@@ -28,6 +30,8 @@
2830
import com.fasterxml.jackson.annotation.JsonInclude;
2931
import com.fasterxml.jackson.core.JsonGenerator;
3032
import com.fasterxml.jackson.core.JsonParser;
33+
import com.fasterxml.jackson.core.JsonProcessingException;
34+
import com.fasterxml.jackson.core.Version;
3135
import com.fasterxml.jackson.databind.DeserializationFeature;
3236
import com.fasterxml.jackson.databind.JsonDeserializer;
3337
import com.fasterxml.jackson.databind.JsonSerializer;
@@ -42,12 +46,17 @@
4246
import com.fasterxml.jackson.databind.deser.std.DateDeserializers.DateDeserializer;
4347
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
4448
import com.fasterxml.jackson.databind.module.SimpleModule;
49+
import com.fasterxml.jackson.databind.module.SimpleSerializers;
4550
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
4651
import com.fasterxml.jackson.databind.ser.Serializers;
4752
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
4853
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
4954
import com.fasterxml.jackson.databind.type.SimpleType;
5055
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
56+
import com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaDateFormat;
57+
import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer;
58+
import org.joda.time.DateTime;
59+
import org.joda.time.format.DateTimeFormat;
5160
import org.junit.Before;
5261
import org.junit.Test;
5362

@@ -221,6 +230,40 @@ public void setModules() {
221230
assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1);
222231
}
223232

233+
@Test
234+
public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException {
235+
this.factory.afterPropertiesSet();
236+
ObjectMapper objectMapper = this.factory.getObject();
237+
238+
DateTime dateTime = DateTime.parse("2011-12-03T10:15:30");
239+
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
240+
}
241+
242+
@Test // SPR-12634
243+
public void customizeDefaultModules() throws JsonProcessingException, UnsupportedEncodingException {
244+
this.factory.setFeaturesToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
245+
this.factory.setModulesToInstall(CustomModule.class);
246+
this.factory.afterPropertiesSet();
247+
ObjectMapper objectMapper = this.factory.getObject();
248+
249+
DateTime dateTime = DateTime.parse("2011-12-03T10:15:30");
250+
assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
251+
}
252+
253+
@Test // SPR-12634
254+
public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException {
255+
Map<Class<?>, JsonSerializer<?>> serializers = new HashMap<>();
256+
serializers.put(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC())));
257+
258+
this.factory.setSerializersByType(serializers);
259+
this.factory.setFeaturesToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
260+
this.factory.afterPropertiesSet();
261+
ObjectMapper objectMapper = this.factory.getObject();
262+
263+
DateTime dateTime = DateTime.parse("2011-12-03T10:15:30");
264+
assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
265+
}
266+
224267
@Test
225268
public void simpleSetup() {
226269
this.factory.afterPropertiesSet();
@@ -283,6 +326,7 @@ public void completeSetup() {
283326
JsonSerializer<Class<?>> serializer1 = new ClassSerializer();
284327
JsonSerializer<Number> serializer2 = new NumberSerializer();
285328

329+
factory.setModules(new ArrayList<>()); // Disable well-known modules detection
286330
factory.setSerializers(serializer1);
287331
factory.setSerializersByType(Collections.<Class<?>, JsonSerializer<?>> singletonMap(Boolean.class, serializer2));
288332
factory.setDeserializersByType(deserializers);
@@ -350,4 +394,25 @@ public void createXmlMapper() {
350394
assertEquals(XmlMapper.class, this.factory.getObjectType());
351395
}
352396

397+
398+
public static class CustomModule extends Module {
399+
400+
@Override
401+
public String getModuleName() {
402+
return this.getClass().getSimpleName();
403+
}
404+
405+
@Override
406+
public Version version() {
407+
return Version.unknownVersion();
408+
}
409+
410+
@Override
411+
public void setupModule(SetupContext context) {
412+
SimpleSerializers serializers = new SimpleSerializers();
413+
serializers.addSerializer(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC())));
414+
context.addSerializers(serializers);
415+
}
416+
}
417+
353418
}

0 commit comments

Comments
 (0)