Adopt JsonElementWriter in GSON.
Add setSerializeNulls() to JsonWriter, so nulls can be skipped from serialization. This does not yet impact JsonElementWriter. One change in behavior: if the only value is skipped, we now emit "null" rather than "".
This commit is contained in:
parent
364de80611
commit
bb7f0b6bb0
@ -54,8 +54,16 @@ GSON 1.x sometimes sets subclass fields when an InstanceCreator returns a subcla
|
||||
GSON 2.x sets fields of the requested type only
|
||||
com.google.gson.functional.InstanceCreatorTest.testInstanceCreatorReturnsSubTypeForField
|
||||
|
||||
|
||||
GSON 1.x applies different rules for versioning for classes vs fields. So, if you deserialize a
|
||||
JSON into a field that is supposed to be skipped, the field is set to null (or default value).
|
||||
JSON into a field that is supposed to be skipped, the field is set to null (or default value).
|
||||
However, if you deserialize it to a top-level class, a default instance is returned.
|
||||
GSON 2.x returns null for the top-level class.
|
||||
com.google.gson.functional.VersioningTest.testIgnoreLaterVersionClassDeserialization
|
||||
GSON 2.x returns null for the top-level class.
|
||||
com.google.gson.functional.VersioningTest.testIgnoreLaterVersionClassDeserialization
|
||||
|
||||
|
||||
GSON 1.x creates the empty string "" if the only element is skipped
|
||||
GSON 2.x writes "null" if the only element is skipped
|
||||
com.google.gson.functional.ObjectTest.testAnonymousLocalClassesSerialization
|
||||
com.google.gson.functional.FieldExclusionTest.testInnerClassExclusion
|
||||
com.google.gson.functional.VersioningTest.testIgnoreLaterVersionClassSerialization
|
@ -443,7 +443,7 @@ public final class Gson {
|
||||
*/
|
||||
public String toJson(Object src, Type typeOfSrc) {
|
||||
StringWriter writer = new StringWriter();
|
||||
toJson(toJsonTree(src, typeOfSrc), writer);
|
||||
toJson(src, typeOfSrc, writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@ -486,8 +486,12 @@ public final class Gson {
|
||||
* @since 1.2
|
||||
*/
|
||||
public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException {
|
||||
JsonElement jsonElement = toJsonTree(src, typeOfSrc);
|
||||
toJson(jsonElement, writer);
|
||||
try {
|
||||
JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
|
||||
toJson(src, typeOfSrc, jsonWriter);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -496,7 +500,22 @@ public final class Gson {
|
||||
* @throws JsonIOException if there was a problem writing to the writer
|
||||
*/
|
||||
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
|
||||
toJson(toJsonTree(src, typeOfSrc), writer);
|
||||
TypeAdapter<?> adapter = miniGson.getAdapter(TypeToken.get(typeOfSrc));
|
||||
boolean oldLenient = writer.isLenient();
|
||||
writer.setLenient(true);
|
||||
boolean oldHtmlSafe = writer.isHtmlSafe();
|
||||
writer.setHtmlSafe(htmlSafe);
|
||||
boolean oldSerializeNulls = writer.getSerializeNulls();
|
||||
writer.setSerializeNulls(serializeNulls);
|
||||
try {
|
||||
((TypeAdapter<Object>) adapter).write(writer, src);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
} finally {
|
||||
writer.setLenient(oldLenient);
|
||||
writer.setHtmlSafe(oldHtmlSafe);
|
||||
writer.setSerializeNulls(oldSerializeNulls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -522,19 +541,29 @@ public final class Gson {
|
||||
*/
|
||||
public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOException {
|
||||
try {
|
||||
if (generateNonExecutableJson) {
|
||||
writer.append(JSON_NON_EXECUTABLE_PREFIX);
|
||||
}
|
||||
JsonWriter jsonWriter = new JsonWriter(Streams.writerForAppendable(writer));
|
||||
if (prettyPrinting) {
|
||||
jsonWriter.setIndent(" ");
|
||||
}
|
||||
JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
|
||||
toJson(jsonElement, jsonWriter);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new JSON writer configured for this GSON and with the non-execute
|
||||
* prefix if that is configured.
|
||||
*/
|
||||
private JsonWriter newJsonWriter(Writer writer) throws IOException {
|
||||
if (generateNonExecutableJson) {
|
||||
writer.write(JSON_NON_EXECUTABLE_PREFIX);
|
||||
}
|
||||
JsonWriter jsonWriter = new JsonWriter(writer);
|
||||
if (prettyPrinting) {
|
||||
jsonWriter.setIndent(" ");
|
||||
}
|
||||
jsonWriter.setSerializeNulls(serializeNulls);
|
||||
return jsonWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the JSON for {@code jsonElement} to {@code writer}.
|
||||
* @throws JsonIOException if there was a problem writing to the writer
|
||||
@ -544,6 +573,8 @@ public final class Gson {
|
||||
writer.setLenient(true);
|
||||
boolean oldHtmlSafe = writer.isHtmlSafe();
|
||||
writer.setHtmlSafe(htmlSafe);
|
||||
boolean oldSerializeNulls = writer.getSerializeNulls();
|
||||
writer.setSerializeNulls(serializeNulls);
|
||||
try {
|
||||
Streams.write(jsonElement, serializeNulls, writer);
|
||||
} catch (IOException e) {
|
||||
@ -551,6 +582,7 @@ public final class Gson {
|
||||
} finally {
|
||||
writer.setLenient(oldLenient);
|
||||
writer.setHtmlSafe(oldHtmlSafe);
|
||||
writer.setSerializeNulls(oldSerializeNulls);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,10 @@ public class JsonWriter implements Closeable {
|
||||
|
||||
private boolean htmlSafe;
|
||||
|
||||
private String deferredName;
|
||||
|
||||
private boolean serializeNulls = true;
|
||||
|
||||
/**
|
||||
* Creates a new instance that writes a JSON-encoded stream to {@code out}.
|
||||
* For best performance, ensure {@link Writer} is buffered; wrapping in
|
||||
@ -217,6 +221,22 @@ public class JsonWriter implements Closeable {
|
||||
return htmlSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether object members are serialized when their value is null.
|
||||
* This has no impact on array elements. The default is true.
|
||||
*/
|
||||
public final void setSerializeNulls(boolean serializeNulls) {
|
||||
this.serializeNulls = serializeNulls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if object members are serialized when their value is null.
|
||||
* This has no impact on array elements. The default is true.
|
||||
*/
|
||||
public final boolean getSerializeNulls() {
|
||||
return serializeNulls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins encoding a new array. Each call to this method must be paired with
|
||||
* a call to {@link #endArray}.
|
||||
@ -224,6 +244,7 @@ public class JsonWriter implements Closeable {
|
||||
* @return this writer.
|
||||
*/
|
||||
public JsonWriter beginArray() throws IOException {
|
||||
writeDeferredName();
|
||||
return open(JsonScope.EMPTY_ARRAY, "[");
|
||||
}
|
||||
|
||||
@ -243,6 +264,7 @@ public class JsonWriter implements Closeable {
|
||||
* @return this writer.
|
||||
*/
|
||||
public JsonWriter beginObject() throws IOException {
|
||||
writeDeferredName();
|
||||
return open(JsonScope.EMPTY_OBJECT, "{");
|
||||
}
|
||||
|
||||
@ -276,6 +298,9 @@ public class JsonWriter implements Closeable {
|
||||
if (context != nonempty && context != empty) {
|
||||
throw new IllegalStateException("Nesting problem: " + stack);
|
||||
}
|
||||
if (deferredName != null) {
|
||||
throw new IllegalStateException("Dangling name: " + deferredName);
|
||||
}
|
||||
|
||||
stack.remove(stack.size() - 1);
|
||||
if (context == nonempty) {
|
||||
@ -309,11 +334,21 @@ public class JsonWriter implements Closeable {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name == null");
|
||||
}
|
||||
beforeName();
|
||||
string(name);
|
||||
if (deferredName != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
deferredName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void writeDeferredName() throws IOException {
|
||||
if (deferredName != null) {
|
||||
beforeName();
|
||||
string(deferredName);
|
||||
deferredName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes {@code value}.
|
||||
*
|
||||
@ -324,6 +359,7 @@ public class JsonWriter implements Closeable {
|
||||
if (value == null) {
|
||||
return nullValue();
|
||||
}
|
||||
writeDeferredName();
|
||||
beforeValue(false);
|
||||
string(value);
|
||||
return this;
|
||||
@ -335,6 +371,14 @@ public class JsonWriter implements Closeable {
|
||||
* @return this writer.
|
||||
*/
|
||||
public JsonWriter nullValue() throws IOException {
|
||||
if (deferredName != null) {
|
||||
if (serializeNulls) {
|
||||
writeDeferredName();
|
||||
} else {
|
||||
deferredName = null;
|
||||
return this; // skip the name and the value
|
||||
}
|
||||
}
|
||||
beforeValue(false);
|
||||
out.write("null");
|
||||
return this;
|
||||
@ -346,6 +390,7 @@ public class JsonWriter implements Closeable {
|
||||
* @return this writer.
|
||||
*/
|
||||
public JsonWriter value(boolean value) throws IOException {
|
||||
writeDeferredName();
|
||||
beforeValue(false);
|
||||
out.write(value ? "true" : "false");
|
||||
return this;
|
||||
@ -362,6 +407,7 @@ public class JsonWriter implements Closeable {
|
||||
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
||||
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
|
||||
}
|
||||
writeDeferredName();
|
||||
beforeValue(false);
|
||||
out.append(Double.toString(value));
|
||||
return this;
|
||||
@ -373,6 +419,7 @@ public class JsonWriter implements Closeable {
|
||||
* @return this writer.
|
||||
*/
|
||||
public JsonWriter value(long value) throws IOException {
|
||||
writeDeferredName();
|
||||
beforeValue(false);
|
||||
out.write(Long.toString(value));
|
||||
return this;
|
||||
@ -390,6 +437,7 @@ public class JsonWriter implements Closeable {
|
||||
return nullValue();
|
||||
}
|
||||
|
||||
writeDeferredName();
|
||||
String string = value.toString();
|
||||
if (!lenient
|
||||
&& (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
|
||||
|
@ -32,59 +32,59 @@ public class FieldExclusionTest extends TestCase {
|
||||
private static final String VALUE = "blah_1234";
|
||||
|
||||
private Outer outer;
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
outer = new Outer();
|
||||
}
|
||||
|
||||
|
||||
public void testDefaultInnerClassExclusion() throws Exception {
|
||||
Gson gson = new Gson();
|
||||
Outer.Inner target = outer.new Inner(VALUE);
|
||||
String result = gson.toJson(target);
|
||||
assertEquals(target.toJson(), result);
|
||||
|
||||
|
||||
gson = new GsonBuilder().create();
|
||||
target = outer.new Inner(VALUE);
|
||||
result = gson.toJson(target);
|
||||
assertEquals(target.toJson(), result);
|
||||
}
|
||||
|
||||
|
||||
public void testInnerClassExclusion() throws Exception {
|
||||
Gson gson = new GsonBuilder().disableInnerClassSerialization().create();
|
||||
Outer.Inner target = outer.new Inner(VALUE);
|
||||
String result = gson.toJson(target);
|
||||
assertEquals("", result);
|
||||
assertEquals("null", result);
|
||||
}
|
||||
|
||||
|
||||
public void testDefaultNestedStaticClassIncluded() throws Exception {
|
||||
Gson gson = new Gson();
|
||||
Outer.Inner target = outer.new Inner(VALUE);
|
||||
String result = gson.toJson(target);
|
||||
assertEquals(target.toJson(), result);
|
||||
|
||||
|
||||
gson = new GsonBuilder().create();
|
||||
target = outer.new Inner(VALUE);
|
||||
result = gson.toJson(target);
|
||||
assertEquals(target.toJson(), result);
|
||||
}
|
||||
|
||||
|
||||
private static class Outer {
|
||||
private class Inner extends NestedClass {
|
||||
public Inner(String value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class NestedClass {
|
||||
private final String value;
|
||||
public NestedClass(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
public String toJson() {
|
||||
return "{\"value\":\"" + value + "\"}";
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class ObjectTest extends TestCase {
|
||||
assertEquals(10, target.intValue);
|
||||
assertEquals(20, target.longValue);
|
||||
}
|
||||
|
||||
|
||||
public void testJsonInMixedQuotesDeserialization() {
|
||||
String json = "{\"stringValue\":'no message','intValue':10,'longValue':20}";
|
||||
BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
|
||||
@ -69,7 +69,7 @@ public class ObjectTest extends TestCase {
|
||||
assertEquals(10, target.intValue);
|
||||
assertEquals(20, target.longValue);
|
||||
}
|
||||
|
||||
|
||||
public void testBagOfPrimitivesSerialization() throws Exception {
|
||||
BagOfPrimitives target = new BagOfPrimitives(10, 20, false, "stringValue");
|
||||
assertEquals(target.getExpectedJson(), gson.toJson(target));
|
||||
@ -201,7 +201,7 @@ public class ObjectTest extends TestCase {
|
||||
String stringValue = "someStringValueInArray";
|
||||
String classWithObjectsJson = gson.toJson(classWithObjects);
|
||||
String bagOfPrimitivesJson = gson.toJson(bagOfPrimitives);
|
||||
|
||||
|
||||
ClassWithArray classWithArray = new ClassWithArray(
|
||||
new Object[] { stringValue, classWithObjects, bagOfPrimitives });
|
||||
String json = gson.toJson(classWithArray);
|
||||
@ -267,7 +267,7 @@ public class ObjectTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testAnonymousLocalClassesSerialization() throws Exception {
|
||||
assertEquals("", gson.toJson(new ClassWithNoFields() {
|
||||
assertEquals("null", gson.toJson(new ClassWithNoFields() {
|
||||
// empty anonymous class
|
||||
}));
|
||||
}
|
||||
@ -278,7 +278,7 @@ public class ObjectTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a class field with type Object can be serialized properly.
|
||||
* Tests that a class field with type Object can be serialized properly.
|
||||
* See issue 54
|
||||
*/
|
||||
public void testClassWithObjectFieldSerialization() {
|
||||
@ -292,28 +292,28 @@ public class ObjectTest extends TestCase {
|
||||
@SuppressWarnings("unused")
|
||||
Object member;
|
||||
}
|
||||
|
||||
public void testInnerClassSerialization() {
|
||||
|
||||
public void testInnerClassSerialization() {
|
||||
Parent p = new Parent();
|
||||
Parent.Child c = p.new Child();
|
||||
String json = gson.toJson(c);
|
||||
assertTrue(json.contains("value2"));
|
||||
assertFalse(json.contains("value1"));
|
||||
}
|
||||
|
||||
|
||||
public void testInnerClassDeserialization() {
|
||||
final Parent p = new Parent();
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(
|
||||
Parent.Child.class, new InstanceCreator<Parent.Child>() {
|
||||
public Parent.Child createInstance(Type type) {
|
||||
return p.new Child();
|
||||
}
|
||||
}
|
||||
}).create();
|
||||
String json = "{'value2':3}";
|
||||
Parent.Child c = gson.fromJson(json, Parent.Child.class);
|
||||
assertEquals(3, c.value2);
|
||||
}
|
||||
|
||||
|
||||
private static class Parent {
|
||||
@SuppressWarnings("unused")
|
||||
int value1 = 1;
|
||||
@ -365,7 +365,7 @@ public class ObjectTest extends TestCase {
|
||||
a = 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* In response to Issue 41 http://code.google.com/p/google-gson/issues/detail?id=41
|
||||
*/
|
||||
@ -376,16 +376,16 @@ public class ObjectTest extends TestCase {
|
||||
assertTrue(bag.booleanValue);
|
||||
assertEquals("bar", bag.stringValue);
|
||||
}
|
||||
|
||||
|
||||
public void testStringFieldWithNumberValueDeserialization() {
|
||||
String json = "{\"stringValue\":1}";
|
||||
BagOfPrimitives bag = gson.fromJson(json, BagOfPrimitives.class);
|
||||
assertEquals("1", bag.stringValue);
|
||||
|
||||
|
||||
json = "{\"stringValue\":1.5E+6}";
|
||||
bag = gson.fromJson(json, BagOfPrimitives.class);
|
||||
assertEquals("1.5E+6", bag.stringValue);
|
||||
|
||||
|
||||
json = "{\"stringValue\":true}";
|
||||
bag = gson.fromJson(json, BagOfPrimitives.class);
|
||||
assertEquals("true", bag.stringValue);
|
||||
@ -419,7 +419,7 @@ public class ObjectTest extends TestCase {
|
||||
String b = "";
|
||||
String c = "";
|
||||
}
|
||||
|
||||
|
||||
public void testJsonObjectSerialization() {
|
||||
Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
JsonObject obj = new JsonObject();
|
||||
|
@ -48,12 +48,12 @@ public class VersioningTest extends TestCase {
|
||||
Gson gson = builder.setVersion(1.29).create();
|
||||
String json = gson.toJson(target);
|
||||
assertTrue(json.contains("\"a\":" + A));
|
||||
|
||||
|
||||
gson = builder.setVersion(1.3).create();
|
||||
json = gson.toJson(target);
|
||||
assertFalse(json.contains("\"a\":" + A));
|
||||
}
|
||||
|
||||
|
||||
public void testVersionedUntilDeserialization() {
|
||||
Gson gson = builder.setVersion(1.3).create();
|
||||
String json = "{\"a\":3,\"b\":4,\"c\":5}";
|
||||
@ -82,7 +82,7 @@ public class VersioningTest extends TestCase {
|
||||
|
||||
public void testIgnoreLaterVersionClassSerialization() {
|
||||
Gson gson = builder.setVersion(1.0).create();
|
||||
assertEquals("", gson.toJson(new Version1_2()));
|
||||
assertEquals("null", gson.toJson(new Version1_2()));
|
||||
}
|
||||
|
||||
public void testIgnoreLaterVersionClassDeserialization() {
|
||||
@ -117,11 +117,11 @@ public class VersioningTest extends TestCase {
|
||||
SinceUntilMixing target = new SinceUntilMixing();
|
||||
String json = gson.toJson(target);
|
||||
assertFalse(json.contains("\"b\":" + B));
|
||||
|
||||
|
||||
gson = builder.setVersion(1.2).create();
|
||||
json = gson.toJson(target);
|
||||
assertTrue(json.contains("\"b\":" + B));
|
||||
|
||||
|
||||
gson = builder.setVersion(1.3).create();
|
||||
json = gson.toJson(target);
|
||||
assertFalse(json.contains("\"b\":" + B));
|
||||
@ -133,12 +133,12 @@ public class VersioningTest extends TestCase {
|
||||
SinceUntilMixing result = gson.fromJson(json, SinceUntilMixing.class);
|
||||
assertEquals(5, result.a);
|
||||
assertEquals(B, result.b);
|
||||
|
||||
|
||||
gson = builder.setVersion(1.2).create();
|
||||
result = gson.fromJson(json, SinceUntilMixing.class);
|
||||
assertEquals(5, result.a);
|
||||
assertEquals(6, result.b);
|
||||
|
||||
|
||||
gson = builder.setVersion(1.3).create();
|
||||
result = gson.fromJson(json, SinceUntilMixing.class);
|
||||
assertEquals(5, result.a);
|
||||
@ -158,10 +158,10 @@ public class VersioningTest extends TestCase {
|
||||
private static class Version1_2 extends Version1_1 {
|
||||
int d = D;
|
||||
}
|
||||
|
||||
|
||||
private static class SinceUntilMixing {
|
||||
int a = A;
|
||||
|
||||
|
||||
@Since(1.1)
|
||||
@Until(1.3)
|
||||
int b = B;
|
||||
|
@ -24,6 +24,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
// TODO: more tests
|
||||
// TODO: close support
|
||||
// TODO: figure out what should be returned by an empty writer
|
||||
// TODO: test when serialize nulls is false
|
||||
|
||||
public void testArray() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
|
Loading…
Reference in New Issue
Block a user