diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java index 0169a3bb2b..da367f7fc4 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.http.converter.json; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -73,7 +74,7 @@ import org.springframework.util.ClassUtils; */ public class Jackson2ObjectMapperBuilder { - private boolean createXmlMapper = false; + private boolean createXmlMapper; private DateFormat dateFormat; @@ -97,10 +98,12 @@ public class Jackson2ObjectMapperBuilder { private List modules; - private Class[] modulesToInstall; + private Class[] moduleClasses; private boolean findModulesViaServiceLoader; + private boolean findWellKnownModules = true; + private ClassLoader moduleClassLoader = getClass().getClassLoader(); private HandlerInstantiator handlerInstantiator; @@ -374,6 +377,21 @@ public class Jackson2ObjectMapperBuilder { return this; } + /** + * Specify one or more modules to be registered with the {@link ObjectMapper}. + *

Note: If this is set, no finding of modules is going to happen - not by + * Jackson, and not by Spring either (see {@link #findModulesViaServiceLoader}). + * As a consequence, specifying an empty list here will suppress any kind of + * module detection. + *

Specify either this or {@link #modulesToInstall}, not both. + * @since 4.1.5 + * @see #modules(List) + * @see com.fasterxml.jackson.databind.Module + */ + public Jackson2ObjectMapperBuilder modules(Module... modules) { + return modules(Arrays.asList(modules)); + } + /** * Set a complete list of modules to be registered with the {@link ObjectMapper}. *

Note: If this is set, no finding of modules is going to happen - not by @@ -381,10 +399,29 @@ public class Jackson2ObjectMapperBuilder { * As a consequence, specifying an empty list here will suppress any kind of * module detection. *

Specify either this or {@link #modulesToInstall}, not both. + * @see #modules(Module...) * @see com.fasterxml.jackson.databind.Module */ public Jackson2ObjectMapperBuilder modules(List modules) { this.modules = new LinkedList(modules); + this.findModulesViaServiceLoader = false; + this.findWellKnownModules = false; + return this; + } + + /** + * Specify one or more modules to be registered with the {@link ObjectMapper}. + *

Modules specified here will be registered after + * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's + * finding of modules (see {@link #findModulesViaServiceLoader}), + * allowing to eventually override their configuration. + *

Specify either this or {@link #modules}, not both. + * @since 4.1.5 + * @see com.fasterxml.jackson.databind.Module + */ + public Jackson2ObjectMapperBuilder modulesToInstall(Module... modules) { + this.modules = Arrays.asList(modules); + this.findWellKnownModules = true; return this; } @@ -396,10 +433,12 @@ public class Jackson2ObjectMapperBuilder { * finding of modules (see {@link #findModulesViaServiceLoader}), * allowing to eventually override their configuration. *

Specify either this or {@link #modules}, not both. + * @see #modulesToInstall(Module...) * @see com.fasterxml.jackson.databind.Module */ public Jackson2ObjectMapperBuilder modulesToInstall(Class... modules) { - this.modulesToInstall = modules; + this.moduleClasses = modules; + this.findWellKnownModules = true; return this; } @@ -482,6 +521,13 @@ public class Jackson2ObjectMapperBuilder { public void configure(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); + if (this.findModulesViaServiceLoader) { + // Jackson 2.2+ + objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader)); + } + else if (this.findWellKnownModules) { + registerWellKnownModulesIfAvailable(objectMapper); + } if (this.modules != null) { // Complete list of modules given for (Module module : this.modules) { @@ -489,19 +535,9 @@ public class Jackson2ObjectMapperBuilder { objectMapper.registerModule(module); } } - else { - // Combination of modules by class presence in the classpath and class names specified - if (this.findModulesViaServiceLoader) { - // Jackson 2.2+ - objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader)); - } - else { - registerWellKnownModulesIfAvailable(objectMapper); - } - if (this.modulesToInstall != null) { - for (Class module : this.modulesToInstall) { - objectMapper.registerModule(BeanUtils.instantiate(module)); - } + if (this.moduleClasses != null) { + for (Class module : this.moduleClasses) { + objectMapper.registerModule(BeanUtils.instantiate(module)); } } diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java index e4d1c782c6..39055fadd1 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package org.springframework.http.converter.json; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -41,6 +41,7 @@ import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory; @@ -55,11 +56,9 @@ import com.fasterxml.jackson.databind.ser.std.ClassSerializer; import com.fasterxml.jackson.databind.ser.std.NumberSerializer; import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaDateFormat; -import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer; +import static org.hamcrest.Matchers.containsString; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; import org.junit.Test; import org.springframework.beans.FatalBeanException; @@ -212,15 +211,29 @@ public class Jackson2ObjectMapperBuilderTests { } @Test - public void setModules() { + public void modules() { NumberSerializer serializer1 = new NumberSerializer(); SimpleModule module = new SimpleModule(); module.addSerializer(Integer.class, serializer1); - ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(Arrays.asList(new Module[]{module})).build(); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(module).build(); Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1); } + @Test + public void modulesToInstallByClass() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build(); + Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null).getClass() == CustomIntegerSerializer.class); + } + + @Test + public void modulesToInstallByInstance() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(new CustomIntegerModule()).build(); + Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null).getClass() == CustomIntegerSerializer.class); + } + @Test public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); @@ -230,21 +243,29 @@ public class Jackson2ObjectMapperBuilderTests { } @Test // SPR-12634 - public void customizeDefaultModules() throws JsonProcessingException, UnsupportedEncodingException { + public void customizeDefaultModulesWithModule() throws JsonProcessingException, UnsupportedEncodingException { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() - .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .modulesToInstall(CustomModule.class).build(); + .modulesToInstall(new CustomIntegerModule()).build(); DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); - assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); + } + + @Test // SPR-12634 + public void customizeDefaultModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build(); + DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); } @Test // SPR-12634 public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() - .serializerByType(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC()))) - .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build(); + .serializerByType(Integer.class, new CustomIntegerSerializer()).build(); DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); - assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); } @@ -387,7 +408,7 @@ public class Jackson2ObjectMapperBuilderTests { } - public static class CustomModule extends Module { + public static class CustomIntegerModule extends Module { @Override public String getModuleName() { @@ -402,9 +423,19 @@ public class Jackson2ObjectMapperBuilderTests { @Override public void setupModule(SetupContext context) { SimpleSerializers serializers = new SimpleSerializers(); - serializers.addSerializer(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC()))); + serializers.addSerializer(Integer.class, new CustomIntegerSerializer()); context.addSerializers(serializers); } } + public static class CustomIntegerSerializer extends JsonSerializer { + + @Override + public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeNumberField("customid", value); + gen.writeEndObject(); + } + } + } diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java index 38b646bd56..a85370ff65 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.http.converter.json; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -40,6 +41,7 @@ import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory; @@ -53,11 +55,9 @@ import com.fasterxml.jackson.databind.ser.std.ClassSerializer; import com.fasterxml.jackson.databind.ser.std.NumberSerializer; import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.fasterxml.jackson.datatype.joda.cfg.JacksonJodaDateFormat; -import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer; +import static org.hamcrest.Matchers.containsString; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; import org.junit.Before; import org.junit.Test; @@ -70,6 +70,7 @@ import static org.junit.Assert.*; * * @author Dmitry Katsubo * @author Brian Clozel + * @author Sebastien Deleuze */ public class Jackson2ObjectMapperFactoryBeanTests { @@ -242,28 +243,28 @@ public class Jackson2ObjectMapperFactoryBeanTests { } @Test // SPR-12634 - public void customizeDefaultModules() throws JsonProcessingException, UnsupportedEncodingException { - this.factory.setFeaturesToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - this.factory.setModulesToInstall(CustomModule.class); + public void customizeDefaultModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException { + this.factory.setModulesToInstall(CustomIntegerModule.class); this.factory.afterPropertiesSet(); ObjectMapper objectMapper = this.factory.getObject(); DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); - assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); } @Test // SPR-12634 public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException { Map, JsonSerializer> serializers = new HashMap<>(); - serializers.put(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC()))); + serializers.put(Integer.class, new CustomIntegerSerializer()); this.factory.setSerializersByType(serializers); - this.factory.setFeaturesToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); this.factory.afterPropertiesSet(); ObjectMapper objectMapper = this.factory.getObject(); DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); - assertEquals("\"2011-12-03\"", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); } @Test @@ -396,8 +397,7 @@ public class Jackson2ObjectMapperFactoryBeanTests { assertEquals(XmlMapper.class, this.factory.getObjectType()); } - - public static class CustomModule extends Module { + public static class CustomIntegerModule extends Module { @Override public String getModuleName() { @@ -412,9 +412,19 @@ public class Jackson2ObjectMapperFactoryBeanTests { @Override public void setupModule(SetupContext context) { SimpleSerializers serializers = new SimpleSerializers(); - serializers.addSerializer(DateTime.class, new DateTimeSerializer(new JacksonJodaDateFormat(DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC()))); + serializers.addSerializer(Integer.class, new CustomIntegerSerializer()); context.addSerializers(serializers); } } + public static class CustomIntegerSerializer extends JsonSerializer { + + @Override + public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeStartObject(); + gen.writeNumberField("customid", value); + gen.writeEndObject(); + } + } + }