Revised GsonFactoryBean's configuration properties; made prettyPrinting test pass on Windows

Issue: SPR-9488
This commit is contained in:
Juergen Hoeller 2014-05-27 18:26:48 +02:00
parent d9f8ee886e
commit 22a38d4547
2 changed files with 87 additions and 154 deletions

View File

@ -18,81 +18,54 @@ package org.springframework.http.converter.json;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import org.apache.commons.logging.Log; import com.google.gson.Gson;
import org.apache.commons.logging.LogFactory; import com.google.gson.GsonBuilder;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/** /**
* A {@link FactoryBean} for creating a Google Gson 2.x {@link Gson} instance. * A {@link FactoryBean} for creating a Google Gson 2.x {@link Gson} instance.
* *
* @author Roy Clarkson * @author Roy Clarkson
* @author Juergen Hoeller
* @since 4.1 * @since 4.1
*/ */
public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware, InitializingBean { public class GsonFactoryBean implements FactoryBean<Gson>, InitializingBean {
private static final boolean base64Present = ClassUtils.isPresent( /** Apache Commons Codec present on the classpath, for Base64 encoding? */
private static final boolean commonsCodecPresent = ClassUtils.isPresent(
"org.apache.commons.codec.binary.Base64", GsonFactoryBean.class.getClassLoader()); "org.apache.commons.codec.binary.Base64", GsonFactoryBean.class.getClassLoader());
private final Log logger = LogFactory.getLog(getClass());
private Gson gson;
private GsonBuilder gsonBuilder; private GsonBuilder gsonBuilder;
private Boolean prettyPrint; private boolean serializeNulls;
private Boolean serializeNulls; private boolean prettyPrinting;
private Boolean disableHtmlEscaping; private boolean disableHtmlEscaping;
private SimpleDateFormat dateFormat; private String dateFormatPattern;
private Boolean base64EncodeByteArrays; private boolean base64EncodeByteArrays;
private ClassLoader beanClassLoader; private Gson gson;
/** /**
* Set the GsonBuilder instance to use. If not set, the GsonBuilder will be * Set the GsonBuilder instance to use.
* created using its default constructor. * If not set, the GsonBuilder will be created using its default constructor.
*/ */
public void setGsonBuilder(GsonBuilder gsonBuilder) { public void setGsonBuilder(GsonBuilder gsonBuilder) {
this.gsonBuilder = gsonBuilder; this.gsonBuilder = gsonBuilder;
} }
/** /**
* Return the configured GsonBuilder instance to use, if any. * Whether to use the {@link GsonBuilder#serializeNulls()} option when writing
* @return the GsonBuilder instance
*/
public GsonBuilder getGsonBuilder() {
return this.gsonBuilder;
}
/**
* Whether to use the {@link GsonBuilder#setPrettyPrinting()} when writing
* JSON. This is a shortcut for setting up a {@code Gson} as follows: * JSON. This is a shortcut for setting up a {@code Gson} as follows:
*
* <pre class="code">
* new GsonBuilder().setPrettyPrinting().create();
* </pre>
*/
public void setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
/**
* Whether to use the {@link GsonBuilder#serializeNulls()} option when
* writing JSON. This is a shortcut for setting up a {@code Gson} as
* follows:
*
* <pre class="code"> * <pre class="code">
* new GsonBuilder().serializeNulls().create(); * new GsonBuilder().serializeNulls().create();
* </pre> * </pre>
@ -101,11 +74,21 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
this.serializeNulls = serializeNulls; this.serializeNulls = serializeNulls;
} }
/**
* Whether to use the {@link GsonBuilder#setPrettyPrinting()} when writing
* JSON. This is a shortcut for setting up a {@code Gson} as follows:
* <pre class="code">
* new GsonBuilder().setPrettyPrinting().create();
* </pre>
*/
public void setPrettyPrinting(boolean prettyPrinting) {
this.prettyPrinting = prettyPrinting;
}
/** /**
* Whether to use the {@link GsonBuilder#disableHtmlEscaping()} when writing * Whether to use the {@link GsonBuilder#disableHtmlEscaping()} when writing
* JSON. Set to {@code true} to disable HTML escaping in JSON. This is a * JSON. Set to {@code true} to disable HTML escaping in JSON. This is a
* shortcut for setting up a {@code Gson} as follows: * shortcut for setting up a {@code Gson} as follows:
*
* <pre class="code"> * <pre class="code">
* new GsonBuilder().disableHtmlEscaping().create(); * new GsonBuilder().disableHtmlEscaping().create();
* </pre> * </pre>
@ -115,92 +98,67 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
} }
/** /**
* Define the format for date/time with the given {@link SimpleDateFormat}. * Define the date/time format with a {@link SimpleDateFormat}-style pattern.
* This is a shortcut for setting up a {@code Gson} as follows: * This is a shortcut for setting up a {@code Gson} as follows:
*
* <pre class="code"> * <pre class="code">
* new GsonBuilder().setDateFormat(dateFormatPattern).create(); * new GsonBuilder().setDateFormat(dateFormatPattern).create();
* </pre> * </pre>
*
* @see #setSimpleDateFormat(String)
*/ */
public void setSimpleDateFormat(SimpleDateFormat dateFormat) { public void setDateFormatPattern(String dateFormatPattern) {
this.dateFormat = dateFormat; this.dateFormatPattern = dateFormatPattern;
} }
/** /**
* Define the date/time format with a {@link SimpleDateFormat}. * Whether to Base64-encode {@code byte[]} properties when reading and
* This is a shortcut for setting up a {@code Gson} as follows:
*
* <pre class="code">
* new GsonBuilder().setDateFormat(dateFormatPattern).create();
* </pre>
*
* @see #setSimpleDateFormat(SimpleDateFormat)
*/
public void setSimpleDateFormat(String format) {
this.dateFormat = new SimpleDateFormat(format);
}
/**
* Whether to Base64 encode {@code byte[]} properties when reading and
* writing JSON. * writing JSON.
* * <p>When set to {@code true} a custom {@link com.google.gson.TypeAdapter} is
* <p>When set to {@code true} a custom {@link com.google.gson.TypeAdapter} * registered via {@link GsonBuilder#registerTypeHierarchyAdapter(Class, Object)}
* is registered via * that serializes a {@code byte[]} property to and from a Base64-encoded String
* {@link GsonBuilder#registerTypeHierarchyAdapter(Class, Object)} * instead of a JSON array.
* that serializes a {@code byte[]} property to and from a Base64 encoded * <p><strong>NOTE:</strong> Use of this option requires the presence of the
* string instead of a JSON array. * Apache Commons Codec library on the classpath.
* * @see GsonBase64ByteArrayJsonTypeAdapter
* <p><strong>NOTE:</strong> Use of this option requires the presence of
* Apache commons-codec on the classpath. Otherwise it is ignored.
*
* @see org.springframework.http.converter.json.GsonBase64ByteArrayJsonTypeAdapter
*/ */
public void setBase64EncodeByteArrays(boolean base64EncodeByteArrays) { public void setBase64EncodeByteArrays(boolean base64EncodeByteArrays) {
this.base64EncodeByteArrays = base64EncodeByteArrays; this.base64EncodeByteArrays = base64EncodeByteArrays;
} }
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() {
if (this.gsonBuilder == null) { if (this.gsonBuilder == null) {
this.gsonBuilder = new GsonBuilder(); this.gsonBuilder = new GsonBuilder();
} }
if (this.prettyPrint != null && this.prettyPrint) { if (this.serializeNulls) {
this.gsonBuilder = this.gsonBuilder.setPrettyPrinting(); this.gsonBuilder.serializeNulls();
} }
if (this.serializeNulls != null && this.serializeNulls) { if (this.prettyPrinting) {
this.gsonBuilder = this.gsonBuilder.serializeNulls(); this.gsonBuilder.setPrettyPrinting();
} }
if (this.disableHtmlEscaping != null && this.disableHtmlEscaping) { if (this.disableHtmlEscaping) {
this.gsonBuilder = this.gsonBuilder.disableHtmlEscaping(); this.gsonBuilder.disableHtmlEscaping();
} }
if (this.dateFormat != null) { if (this.dateFormatPattern != null) {
this.gsonBuilder.setDateFormat(this.dateFormat.toPattern()); this.gsonBuilder.setDateFormat(this.dateFormatPattern);
} }
if (base64Present) { if (this.base64EncodeByteArrays) {
if (this.base64EncodeByteArrays != null && this.base64EncodeByteArrays) { if (commonsCodecPresent) {
this.gsonBuilder.registerTypeHierarchyAdapter(byte[].class, new GsonBase64ByteArrayJsonTypeAdapter()); this.gsonBuilder.registerTypeHierarchyAdapter(byte[].class, new GsonBase64ByteArrayJsonTypeAdapter());
} }
} else {
else if (logger.isDebugEnabled()) { throw new IllegalStateException(
logger.debug("org.apache.commons.codec.binary.Base64 is not " + "Apache Commons Codec is not available on the classpath - cannot enable Gson Base64 encoding");
"available on the class path. Gson Base64 encoding is disabled."); }
} }
this.gson = this.gsonBuilder.create(); this.gson = this.gsonBuilder.create();
} }
/** /**
* Return the created Gson instance. * Return the created Gson instance.
*/ */
@Override @Override
public Gson getObject() throws Exception { public Gson getObject() {
return this.gson; return this.gson;
} }

View File

@ -16,14 +16,11 @@
package org.springframework.http.converter.json; package org.springframework.http.converter.json;
import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -36,37 +33,9 @@ public class GsonFactoryBeanTests {
private static final String DATE_FORMAT = "yyyy-MM-dd"; private static final String DATE_FORMAT = "yyyy-MM-dd";
private GsonFactoryBean factory; private GsonFactoryBean factory = new GsonFactoryBean();
@Before
public void setUp() {
factory = new GsonFactoryBean();
}
@Test
public void prettyPrint() throws Exception {
this.factory.setPrettyPrint(true);
this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject();
StringBean bean = new StringBean();
bean.setName("Jason");
String result = gson.toJson(bean);
String lineSeparator = System.getProperty("line.separator");
assertEquals("{" + lineSeparator + " \"name\": \"Jason\"" + lineSeparator + "}", result);
}
@Test
public void prettyPrintFalse() throws Exception {
this.factory.setPrettyPrint(false);
this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject();
StringBean bean = new StringBean();
bean.setName("Jason");
String result = gson.toJson(bean);
assertEquals("{\"name\":\"Jason\"}", result);
}
@Test @Test
public void serializeNulls() throws Exception { public void serializeNulls() throws Exception {
this.factory.setSerializeNulls(true); this.factory.setSerializeNulls(true);
@ -87,6 +56,28 @@ public class GsonFactoryBeanTests {
assertEquals("{}", result); assertEquals("{}", result);
} }
@Test
public void prettyPrinting() throws Exception {
this.factory.setPrettyPrinting(true);
this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject();
StringBean bean = new StringBean();
bean.setName("Jason");
String result = gson.toJson(bean);
assertTrue(result.contains(" \"name\": \"Jason\""));
}
@Test
public void prettyPrintingFalse() throws Exception {
this.factory.setPrettyPrinting(false);
this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject();
StringBean bean = new StringBean();
bean.setName("Jason");
String result = gson.toJson(bean);
assertEquals("{\"name\":\"Jason\"}", result);
}
@Test @Test
public void disableHtmlEscaping() throws Exception { public void disableHtmlEscaping() throws Exception {
this.factory.setDisableHtmlEscaping(true); this.factory.setDisableHtmlEscaping(true);
@ -110,26 +101,8 @@ public class GsonFactoryBeanTests {
} }
@Test @Test
public void customizeDateFormat() throws Exception { public void customizeDateFormatPattern() throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); this.factory.setDateFormatPattern(DATE_FORMAT);
this.factory.setSimpleDateFormat(dateFormat);
this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject();
DateBean bean = new DateBean();
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(Calendar.YEAR, 2014);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DATE, 1);
Date date = cal.getTime();
bean.setDate(date);
String result = gson.toJson(bean);
assertEquals("{\"date\":\"2014-01-01\"}", result);
}
@Test
public void customizeDateFormatString() throws Exception {
this.factory.setSimpleDateFormat(DATE_FORMAT);
this.factory.afterPropertiesSet(); this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject(); Gson gson = this.factory.getObject();
DateBean bean = new DateBean(); DateBean bean = new DateBean();
@ -166,7 +139,7 @@ public class GsonFactoryBeanTests {
this.factory.afterPropertiesSet(); this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject(); Gson gson = this.factory.getObject();
ByteArrayBean bean = new ByteArrayBean(); ByteArrayBean bean = new ByteArrayBean();
bean.setBytes(new byte[] { 0x1, 0x2 }); bean.setBytes(new byte[] {0x1, 0x2});
String result = gson.toJson(bean); String result = gson.toJson(bean);
assertEquals("{\"bytes\":\"AQI\\u003d\"}", result); assertEquals("{\"bytes\":\"AQI\\u003d\"}", result);
} }
@ -178,7 +151,7 @@ public class GsonFactoryBeanTests {
this.factory.afterPropertiesSet(); this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject(); Gson gson = this.factory.getObject();
ByteArrayBean bean = new ByteArrayBean(); ByteArrayBean bean = new ByteArrayBean();
bean.setBytes(new byte[] { 0x1, 0x2 }); bean.setBytes(new byte[] {0x1, 0x2});
String result = gson.toJson(bean); String result = gson.toJson(bean);
assertEquals("{\"bytes\":\"AQI=\"}", result); assertEquals("{\"bytes\":\"AQI=\"}", result);
} }
@ -189,7 +162,7 @@ public class GsonFactoryBeanTests {
this.factory.afterPropertiesSet(); this.factory.afterPropertiesSet();
Gson gson = this.factory.getObject(); Gson gson = this.factory.getObject();
ByteArrayBean bean = new ByteArrayBean(); ByteArrayBean bean = new ByteArrayBean();
bean.setBytes(new byte[] { 0x1, 0x2 }); bean.setBytes(new byte[] {0x1, 0x2});
String result = gson.toJson(bean); String result = gson.toJson(bean);
assertEquals("{\"bytes\":[1,2]}", result); assertEquals("{\"bytes\":[1,2]}", result);
} }
@ -208,6 +181,7 @@ public class GsonFactoryBeanTests {
} }
} }
private static class DateBean { private static class DateBean {
private Date date; private Date date;
@ -221,7 +195,8 @@ public class GsonFactoryBeanTests {
} }
} }
public static class ByteArrayBean {
private static class ByteArrayBean {
private byte[] bytes; private byte[] bytes;