diff --git a/gson/pom.xml b/gson/pom.xml index ad8f937de..007397956 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -117,6 +117,43 @@ 2.11 test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + test + + + com.fasterxml.woodstox + woodstox-core + 5.1.0 + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-smile + ${jackson.version} + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-properties + ${jackson.version} + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor + ${jackson.version} + test + diff --git a/gson/src/org/immutables/gson/stream/JsonGeneratorWriter.java b/gson/src/org/immutables/gson/stream/JsonGeneratorWriter.java index 6b726e492..988d3b249 100644 --- a/gson/src/org/immutables/gson/stream/JsonGeneratorWriter.java +++ b/gson/src/org/immutables/gson/stream/JsonGeneratorWriter.java @@ -17,13 +17,16 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.google.gson.stream.JsonWriter; + +import javax.annotation.concurrent.NotThreadSafe; import java.io.IOException; import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.concurrent.Callable; -import javax.annotation.concurrent.NotThreadSafe; /** - * {@link JsonWriter} impementation backed by Jackson's {@link JsonGenerator}. + * {@link JsonWriter} implementation backed by Jackson's {@link JsonGenerator}. * Provides measurable JSON writing improvements over Gson's native implementation. * Error reporting is might differ, however. */ @@ -139,16 +142,42 @@ public JsonWriter value(Number value) throws IOException { if (value == null) { return nullValue(); } - double d = value.doubleValue(); - if (!isLenient()) { - if (Double.isNaN(d) || Double.isInfinite(d)) { - throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value); - } + if (value instanceof Integer) { + generator.writeNumber(value.intValue()); + } else if (value instanceof Short) { + generator.writeNumber(value.shortValue()); + } else if (value instanceof Long) { + generator.writeNumber(value.longValue()); + } else if (value instanceof Float) { + float f = value.floatValue(); + checkStrictNumber(f); + generator.writeNumber(f); + } else if (value instanceof BigInteger) { + generator.writeNumber((BigInteger) value); + } else if (value instanceof BigDecimal) { + BigDecimal bd = (BigDecimal) value; + checkStrictNumber(bd); + generator.writeNumber(bd); + } else { + double d = value.doubleValue(); + checkStrictNumber(d); + generator.writeNumber(d); } - generator.writeNumber(d); return this; } + private void checkStrictNumber(Number number) { + if (!isLenient()) { + checkStrictNumber(number.doubleValue()); + } + } + + private void checkStrictNumber(double number) { + if (!isLenient() && (Double.isInfinite(number) || Double.isNaN(number))) { + throw new IllegalArgumentException("JSON forbids NaN and infinities: " + number); + } + } + @Override public void flush() throws IOException { generator.flush(); diff --git a/gson/src/org/immutables/gson/stream/JsonParserReader.java b/gson/src/org/immutables/gson/stream/JsonParserReader.java index a610c4bb2..052a65394 100644 --- a/gson/src/org/immutables/gson/stream/JsonParserReader.java +++ b/gson/src/org/immutables/gson/stream/JsonParserReader.java @@ -16,6 +16,7 @@ package org.immutables.gson.stream; import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.databind.util.TokenBuffer; @@ -31,7 +32,7 @@ import static com.fasterxml.jackson.core.JsonToken.*; /** - * {@link JsonReader} impementation backed by Jackson's {@link JsonParser}. + * {@link JsonReader} implementation backed by Jackson's {@link JsonParser}. * Provides measurable JSON parsing improvements over Gson's native implementation. * Error reporting might differ, however. */ diff --git a/gson/src/org/immutables/gson/stream/XmlParserReader.java b/gson/src/org/immutables/gson/stream/XmlParserReader.java new file mode 100644 index 000000000..0f3ba7bac --- /dev/null +++ b/gson/src/org/immutables/gson/stream/XmlParserReader.java @@ -0,0 +1,39 @@ +package org.immutables.gson.stream; + +import com.fasterxml.jackson.core.JsonParser; +import com.google.gson.stream.JsonReader; + +import javax.annotation.concurrent.NotThreadSafe; +import java.io.IOException; + +/** + * {@link JsonReader} implementation backed by Jackson's {@link JsonParser}. + * This version assumes the token producer only supports strings, therefore will + * work with the XML and properties formats. + */ +@NotThreadSafe +public class XmlParserReader extends JsonParserReader { + public XmlParserReader(JsonParser parser) { + super(parser); + } + + @Override + public boolean nextBoolean() throws IOException { + return Boolean.parseBoolean(nextString()); + } + + @Override + public double nextDouble() throws IOException { + return Double.parseDouble(nextString()); + } + + @Override + public long nextLong() throws IOException { + return Long.parseLong(nextString()); + } + + @Override + public int nextInt() throws IOException { + return Integer.parseInt(nextString()); + } +} diff --git a/gson/test/org/immutables/gson/bridge/GsonJacksonBridgeSerializationTest.java b/gson/test/org/immutables/gson/bridge/GsonJacksonBridgeSerializationTest.java new file mode 100644 index 000000000..bbf3797e3 --- /dev/null +++ b/gson/test/org/immutables/gson/bridge/GsonJacksonBridgeSerializationTest.java @@ -0,0 +1,158 @@ +package org.immutables.gson.bridge; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.fasterxml.jackson.dataformat.javaprop.JavaPropsFactory; +import com.fasterxml.jackson.dataformat.smile.SmileFactory; +import com.fasterxml.jackson.dataformat.xml.XmlFactory; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.immutables.gson.stream.JsonGeneratorWriter; +import org.immutables.gson.stream.JsonParserReader; +import org.immutables.gson.stream.XmlParserReader; +import org.junit.Assert; +import org.junit.Test; + +import javax.xml.namespace.QName; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; + +/** + * Test the various Jackson serialization formats against the Gson/Jackson bridge. + */ +public class GsonJacksonBridgeSerializationTest { + + private final Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(new GsonAdaptersTestObject()) + .registerTypeAdapterFactory(new GsonAdaptersTestSubObject()) + .create(); + + private TestObject createTestObject() { + return ImmutableTestObject.builder() + .intVal(123) + .integerVal(123) + .addListOfInteger(1, 2, 3, 4, 5, 6) + .longVal(2349823948398472394L) + .longObjVal(2349823948398472394L) + .addListOfLong(2349823948398472394L, 2349822348398472394L, 2349823948123332394L) + .floatVal(123123.0980F) + .floatObjVal(123123.0980F) + .addListOfFloat(123123.0980F, 124234.0980F, 1233455.0980F) + .doubleVal(123123123.1231231) + .doubleObjVal(123123123.1231231) + .addListOfDouble(123123.1231, 123123123.123213, 234234234.23423) + .bigDecimalVal(new BigDecimal(4234234.1312313123)) + .addListOfBigDecimal(new BigDecimal(123123.123123), new BigDecimal(3534345.345345), new BigDecimal(35252.2411)) + .booleanVal(true) + .booleanObjVal(false) + .addListOfBoolean(true, false, true) + .stringVal("This is a test") + .addListOfString("a", "b", "c") + .addSetOfString("a", "b", "c") + .putMapOfStringToString("a", "1") + .putMapOfStringToString("b", "2") + .testEnumVal(TestEnum.VALUE1) + .putMapOfTestEnumToString(TestEnum.VALUE2, "test2") + .putMapOfTestEnumToString(TestEnum.VALUE3, "test3") + .addListOfSubObject(ImmutableTestSubObject.builder() + .a("Test") + .b(1231) + .build()) + .build(); + } + + @Test + public void jsonFactoryTest() throws IOException { + TestObject value = createTestObject(); + JsonFactory factory = new JsonFactory(); + Class clazz = TestObject.class; + ByteArrayOutputStream outputStream = testWriting(value, factory, clazz); + TestObject value2 = testReading(factory, clazz, outputStream); + Assert.assertEquals(value2.toString(), value.toString()); + } + + @Test + public void xmlFactoryTest() throws IOException { + TestObject value = createTestObject(); + XmlFactory factory = new XmlFactory(); + Class clazz = TestObject.class; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ToXmlGenerator g = factory.createGenerator(outputStream); + g.setNextName(QName.valueOf(clazz.getAnnotation(JacksonXmlRootElement.class).localName())); + JsonGeneratorWriter generatorWriter = new JsonGeneratorWriter(g); + gson.toJson(value, clazz, generatorWriter); + generatorWriter.flush(); + TestObject value2 = testXmlReading(factory, clazz, outputStream); + Assert.assertEquals(value2.toString(), value.toString()); + } + + @Test + public void yamlFactoryTest() throws IOException { + TestObject value = createTestObject(); + YAMLFactory factory = new YAMLFactory(); + Class clazz = TestObject.class; + ByteArrayOutputStream outputStream = testWriting(value, factory, clazz); + TestObject value2 = testReading(factory, clazz, outputStream); + Assert.assertEquals(value2.toString(), value.toString()); + } + + @Test + public void smileFactoryTest() throws IOException { + TestObject value = createTestObject(); + SmileFactory factory = new SmileFactory(); + Class clazz = TestObject.class; + ByteArrayOutputStream outputStream = testWriting(value, factory, clazz); + TestObject value2 = testReading(factory, clazz, outputStream); + Assert.assertEquals(value2.toString(), value.toString()); + } + + @Test + public void propertiesFactoryTest() throws IOException { + TestObject value = ImmutableTestObject.copyOf(createTestObject()) + // excluding map here because when it is de-serialized, the order is changed and the unit test fails + .withMapOfStringToString(new HashMap()); + JavaPropsFactory factory = new JavaPropsFactory(); + Class clazz = TestObject.class; + ByteArrayOutputStream outputStream = testWriting(value, factory, clazz); + TestObject value2 = testXmlReading(factory, clazz, outputStream); + Assert.assertEquals(value2.toString(), value.toString()); + } + + @Test + public void cborFactoryTest() throws IOException { + TestObject value = createTestObject(); + CBORFactory factory = new CBORFactory(); + Class clazz = TestObject.class; + ByteArrayOutputStream outputStream = testWriting(value, factory, clazz); + TestObject value2 = testReading(factory, clazz, outputStream); + Assert.assertEquals(value2.toString(), value.toString()); + } + + private ByteArrayOutputStream testWriting(TestObject value, JsonFactory factory, Class clazz) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + JsonGenerator g = factory.createGenerator(outputStream); + JsonGeneratorWriter generatorWriter = new JsonGeneratorWriter(g); + gson.toJson(value, clazz, generatorWriter); + generatorWriter.flush(); + return outputStream; + } + + private TestObject testReading(JsonFactory factory, Class clazz, ByteArrayOutputStream outputStream) throws IOException { + JsonParser r = factory.createParser(outputStream.toByteArray()); + JsonParserReader parserReader = new JsonParserReader(r); + return gson.fromJson(parserReader, clazz); + } + + private TestObject testXmlReading(JsonFactory factory, Class clazz, ByteArrayOutputStream outputStream) throws IOException { + JsonParser r = factory.createParser(outputStream.toByteArray()); + JsonParserReader parserReader = new XmlParserReader(r); + return gson.fromJson(parserReader, clazz); + } +} diff --git a/gson/test/org/immutables/gson/bridge/TestEnum.java b/gson/test/org/immutables/gson/bridge/TestEnum.java new file mode 100644 index 000000000..018e6cf55 --- /dev/null +++ b/gson/test/org/immutables/gson/bridge/TestEnum.java @@ -0,0 +1,5 @@ +package org.immutables.gson.bridge; + +public enum TestEnum { + VALUE1, VALUE2, VALUE3 +} diff --git a/gson/test/org/immutables/gson/bridge/TestObject.java b/gson/test/org/immutables/gson/bridge/TestObject.java new file mode 100644 index 000000000..a9d86b669 --- /dev/null +++ b/gson/test/org/immutables/gson/bridge/TestObject.java @@ -0,0 +1,65 @@ +package org.immutables.gson.bridge; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import org.immutables.gson.Gson; +import org.immutables.value.Value; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Gson.TypeAdapters +@Value.Immutable +@JacksonXmlRootElement(localName = "test-object") +public interface TestObject { + int intVal(); + + Integer integerVal(); + + List listOfInteger(); + + long longVal(); + + Long longObjVal(); + + List listOfLong(); + + float floatVal(); + + Float floatObjVal(); + + List listOfFloat(); + + double doubleVal(); + + Double doubleObjVal(); + + List listOfDouble(); + + BigDecimal bigDecimalVal(); + + List listOfBigDecimal(); + + boolean booleanVal(); + + Boolean booleanObjVal(); + + List listOfBoolean(); + + String stringVal(); + + List listOfString(); + + Set setOfString(); + + Map mapOfStringToString(); + + TestEnum testEnumVal(); + + List listOfTestEnum(); + + Map mapOfTestEnumToString(); + + List listOfSubObject(); +} diff --git a/gson/test/org/immutables/gson/bridge/TestSubObject.java b/gson/test/org/immutables/gson/bridge/TestSubObject.java new file mode 100644 index 000000000..20ccbc0fe --- /dev/null +++ b/gson/test/org/immutables/gson/bridge/TestSubObject.java @@ -0,0 +1,12 @@ +package org.immutables.gson.bridge; + +import org.immutables.gson.Gson; +import org.immutables.value.Value; + +@Gson.TypeAdapters +@Value.Immutable +public interface TestSubObject { + String a(); + + Number b(); +}