Consistent object type exposure for JSON rendering (workaround for Gson)

Issue: SPR-16461

(cherry picked from commit 817a836)
This commit is contained in:
Juergen Hoeller
2018-02-05 22:51:33 +01:00
parent 5fd761ee39
commit fd964ca4b1
3 changed files with 253 additions and 89 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -26,6 +26,7 @@ import java.util.Map;
import com.google.gson.reflect.TypeToken;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
@@ -39,12 +40,13 @@ import static org.junit.Assert.*;
* Gson 2.x converter tests.
*
* @author Roy Clarkson
* @author Juergen Hoeller
*/
public class GsonHttpMessageConverterTests {
private static final Charset UTF8 = Charset.forName("UTF-8");
private GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
private final GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
@Test
@@ -76,9 +78,9 @@ public class GsonHttpMessageConverterTests {
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
}
@Test
@@ -104,7 +106,7 @@ public class GsonHttpMessageConverterTests {
for (int i = 0; i < 2; i++) {
bytes[i] = resultBytes.get(i).byteValue();
}
assertArrayEquals(new byte[]{0x1, 0x2}, bytes);
assertArrayEquals(new byte[] {0x1, 0x2}, bytes);
}
@Test
@@ -114,9 +116,9 @@ public class GsonHttpMessageConverterTests {
body.setString("Foo");
body.setNumber(42);
body.setFraction(42F);
body.setArray(new String[]{"Foo", "Bar"});
body.setArray(new String[] {"Foo", "Bar"});
body.setBool(true);
body.setBytes(new byte[]{0x1, 0x2});
body.setBytes(new byte[] {0x1, 0x2});
this.converter.write(body, null, outputMessage);
String result = outputMessage.getBodyAsString(UTF8);
assertTrue(result.contains("\"string\":\"Foo\""));
@@ -129,6 +131,28 @@ public class GsonHttpMessageConverterTests {
outputMessage.getHeaders().getContentType());
}
@Test
public void writeWithBaseType() throws IOException {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
MyBean body = new MyBean();
body.setString("Foo");
body.setNumber(42);
body.setFraction(42F);
body.setArray(new String[] {"Foo", "Bar"});
body.setBool(true);
body.setBytes(new byte[] {0x1, 0x2});
this.converter.write(body, MyBase.class, null, outputMessage);
String result = outputMessage.getBodyAsString(UTF8);
assertTrue(result.contains("\"string\":\"Foo\""));
assertTrue(result.contains("\"number\":42"));
assertTrue(result.contains("fraction\":42.0"));
assertTrue(result.contains("\"array\":[\"Foo\",\"Bar\"]"));
assertTrue(result.contains("\"bool\":true"));
assertTrue(result.contains("\"bytes\":[1,2]"));
assertEquals("Invalid content-type", new MediaType("application", "json", UTF8),
outputMessage.getHeaders().getContentType());
}
@Test
public void writeUTF16() throws IOException {
Charset utf16 = Charset.forName("UTF-16BE");
@@ -174,14 +198,14 @@ public class GsonHttpMessageConverterTests {
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[] { "Foo", "Bar" }, result.getArray());
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[] { 0x1, 0x2 }, result.getBytes());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
}
@Test
@SuppressWarnings("unchecked")
public void readParameterizedType() throws IOException {
public void readAndWriteParameterizedType() throws Exception {
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {
};
@@ -190,20 +214,49 @@ public class GsonHttpMessageConverterTests {
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(UTF8));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), null, inputMessage);
assertEquals(1, results.size());
MyBean result = results.get(0);
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[] { "Foo", "Bar" }, result.getArray());
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[] { 0x1, 0x2 }, result.getBytes());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(results, beansList.getType(), new MediaType("application", "json"), outputMessage);
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(UTF8), true);
}
@Test
public void prefixJson() throws Exception {
@SuppressWarnings("unchecked")
public void writeParameterizedBaseType() throws Exception {
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {};
ParameterizedTypeReference<List<MyBase>> baseList = new ParameterizedTypeReference<List<MyBase>>() {};
String body = "[{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(UTF8));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), null, inputMessage);
assertEquals(1, results.size());
MyBean result = results.get(0);
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(results, baseList.getType(), new MediaType("application", "json"), outputMessage);
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(UTF8), true);
}
@Test
public void prefixJson() throws IOException {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.setPrefixJson(true);
this.converter.writeInternal("foo", null, outputMessage);
@@ -211,7 +264,7 @@ public class GsonHttpMessageConverterTests {
}
@Test
public void prefixJsonCustom() throws Exception {
public void prefixJsonCustom() throws IOException {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.setJsonPrefix(")))");
this.converter.writeInternal("foo", null, outputMessage);
@@ -219,10 +272,22 @@ public class GsonHttpMessageConverterTests {
}
public static class MyBean {
public static class MyBase {
private String string;
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
public static class MyBean extends MyBase {
private int number;
private float fraction;
@@ -233,30 +298,6 @@ public class GsonHttpMessageConverterTests {
private byte[] bytes;
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public boolean isBool() {
return bool;
}
public void setBool(boolean bool) {
this.bool = bool;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public int getNumber() {
return number;
}
@@ -280,6 +321,32 @@ public class GsonHttpMessageConverterTests {
public void setArray(String[] array) {
this.array = array;
}
public boolean isBool() {
return bool;
}
public void setBool(boolean bool) {
this.bool = bool;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
}
public static class ListHolder<E> {
public List<E> listField;
}
public static class MyBeanListHolder extends ListHolder<MyBean> {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
@@ -47,6 +48,7 @@ import static org.junit.Assert.*;
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @author Juergen Hoeller
*/
public class MappingJackson2HttpMessageConverterTests {
@@ -75,24 +77,34 @@ public class MappingJackson2HttpMessageConverterTests {
@Test
public void readTyped() throws IOException {
String body =
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
String body = "{" +
"\"bytes\":\"AQI=\"," +
"\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42," +
"\"string\":\"Foo\"," +
"\"bool\":true," +
"\"fraction\":42.0}";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
}
@Test
@SuppressWarnings("unchecked")
public void readUntyped() throws IOException {
String body =
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
String body = "{" +
"\"bytes\":\"AQI=\"," +
"\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42," +
"\"string\":\"Foo\"," +
"\"bool\":true," +
"\"fraction\":42.0}";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
HashMap<String, Object> result = (HashMap<String, Object>) converter.read(HashMap.class, inputMessage);
@@ -114,9 +126,9 @@ public class MappingJackson2HttpMessageConverterTests {
body.setString("Foo");
body.setNumber(42);
body.setFraction(42F);
body.setArray(new String[]{"Foo", "Bar"});
body.setArray(new String[] {"Foo", "Bar"});
body.setBool(true);
body.setBytes(new byte[]{0x1, 0x2});
body.setBytes(new byte[] {0x1, 0x2});
converter.write(body, null, outputMessage);
Charset utf8 = Charset.forName("UTF-8");
String result = outputMessage.getBodyAsString(utf8);
@@ -130,6 +142,29 @@ public class MappingJackson2HttpMessageConverterTests {
outputMessage.getHeaders().getContentType());
}
@Test
public void writeWithBaseType() throws IOException {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
MyBean body = new MyBean();
body.setString("Foo");
body.setNumber(42);
body.setFraction(42F);
body.setArray(new String[] {"Foo", "Bar"});
body.setBool(true);
body.setBytes(new byte[] {0x1, 0x2});
converter.write(body, MyBase.class, null, outputMessage);
Charset utf8 = Charset.forName("UTF-8");
String result = outputMessage.getBodyAsString(utf8);
assertTrue(result.contains("\"string\":\"Foo\""));
assertTrue(result.contains("\"number\":42"));
assertTrue(result.contains("fraction\":42.0"));
assertTrue(result.contains("\"array\":[\"Foo\",\"Bar\"]"));
assertTrue(result.contains("\"bool\":true"));
assertTrue(result.contains("\"bytes\":\"AQI=\""));
assertEquals("Invalid content-type", new MediaType("application", "json", utf8),
outputMessage.getHeaders().getContentType());
}
@Test
public void writeUTF16() throws IOException {
Charset utf16 = Charset.forName("UTF-16BE");
@@ -160,7 +195,7 @@ public class MappingJackson2HttpMessageConverterTests {
@Test
@SuppressWarnings("unchecked")
public void readGenerics() throws IOException {
public void readAndWriteGenerics() throws Exception {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter() {
@Override
protected JavaType getJavaType(Type type, Class<?> contextClass) {
@@ -172,8 +207,13 @@ public class MappingJackson2HttpMessageConverterTests {
}
}
};
String body =
"[{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
String body = "[{" +
"\"bytes\":\"AQI=\"," +
"\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42," +
"\"string\":\"Foo\"," +
"\"bool\":true," +
"\"fraction\":42.0}]";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
@@ -183,18 +223,27 @@ public class MappingJackson2HttpMessageConverterTests {
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(results, new MediaType("application", "json"), outputMessage);
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(Charset.forName("UTF-8")), true);
}
@Test
@SuppressWarnings("unchecked")
public void readParameterizedType() throws IOException {
public void readAndWriteParameterizedType() throws Exception {
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {};
String body =
"[{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
String body = "[{" +
"\"bytes\":\"AQI=\"," +
"\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42," +
"\"string\":\"Foo\"," +
"\"bool\":true," +
"\"fraction\":42.0}]";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
@@ -205,11 +254,46 @@ public class MappingJackson2HttpMessageConverterTests {
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(results, beansList.getType(), new MediaType("application", "json"), outputMessage);
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(Charset.forName("UTF-8")), true);
}
@Test
@SuppressWarnings("unchecked")
public void writeParameterizedBaseType() throws Exception {
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {};
ParameterizedTypeReference<List<MyBase>> baseList = new ParameterizedTypeReference<List<MyBase>>() {};
String body = "[{" +
"\"bytes\":\"AQI=\"," +
"\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42," +
"\"string\":\"Foo\"," +
"\"bool\":true," +
"\"fraction\":42.0}]";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), null, inputMessage);
assertEquals(1, results.size());
MyBean result = results.get(0);
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
assertArrayEquals(new String[] {"Foo", "Bar"}, result.getArray());
assertTrue(result.isBool());
assertArrayEquals(new byte[] {0x1, 0x2}, result.getBytes());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(results, baseList.getType(), new MediaType("application", "json"), outputMessage);
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(Charset.forName("UTF-8")), true);
}
@Test
public void prettyPrint() throws Exception {
@@ -221,13 +305,14 @@ public class MappingJackson2HttpMessageConverterTests {
this.converter.writeInternal(bean, null, outputMessage);
String result = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result);
assertEquals("{" + NEWLINE_SYSTEM_PROPERTY +
" \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result);
}
@Test
public void prettyPrintWithSse() throws Exception {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
outputMessage.getHeaders().setContentType(new MediaType("text", "event-stream"));
outputMessage.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM);
PrettyPrintBean bean = new PrettyPrintBean();
bean.setName("Jason");
@@ -342,7 +427,7 @@ public class MappingJackson2HttpMessageConverterTests {
@Test // SPR-13318
public void writeSubTypeList() throws Exception {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
List<MyBean> beans = new ArrayList<MyBean>();
List<MyBean> beans = new ArrayList<>();
MyBean foo = new MyBean();
foo.setString("Foo");
foo.setNumber(42);
@@ -372,10 +457,22 @@ public class MappingJackson2HttpMessageConverterTests {
}
public static class MyBean implements MyInterface {
public static class MyBase implements MyInterface{
private String string;
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
public static class MyBean extends MyBase {
private int number;
private float fraction;
@@ -386,30 +483,6 @@ public class MappingJackson2HttpMessageConverterTests {
private byte[] bytes;
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public boolean isBool() {
return bool;
}
public void setBool(boolean bool) {
this.bool = bool;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public int getNumber() {
return number;
}
@@ -433,6 +506,22 @@ public class MappingJackson2HttpMessageConverterTests {
public void setArray(String[] array) {
this.array = array;
}
public boolean isBool() {
return bool;
}
public void setBool(boolean bool) {
this.bool = bool;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
}