Skip to content

Commit 811330f

Browse files
royclarksonrstoyanchev
authored andcommitted
Add GsonHttpMessageConverter
This commit adds support to read and write JSON using the Google Gson library. GsonHttpMessageConverter offers default Gson configuration, but can be customized by using GsonFactoryBean. GsonFactoryBean includes several convenience properties for configuring the internal GsonBuilder and the resulting Gson object. By default Gson converts byte arrays to JSON arrays instead of a Base64 encoded string. GsonBase64ByteArrayJsonTypeAdapter provides support to read and write Base64 encoded byte arrays, and can be enabled in GsonFactoryBean. RestTemplate will enable GsonHttpMessageConverter only if Jackson 2 is not found on the class path, because by default GsonHttpMessageConverter supports the same media types as Jackson. Issue: SPR-9488
1 parent 9952973 commit 811330f

File tree

7 files changed

+1033
-0
lines changed

7 files changed

+1033
-0
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ configure(allprojects) { project ->
2222
ext.hibVal5Version = "5.1.1.Final"
2323
ext.hsqldbVersion = "2.3.2"
2424
ext.jackson2Version = "2.3.3"
25+
ext.gsonVersion = "2.2.4"
2526
ext.jasperReportsVersion = "5.5.2"
2627
ext.jettyVersion = "9.1.5.v20140505"
2728
ext.jodaVersion = "2.3"
@@ -621,6 +622,8 @@ project("spring-web") {
621622
optional("org.apache.httpcomponents:httpclient:4.3.3")
622623
optional("org.apache.httpcomponents:httpasyncclient:4.0.1")
623624
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
625+
optional("com.google.code.gson:gson:${gsonVersion}")
626+
optional("commons-codec:commons-codec:1.9")
624627
optional("rome:rome:1.0")
625628
optional("org.eclipse.jetty:jetty-servlet:${jettyVersion}") {
626629
exclude group: "javax.servlet", module: "javax.servlet-api"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2002-2014 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.http.converter.json;
18+
19+
import java.lang.reflect.Type;
20+
import java.nio.charset.Charset;
21+
22+
import org.apache.commons.codec.binary.Base64;
23+
24+
import com.google.gson.GsonBuilder;
25+
import com.google.gson.JsonDeserializationContext;
26+
import com.google.gson.JsonDeserializer;
27+
import com.google.gson.JsonElement;
28+
import com.google.gson.JsonParseException;
29+
import com.google.gson.JsonPrimitive;
30+
import com.google.gson.JsonSerializationContext;
31+
import com.google.gson.JsonSerializer;
32+
import com.google.gson.TypeAdapter;
33+
34+
/**
35+
* Custom Gson {@link TypeAdapter} for serialization or deserialization of
36+
* {@code byte[]}. By default Gson converts byte arrays to JSON arrays instead
37+
* of a Base64 encoded string. Use this type adapter with
38+
* {@link org.springframework.http.converter.json.GsonHttpMessageConverter
39+
* GsonHttpMessageConverter} to read and write Base64 encoded byte arrays.
40+
*
41+
* @author Roy Clarkson
42+
* @since 4.1
43+
* @see GsonBuilder#registerTypeHierarchyAdapter(Class, Object)
44+
*/
45+
final class GsonBase64ByteArrayJsonTypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
46+
47+
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
48+
49+
private final Base64 base64 = new Base64();
50+
51+
52+
@Override
53+
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
54+
return new JsonPrimitive(new String(this.base64.encode(src), DEFAULT_CHARSET));
55+
}
56+
57+
@Override
58+
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
59+
throws JsonParseException {
60+
61+
return this.base64.decode(json.getAsString().getBytes(DEFAULT_CHARSET));
62+
}
63+
64+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright 2002-2014 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.http.converter.json;
18+
19+
import java.text.SimpleDateFormat;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
import org.springframework.beans.factory.BeanClassLoaderAware;
24+
import org.springframework.beans.factory.FactoryBean;
25+
import org.springframework.beans.factory.InitializingBean;
26+
import org.springframework.util.ClassUtils;
27+
28+
import com.fasterxml.jackson.databind.ObjectMapper;
29+
import com.google.gson.Gson;
30+
import com.google.gson.GsonBuilder;
31+
32+
33+
/**
34+
* A {@link FactoryBean} for creating a Google Gson 2.x {@link Gson}
35+
*
36+
* @author Roy Clarkson
37+
* @since 4.1
38+
*/
39+
public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware, InitializingBean {
40+
41+
private static final boolean base64Present = ClassUtils.isPresent(
42+
"org.apache.commons.codec.binary.Base64", GsonFactoryBean.class.getClassLoader());
43+
44+
private final Log logger = LogFactory.getLog(getClass());
45+
46+
private Gson gson;
47+
48+
private GsonBuilder gsonBuilder;
49+
50+
private Boolean prettyPrint;
51+
52+
private Boolean serializeNulls;
53+
54+
private Boolean disableHtmlEscaping;
55+
56+
private SimpleDateFormat dateFormat;
57+
58+
private Boolean base64EncodeByteArrays;
59+
60+
private ClassLoader beanClassLoader;
61+
62+
63+
/**
64+
* Set the GsonBuilder instance to use. If not set, the GsonBuilder will be created
65+
* using its default constructor.
66+
*/
67+
public void setGsonBuilder(GsonBuilder gsonBuilder) {
68+
this.gsonBuilder = gsonBuilder;
69+
}
70+
71+
/**
72+
* Return the GsonBuilder instance being used.
73+
* @return the GsonBuilder instance
74+
*/
75+
public GsonBuilder getGsonBuilder() {
76+
return this.gsonBuilder;
77+
}
78+
79+
/**
80+
* Whether to use the {@link GsonBuilder#setPrettyPrinting()} when writing JSON. This
81+
* is a shortcut for setting up a {@code Gson} as follows:
82+
*
83+
* <pre class="code">
84+
* new GsonBuilder().setPrettyPrinting().create();
85+
* </pre>
86+
*/
87+
public void setPrettyPrint(boolean prettyPrint) {
88+
this.prettyPrint = prettyPrint;
89+
}
90+
91+
/**
92+
* Whether to use the {@link GsonBuilder#serializeNulls()} option when writing JSON.
93+
* This is a shortcut for setting up a {@code Gson} as follows:
94+
*
95+
* <pre class="code">
96+
* new GsonBuilder().serializeNulls().create();
97+
* </pre>
98+
*/
99+
public void setSerializeNulls(boolean serializeNulls) {
100+
this.serializeNulls = serializeNulls;
101+
}
102+
103+
/**
104+
* Whether to use the {@link GsonBuilder#disableHtmlEscaping()} when writing JSON. Set
105+
* to {@code true} to disable HTML escaping in JSON. This is a shortcut for setting up
106+
* a {@code Gson} as follows:
107+
*
108+
* <pre class="code">
109+
* new GsonBuilder().disableHtmlEscaping().create();
110+
* </pre>
111+
*/
112+
public void setDisableHtmlEscaping(boolean disableHtmlEscaping) {
113+
this.disableHtmlEscaping = disableHtmlEscaping;
114+
}
115+
116+
/**
117+
* Define the format for date/time with the given {@link SimpleDateFormat}.
118+
* This is a shortcut for setting up a {@code Gson} as follows:
119+
*
120+
* <pre class="code">
121+
* new GsonBuilder().setDateFormat(dateFormatPattern).create();
122+
* </pre>
123+
*
124+
* @see #setSimpleDateFormat(String)
125+
*/
126+
public void setSimpleDateFormat(SimpleDateFormat dateFormat) {
127+
this.dateFormat = dateFormat;
128+
}
129+
130+
/**
131+
* Define the date/time format with a {@link SimpleDateFormat}.
132+
* This is a shortcut for setting up a {@code Gson} as follows:
133+
*
134+
* <pre class="code">
135+
* new GsonBuilder().setDateFormat(dateFormatPattern).create();
136+
* </pre>
137+
*
138+
* @see #setSimpleDateFormat(SimpleDateFormat)
139+
*/
140+
public void setSimpleDateFormat(String format) {
141+
this.dateFormat = new SimpleDateFormat(format);
142+
}
143+
144+
/**
145+
* Whether to Base64 encode {@code byte[]} properties when reading and
146+
* writing JSON.
147+
*
148+
* <p>When set to {@code true} a custom {@link com.google.gson.TypeAdapter}
149+
* is registered via {@link GsonBuilder#registerTypeHierarchyAdapter(Class, Object)}
150+
* that serializes a {@code byte[]} property to and from a Base64 encoded
151+
* string instead of a JSON array.
152+
*
153+
* <p><strong>NOTE:</strong> Use of this option requires the presence of
154+
* Apache commons-codec on the classpath. Otherwise it is ignored.
155+
*
156+
* @see org.springframework.http.converter.json.GsonBase64ByteArrayJsonTypeAdapter
157+
*/
158+
public void setBase64EncodeByteArrays(boolean base64EncodeByteArrays) {
159+
this.base64EncodeByteArrays = base64EncodeByteArrays;
160+
}
161+
162+
@Override
163+
public void setBeanClassLoader(ClassLoader beanClassLoader) {
164+
this.beanClassLoader = beanClassLoader;
165+
}
166+
167+
168+
@Override
169+
public void afterPropertiesSet() throws Exception {
170+
if (gsonBuilder == null) {
171+
this.gsonBuilder = new GsonBuilder();
172+
}
173+
if (this.prettyPrint != null && this.prettyPrint) {
174+
this.gsonBuilder = this.gsonBuilder.setPrettyPrinting();
175+
}
176+
if (this.serializeNulls != null && this.serializeNulls) {
177+
this.gsonBuilder = this.gsonBuilder.serializeNulls();
178+
}
179+
if (this.disableHtmlEscaping != null && this.disableHtmlEscaping) {
180+
this.gsonBuilder = this.gsonBuilder.disableHtmlEscaping();
181+
}
182+
if (this.dateFormat != null) {
183+
this.gsonBuilder.setDateFormat(this.dateFormat.toPattern());
184+
}
185+
if (base64Present) {
186+
if (this.base64EncodeByteArrays != null && this.base64EncodeByteArrays) {
187+
this.gsonBuilder.registerTypeHierarchyAdapter(byte[].class, new GsonBase64ByteArrayJsonTypeAdapter());
188+
}
189+
}
190+
else if (logger.isDebugEnabled()) {
191+
logger.debug("org.apache.commons.codec.binary.Base64 is not available on the class path. Gson Base64 encoding is disabled.");
192+
}
193+
this.gson = this.gsonBuilder.create();
194+
}
195+
196+
197+
/**
198+
* Return the singleton Gson.
199+
*/
200+
@Override
201+
public Gson getObject() throws Exception {
202+
return this.gson;
203+
}
204+
205+
@Override
206+
public Class<?> getObjectType() {
207+
return Gson.class;
208+
}
209+
210+
@Override
211+
public boolean isSingleton() {
212+
return true;
213+
}
214+
215+
}

0 commit comments

Comments
 (0)