Skip to content

Commit aa415d7

Browse files
Dmitry Katsuborstoyanchev
authored andcommitted
BeanFactory for configuring a Jackson ObjectMapper
The BeanFactory makes it easier to configure a customized Jackson ObjectMapper (for example enable/disable certain features). This bean factory is usually used with MappingJacksonHttpMessageConverter or MappingJacksonJsonView. See JavaDoc for examples. Issue: SPR-9125
1 parent 5959de5 commit aa415d7

File tree

3 files changed

+422
-0
lines changed

3 files changed

+422
-0
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.context.support;
18+
19+
import java.text.DateFormat;
20+
import java.text.SimpleDateFormat;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
import org.codehaus.jackson.JsonGenerator;
25+
import org.codehaus.jackson.JsonParser;
26+
import org.codehaus.jackson.map.AnnotationIntrospector;
27+
import org.codehaus.jackson.map.DeserializationConfig;
28+
import org.codehaus.jackson.map.ObjectMapper;
29+
import org.codehaus.jackson.map.SerializationConfig;
30+
import org.springframework.beans.FatalBeanException;
31+
import org.springframework.beans.factory.FactoryBean;
32+
import org.springframework.beans.factory.InitializingBean;
33+
34+
/**
35+
* A FactoryBean for creating a Jackson {@link ObjectMapper} with setters to
36+
* enable or disable Jackson features from within XML configuration.
37+
*
38+
* <p>Example usage with MappingJacksonHttpMessageConverter:</p>
39+
* <pre>
40+
* &lt;bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
41+
* &lt;property name="objectMapper">
42+
* &lt;bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
43+
* p:autoDetectFields="false"
44+
* p:autoDetectGettersSetters="false"
45+
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
46+
* &lt;/property>
47+
* &lt;/bean>
48+
* </pre>
49+
*
50+
* <p>Example usage with MappingJacksonJsonView:</p>
51+
* <pre>
52+
* &lt;bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
53+
* &lt;property name="objectMapper">
54+
* &lt;bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
55+
* p:autoDetectFields="false"
56+
* p:autoDetectGettersSetters="false"
57+
* p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
58+
* &lt;/property>
59+
* &lt;/bean>
60+
* </pre>
61+
*
62+
* <p>In case there are no specific setters provided (for some rarely used
63+
* options), you can still use the more general methods
64+
* {@link #setFeaturesToEnable(Object[])} and {@link #setFeaturesToDisable(Object[])}.
65+
*
66+
* <pre>
67+
* &lt;bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean">
68+
* &lt;property name="featuresToEnable">
69+
* &lt;array>
70+
* &lt;util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.WRAP_ROOT_VALUE"/>
71+
* &lt;util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.CLOSE_CLOSEABLE"/>
72+
* &lt;/array>
73+
* &lt;/property>
74+
* &lt;property name="featuresToDisable">
75+
* &lt;array>
76+
* &lt;util:constant static-field="org.codehaus.jackson.map.DeserializationConfig$Feature.USE_ANNOTATIONS"/>
77+
* &lt;/array>
78+
* &lt;/property>
79+
* &lt;/bean>
80+
* </pre>
81+
*
82+
* <p>Note: This BeanFctory is singleton, so if you need more than one, you'll
83+
* need to configure multiple instances.
84+
*
85+
* @author <a href="mailto:[email protected]">Dmitry Katsubo</a>
86+
* @author Rossen Stoyanchev
87+
*
88+
* @since 3.2
89+
*/
90+
public class JacksonObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, InitializingBean {
91+
92+
private ObjectMapper objectMapper;
93+
94+
private Map<Object, Boolean> features = new HashMap<Object, Boolean>();
95+
96+
private AnnotationIntrospector annotationIntrospector;
97+
98+
private DateFormat dateFormat;
99+
100+
/**
101+
* Set the ObjectMapper instance to use.
102+
* If not set an instance will be created using the default constructor.
103+
*/
104+
public void setObjectMapper(ObjectMapper objectMapper) {
105+
this.objectMapper = objectMapper;
106+
}
107+
108+
/**
109+
* Define annotationIntrospector for
110+
* {@link SerializationConfig#setAnnotationIntrospector(AnnotationIntrospector)}.
111+
*/
112+
public void setAnnotationIntrospector(AnnotationIntrospector annotationIntrospector) {
113+
this.annotationIntrospector = annotationIntrospector;
114+
}
115+
116+
/**
117+
* Define the date/time format with the given string, which is in turn used
118+
* to create a {@link SimpleDateFormat}.
119+
* @see #setDateFormat(DateFormat)
120+
*/
121+
public void setSimpleDateFormat(String format) {
122+
this.dateFormat = new SimpleDateFormat(format);
123+
}
124+
125+
/**
126+
* Define the format for date/time with the given {@link DateFormat} instance.
127+
* @see #setSimpleDateFormat(String)
128+
*/
129+
public void setDateFormat(DateFormat dateFormat) {
130+
this.dateFormat = dateFormat;
131+
}
132+
133+
/**
134+
* Shortcut for {@link SerializationConfig.Feature#AUTO_DETECT_FIELDS} and
135+
* {@link DeserializationConfig.Feature#AUTO_DETECT_FIELDS}.
136+
*/
137+
public void setAutoDetectFields(boolean autoDetectFields) {
138+
this.features.put(DeserializationConfig.Feature.AUTO_DETECT_FIELDS, Boolean.valueOf(autoDetectFields));
139+
this.features.put(SerializationConfig.Feature.AUTO_DETECT_FIELDS, Boolean.valueOf(autoDetectFields));
140+
}
141+
142+
/**
143+
* Shortcut for {@link SerializationConfig.Feature#AUTO_DETECT_GETTERS} and
144+
* {@link DeserializationConfig.Feature#AUTO_DETECT_SETTERS}.
145+
*/
146+
public void setAutoDetectGettersSetters(boolean autoDetectGettersSetters) {
147+
this.features.put(SerializationConfig.Feature.AUTO_DETECT_GETTERS, Boolean.valueOf(autoDetectGettersSetters));
148+
this.features.put(DeserializationConfig.Feature.AUTO_DETECT_SETTERS, Boolean.valueOf(autoDetectGettersSetters));
149+
}
150+
151+
/**
152+
* Shortcut for {@link SerializationConfig.Feature#FAIL_ON_EMPTY_BEANS}.
153+
*/
154+
public void setFailOnEmptyBeans(boolean failOnEmptyBeans) {
155+
this.features.put(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, Boolean.valueOf(failOnEmptyBeans));
156+
}
157+
158+
/**
159+
* Shortcut for {@link SerializationConfig.Feature#INDENT_OUTPUT}.
160+
*/
161+
public void setIndentOutput(boolean indentOutput) {
162+
this.features.put(SerializationConfig.Feature.INDENT_OUTPUT, Boolean.valueOf(indentOutput));
163+
}
164+
165+
/**
166+
* Specify features to enable.
167+
* @see SerializationConfig.Feature
168+
* @see DeserializationConfig.Feature
169+
* @see JsonParser.Feature
170+
* @see JsonGenerator.Feature
171+
*/
172+
public void setFeaturesToEnable(Object[] featuresToEnable) {
173+
if (featuresToEnable == null) {
174+
throw new FatalBeanException("featuresToEnable property should not be null");
175+
}
176+
for (Object feature : featuresToEnable) {
177+
this.features.put(feature, Boolean.TRUE);
178+
}
179+
}
180+
181+
/**
182+
* Specify features to disable.
183+
* @see SerializationConfig.Feature
184+
* @see DeserializationConfig.Feature
185+
* @see JsonParser.Feature
186+
* @see JsonGenerator.Feature
187+
*/
188+
public void setFeaturesToDisable(Object[] featuresToDisable) {
189+
if (featuresToDisable == null) {
190+
throw new FatalBeanException("featuresToDisable property should not be null");
191+
}
192+
for (Object feature : featuresToDisable) {
193+
this.features.put(feature, Boolean.FALSE);
194+
}
195+
}
196+
197+
public ObjectMapper getObject() {
198+
return this.objectMapper;
199+
}
200+
201+
public Class<?> getObjectType() {
202+
return ObjectMapper.class;
203+
}
204+
205+
public boolean isSingleton() {
206+
return true;
207+
}
208+
209+
/**
210+
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
211+
*/
212+
public void afterPropertiesSet() throws FatalBeanException {
213+
if (this.objectMapper == null) {
214+
this.objectMapper = new ObjectMapper();
215+
}
216+
217+
if (this.annotationIntrospector != null) {
218+
this.objectMapper.getSerializationConfig().setAnnotationIntrospector(annotationIntrospector);
219+
this.objectMapper.getDeserializationConfig().setAnnotationIntrospector(annotationIntrospector);
220+
}
221+
222+
if (this.dateFormat != null) {
223+
// Deprecated for 1.8+, use
224+
// objectMapper.setDateFormat(dateFormat);
225+
this.objectMapper.getSerializationConfig().setDateFormat(this.dateFormat);
226+
}
227+
228+
for (Map.Entry<Object, Boolean> entry : features.entrySet()) {
229+
setFeatureEnabled(entry.getKey(), entry.getValue().booleanValue());
230+
}
231+
}
232+
233+
private void setFeatureEnabled(Object feature, boolean enabled) {
234+
if (feature instanceof DeserializationConfig.Feature) {
235+
this.objectMapper.configure((DeserializationConfig.Feature) feature, enabled);
236+
}
237+
else if (feature instanceof SerializationConfig.Feature) {
238+
this.objectMapper.configure((SerializationConfig.Feature) feature, enabled);
239+
}
240+
else if (feature instanceof JsonParser.Feature) {
241+
this.objectMapper.configure((JsonParser.Feature) feature, enabled);
242+
}
243+
else if (feature instanceof JsonGenerator.Feature) {
244+
this.objectMapper.configure((JsonGenerator.Feature) feature, enabled);
245+
}
246+
else {
247+
throw new FatalBeanException("Unknown feature class " + feature.getClass().getName());
248+
}
249+
}
250+
}

0 commit comments

Comments
 (0)