Skip to content

Commit

Permalink
Issues with Gson Jackson Bridge (immutables#1120)
Browse files Browse the repository at this point in the history
- Integers are printed as doubles (delegate to each specific number type method, eg `writeNumber(long`)
- Allow strings for boolean in XML streaming parser
  • Loading branch information
bmazzarol authored and asereda-gs committed Nov 13, 2019
1 parent c5404ca commit 3e3ccc6
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 9 deletions.
37 changes: 37 additions & 0 deletions gson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,43 @@
<version>2.11</version>
<scope>test</scope>
</dependency>
<!-- Test dependencies to test other jackson serialization formats -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
<version>5.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-smile</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-properties</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
45 changes: 37 additions & 8 deletions gson/src/org/immutables/gson/stream/JsonGeneratorWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion gson/src/org/immutables/gson/stream/JsonParserReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*/
Expand Down
39 changes: 39 additions & 0 deletions gson/src/org/immutables/gson/stream/XmlParserReader.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<TestObject> 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<TestObject> 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<TestObject> 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<TestObject> 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<String, String>());
JavaPropsFactory factory = new JavaPropsFactory();
Class<TestObject> 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<TestObject> 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<TestObject> 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<TestObject> 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<TestObject> clazz, ByteArrayOutputStream outputStream) throws IOException {
JsonParser r = factory.createParser(outputStream.toByteArray());
JsonParserReader parserReader = new XmlParserReader(r);
return gson.fromJson(parserReader, clazz);
}
}
5 changes: 5 additions & 0 deletions gson/test/org/immutables/gson/bridge/TestEnum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.immutables.gson.bridge;

public enum TestEnum {
VALUE1, VALUE2, VALUE3
}
65 changes: 65 additions & 0 deletions gson/test/org/immutables/gson/bridge/TestObject.java
Original file line number Diff line number Diff line change
@@ -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<Integer> listOfInteger();

long longVal();

Long longObjVal();

List<Long> listOfLong();

float floatVal();

Float floatObjVal();

List<Float> listOfFloat();

double doubleVal();

Double doubleObjVal();

List<Double> listOfDouble();

BigDecimal bigDecimalVal();

List<BigDecimal> listOfBigDecimal();

boolean booleanVal();

Boolean booleanObjVal();

List<Boolean> listOfBoolean();

String stringVal();

List<String> listOfString();

Set<String> setOfString();

Map<String, String> mapOfStringToString();

TestEnum testEnumVal();

List<TestEnum> listOfTestEnum();

Map<TestEnum, String> mapOfTestEnumToString();

List<TestSubObject> listOfSubObject();
}
Loading

0 comments on commit 3e3ccc6

Please sign in to comment.