Merge pull request #238 from square/jwilson.0129.serializeNulls
New JsonAdapter.serializeNulls() method.
This commit is contained in:
commit
b4367899cd
9 changed files with 235 additions and 91 deletions
|
@ -200,7 +200,7 @@ final class BufferedSinkJsonWriter extends JsonWriter {
|
|||
}
|
||||
|
||||
@Override public JsonWriter value(double value) throws IOException {
|
||||
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
||||
if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
|
||||
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
|
||||
}
|
||||
if (promoteNameToValue) {
|
||||
|
|
|
@ -83,6 +83,31 @@ public abstract class JsonAdapter<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON adapter equal to this JSON adapter, but that serializes nulls when encoding
|
||||
* JSON.
|
||||
*/
|
||||
public final JsonAdapter<T> serializeNulls() {
|
||||
final JsonAdapter<T> delegate = this;
|
||||
return new JsonAdapter<T>() {
|
||||
@Override public T fromJson(JsonReader reader) throws IOException {
|
||||
return delegate.fromJson(reader);
|
||||
}
|
||||
@Override public void toJson(JsonWriter writer, T value) throws IOException {
|
||||
boolean serializeNulls = writer.getSerializeNulls();
|
||||
writer.setSerializeNulls(true);
|
||||
try {
|
||||
delegate.toJson(writer, value);
|
||||
} finally {
|
||||
writer.setSerializeNulls(serializeNulls);
|
||||
}
|
||||
}
|
||||
@Override public String toString() {
|
||||
return delegate + ".serializeNulls()";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON adapter equal to this JSON adapter, but with support for reading and writing
|
||||
* nulls.
|
||||
|
|
167
moshi/src/test/java/com/squareup/moshi/JsonAdapterTest.java
Normal file
167
moshi/src/test/java/com/squareup/moshi/JsonAdapterTest.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class JsonAdapterTest {
|
||||
@Parameter public JsonCodecFactory factory;
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static List<Object[]> parameters() {
|
||||
return JsonCodecFactory.factories();
|
||||
}
|
||||
|
||||
@Test public void lenient() throws Exception {
|
||||
JsonAdapter<Double> lenient = new JsonAdapter<Double>() {
|
||||
@Override public Double fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextDouble();
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, Double value) throws IOException {
|
||||
writer.value(value);
|
||||
}
|
||||
}.lenient();
|
||||
|
||||
JsonReader reader = factory.newReader("[-Infinity, NaN, Infinity]");
|
||||
reader.beginArray();
|
||||
assertThat(lenient.fromJson(reader)).isEqualTo(Double.NEGATIVE_INFINITY);
|
||||
assertThat(lenient.fromJson(reader)).isNaN();
|
||||
assertThat(lenient.fromJson(reader)).isEqualTo(Double.POSITIVE_INFINITY);
|
||||
reader.endArray();
|
||||
|
||||
JsonWriter writer = factory.newWriter();
|
||||
writer.beginArray();
|
||||
lenient.toJson(writer, Double.NEGATIVE_INFINITY);
|
||||
lenient.toJson(writer, Double.NaN);
|
||||
lenient.toJson(writer, Double.POSITIVE_INFINITY);
|
||||
writer.endArray();
|
||||
assertThat(factory.json()).isEqualTo("[-Infinity,NaN,Infinity]");
|
||||
}
|
||||
|
||||
@Test public void nullSafe() throws Exception {
|
||||
JsonAdapter<String> toUpperCase = new JsonAdapter<String>() {
|
||||
@Override public String fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextString().toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, String value) throws IOException {
|
||||
writer.value(value.toUpperCase(Locale.US));
|
||||
}
|
||||
}.nullSafe();
|
||||
|
||||
JsonReader reader = factory.newReader("[\"a\", null, \"c\"]");
|
||||
reader.beginArray();
|
||||
assertThat(toUpperCase.fromJson(reader)).isEqualTo("A");
|
||||
assertThat(toUpperCase.fromJson(reader)).isNull();
|
||||
assertThat(toUpperCase.fromJson(reader)).isEqualTo("C");
|
||||
reader.endArray();
|
||||
|
||||
JsonWriter writer = factory.newWriter();
|
||||
writer.beginArray();
|
||||
toUpperCase.toJson(writer, "a");
|
||||
toUpperCase.toJson(writer, null);
|
||||
toUpperCase.toJson(writer, "c");
|
||||
writer.endArray();
|
||||
assertThat(factory.json()).isEqualTo("[\"A\",null,\"C\"]");
|
||||
}
|
||||
|
||||
@Test public void failOnUnknown() throws Exception {
|
||||
JsonAdapter<String> alwaysSkip = new JsonAdapter<String>() {
|
||||
@Override public String fromJson(JsonReader reader) throws IOException {
|
||||
reader.skipValue();
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, String value) throws IOException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}.failOnUnknown();
|
||||
|
||||
JsonReader reader = factory.newReader("[\"a\"]");
|
||||
reader.beginArray();
|
||||
try {
|
||||
alwaysSkip.fromJson(reader);
|
||||
fail();
|
||||
} catch (JsonDataException expected) {
|
||||
assertThat(expected).hasMessage("Cannot skip unexpected STRING at $[0]");
|
||||
}
|
||||
assertThat(reader.nextString()).isEqualTo("a");
|
||||
reader.endArray();
|
||||
}
|
||||
|
||||
@Test public void indent() throws Exception {
|
||||
assumeTrue(factory.encodesToBytes());
|
||||
|
||||
JsonAdapter<List<String>> indent = new JsonAdapter<List<String>>() {
|
||||
@Override public List<String> fromJson(JsonReader reader) throws IOException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, List<String> value) throws IOException {
|
||||
writer.beginArray();
|
||||
for (String s : value) {
|
||||
writer.value(s);
|
||||
}
|
||||
writer.endArray();
|
||||
}
|
||||
}.indent("\t\t\t");
|
||||
|
||||
JsonWriter writer = factory.newWriter();
|
||||
indent.toJson(writer, Arrays.asList("a", "b", "c"));
|
||||
assertThat(factory.json()).isEqualTo(""
|
||||
+ "[\n"
|
||||
+ "\t\t\t\"a\",\n"
|
||||
+ "\t\t\t\"b\",\n"
|
||||
+ "\t\t\t\"c\"\n"
|
||||
+ "]");
|
||||
}
|
||||
|
||||
@Test public void serializeNulls() throws Exception {
|
||||
JsonAdapter<Map<String, String>> serializeNulls = new JsonAdapter<Map<String, String>>() {
|
||||
@Override public Map<String, String> fromJson(JsonReader reader) throws IOException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, Map<String, String> map) throws IOException {
|
||||
writer.beginObject();
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
writer.name(entry.getKey()).value(entry.getValue());
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
}.serializeNulls();
|
||||
|
||||
JsonWriter writer = factory.newWriter();
|
||||
serializeNulls.toJson(writer, Collections.<String, String>singletonMap("a", null));
|
||||
assertThat(factory.json()).isEqualTo("{\"a\":null}");
|
||||
}
|
||||
}
|
|
@ -20,14 +20,19 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import okio.Buffer;
|
||||
|
||||
abstract class JsonWriterFactory {
|
||||
abstract class JsonCodecFactory {
|
||||
private static final Moshi MOSHI = new Moshi.Builder().build();
|
||||
private static final JsonAdapter<Object> OBJECT_ADAPTER = MOSHI.adapter(Object.class);
|
||||
|
||||
static List<Object[]> factories() {
|
||||
final JsonWriterFactory bufferedSink = new JsonWriterFactory() {
|
||||
final JsonCodecFactory bufferedSink = new JsonCodecFactory() {
|
||||
Buffer buffer;
|
||||
|
||||
@Override public JsonReader newReader(String json) {
|
||||
Buffer buffer = new Buffer().writeUtf8(json);
|
||||
return JsonReader.of(buffer);
|
||||
}
|
||||
|
||||
@Override JsonWriter newWriter() {
|
||||
buffer = new Buffer();
|
||||
return new BufferedSinkJsonWriter(buffer);
|
||||
|
@ -39,18 +44,29 @@ abstract class JsonWriterFactory {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override boolean supportsMultipleTopLevelValuesInOneDocument() {
|
||||
@Override boolean encodesToBytes() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "BufferedSinkJsonWriter";
|
||||
return "Buffer";
|
||||
}
|
||||
};
|
||||
|
||||
final JsonWriterFactory object = new JsonWriterFactory() {
|
||||
final JsonCodecFactory object = new JsonCodecFactory() {
|
||||
ObjectJsonWriter writer;
|
||||
|
||||
@Override public JsonReader newReader(String json) throws IOException {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
Object object = moshi.adapter(Object.class).lenient().fromJson(json);
|
||||
return new ObjectJsonReader(object);
|
||||
}
|
||||
|
||||
// TODO(jwilson): fix precision checks and delete his method.
|
||||
@Override boolean implementsStrictPrecision() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override JsonWriter newWriter() {
|
||||
writer = new ObjectJsonWriter();
|
||||
return writer;
|
||||
|
@ -62,6 +78,7 @@ abstract class JsonWriterFactory {
|
|||
Buffer buffer = new Buffer();
|
||||
JsonWriter bufferedSinkWriter = JsonWriter.of(buffer);
|
||||
bufferedSinkWriter.setSerializeNulls(true);
|
||||
bufferedSinkWriter.setLenient(true);
|
||||
OBJECT_ADAPTER.toJson(bufferedSinkWriter, writer.root());
|
||||
return buffer.readUtf8();
|
||||
} catch (IOException e) {
|
||||
|
@ -75,7 +92,7 @@ abstract class JsonWriterFactory {
|
|||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "ObjectJsonWriter";
|
||||
return "Object";
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -84,10 +101,17 @@ abstract class JsonWriterFactory {
|
|||
new Object[] { object });
|
||||
}
|
||||
|
||||
abstract JsonReader newReader(String json) throws IOException;
|
||||
|
||||
abstract JsonWriter newWriter();
|
||||
|
||||
boolean implementsStrictPrecision() {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract String json();
|
||||
|
||||
boolean supportsMultipleTopLevelValuesInOneDocument() {
|
||||
boolean encodesToBytes() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import okio.Buffer;
|
||||
|
||||
abstract class JsonReaderFactory {
|
||||
static List<Object[]> factories() {
|
||||
JsonReaderFactory bufferedSource = new JsonReaderFactory() {
|
||||
@Override public JsonReader newReader(String json) {
|
||||
Buffer buffer = new Buffer().writeUtf8(json);
|
||||
return JsonReader.of(buffer);
|
||||
}
|
||||
|
||||
@Override boolean supportsMultipleTopLevelValuesInOneDocument() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "BufferedSourceJsonReader";
|
||||
}
|
||||
};
|
||||
|
||||
JsonReaderFactory jsonObject = new JsonReaderFactory() {
|
||||
@Override public JsonReader newReader(String json) throws IOException {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
Object object = moshi.adapter(Object.class).lenient().fromJson(json);
|
||||
return new ObjectJsonReader(object);
|
||||
}
|
||||
|
||||
// TODO(jwilson): fix precision checks and delete his method.
|
||||
@Override boolean implementsStrictPrecision() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "ObjectJsonReader";
|
||||
}
|
||||
};
|
||||
|
||||
return Arrays.asList(
|
||||
new Object[] { bufferedSource },
|
||||
new Object[] { jsonObject });
|
||||
}
|
||||
|
||||
abstract JsonReader newReader(String json) throws IOException;
|
||||
|
||||
boolean supportsMultipleTopLevelValuesInOneDocument() {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean implementsStrictPrecision() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -28,11 +28,11 @@ import static org.junit.Assume.assumeTrue;
|
|||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class JsonReaderPathTest {
|
||||
@Parameter public JsonReaderFactory factory;
|
||||
@Parameter public JsonCodecFactory factory;
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static List<Object[]> parameters() {
|
||||
return JsonReaderFactory.factories();
|
||||
return JsonCodecFactory.factories();
|
||||
}
|
||||
|
||||
@Test public void path() throws IOException {
|
||||
|
@ -185,7 +185,7 @@ public final class JsonReaderPathTest {
|
|||
}
|
||||
|
||||
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
|
||||
assumeTrue(factory.supportsMultipleTopLevelValuesInOneDocument());
|
||||
assumeTrue(factory.encodesToBytes());
|
||||
|
||||
JsonReader reader = factory.newReader("[][]");
|
||||
reader.setLenient(true);
|
||||
|
|
|
@ -28,7 +28,6 @@ import static com.squareup.moshi.JsonReader.Token.BEGIN_ARRAY;
|
|||
import static com.squareup.moshi.JsonReader.Token.BEGIN_OBJECT;
|
||||
import static com.squareup.moshi.JsonReader.Token.NAME;
|
||||
import static com.squareup.moshi.JsonReader.Token.STRING;
|
||||
import static com.squareup.moshi.TestUtil.newReader;
|
||||
import static com.squareup.moshi.TestUtil.repeat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -37,11 +36,11 @@ import static org.junit.Assume.assumeTrue;
|
|||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class JsonReaderTest {
|
||||
@Parameter public JsonReaderFactory factory;
|
||||
@Parameter public JsonCodecFactory factory;
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static List<Object[]> parameters() {
|
||||
return JsonReaderFactory.factories();
|
||||
return JsonCodecFactory.factories();
|
||||
}
|
||||
|
||||
JsonReader newReader(String json) throws IOException {
|
||||
|
|
|
@ -29,11 +29,11 @@ import static org.junit.Assume.assumeTrue;
|
|||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class JsonWriterPathTest {
|
||||
@Parameter public JsonWriterFactory factory;
|
||||
@Parameter public JsonCodecFactory factory;
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static List<Object[]> parameters() {
|
||||
return JsonWriterFactory.factories();
|
||||
return JsonCodecFactory.factories();
|
||||
}
|
||||
|
||||
@Test public void path() throws IOException {
|
||||
|
@ -202,7 +202,7 @@ public final class JsonWriterPathTest {
|
|||
}
|
||||
|
||||
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
|
||||
assumeTrue(factory.supportsMultipleTopLevelValuesInOneDocument());
|
||||
assumeTrue(factory.encodesToBytes());
|
||||
|
||||
JsonWriter writer = factory.newWriter();
|
||||
writer.setLenient(true);
|
||||
|
|
|
@ -31,11 +31,11 @@ import static org.junit.Assume.assumeTrue;
|
|||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class JsonWriterTest {
|
||||
@Parameter public JsonWriterFactory factory;
|
||||
@Parameter public JsonCodecFactory factory;
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static List<Object[]> parameters() {
|
||||
return JsonWriterFactory.factories();
|
||||
return JsonCodecFactory.factories();
|
||||
}
|
||||
|
||||
@Test public void nullsValuesNotSerializedByDefault() throws IOException {
|
||||
|
@ -469,7 +469,7 @@ public final class JsonWriterTest {
|
|||
}
|
||||
|
||||
@Test public void lenientWriterPermitsMultipleTopLevelValues() throws IOException {
|
||||
assumeTrue(factory.supportsMultipleTopLevelValuesInOneDocument());
|
||||
assumeTrue(factory.encodesToBytes());
|
||||
|
||||
JsonWriter writer = factory.newWriter();
|
||||
writer.setLenient(true);
|
||||
|
|
Loading…
Reference in a new issue