mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Update reader and writer to RFC 7159.
This commit is contained in:
@@ -23,7 +23,7 @@ import okio.BufferedSource;
|
|||||||
import okio.ByteString;
|
import okio.ByteString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
|
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>)
|
||||||
* encoded value as a stream of tokens. This stream includes both literal
|
* encoded value as a stream of tokens. This stream includes both literal
|
||||||
* values (strings, numbers, booleans, and nulls) as well as the begin and
|
* values (strings, numbers, booleans, and nulls) as well as the begin and
|
||||||
* end delimiters of objects and arrays. The tokens are traversed in
|
* end delimiters of objects and arrays. The tokens are traversed in
|
||||||
@@ -272,7 +272,7 @@ public class JsonReader implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* Configure this parser to be liberal in what it accepts. By default
|
* Configure this parser to be liberal in what it accepts. By default
|
||||||
* this parser is strict and only accepts JSON as specified by <a
|
* this parser is strict and only accepts JSON as specified by <a
|
||||||
* href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
|
* href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the
|
||||||
* parser to lenient causes it to ignore the following syntax errors:
|
* parser to lenient causes it to ignore the following syntax errors:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
@@ -568,9 +568,6 @@ public class JsonReader implements Closeable {
|
|||||||
buffer.readByte(); // Consume '\''.
|
buffer.readByte(); // Consume '\''.
|
||||||
return peeked = PEEKED_SINGLE_QUOTED;
|
return peeked = PEEKED_SINGLE_QUOTED;
|
||||||
case '"':
|
case '"':
|
||||||
if (stackSize == 1) {
|
|
||||||
checkLenient();
|
|
||||||
}
|
|
||||||
buffer.readByte(); // Consume '\"'.
|
buffer.readByte(); // Consume '\"'.
|
||||||
return peeked = PEEKED_DOUBLE_QUOTED;
|
return peeked = PEEKED_DOUBLE_QUOTED;
|
||||||
case '[':
|
case '[':
|
||||||
@@ -582,10 +579,6 @@ public class JsonReader implements Closeable {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stackSize == 1) {
|
|
||||||
checkLenient(); // Top-level value isn't an array or an object.
|
|
||||||
}
|
|
||||||
|
|
||||||
int result = peekKeyword();
|
int result = peekKeyword();
|
||||||
if (result != PEEKED_NONE) {
|
if (result != PEEKED_NONE) {
|
||||||
return result;
|
return result;
|
||||||
|
@@ -30,7 +30,7 @@ import static com.squareup.moshi.JsonScope.NONEMPTY_DOCUMENT;
|
|||||||
import static com.squareup.moshi.JsonScope.NONEMPTY_OBJECT;
|
import static com.squareup.moshi.JsonScope.NONEMPTY_OBJECT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
|
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>)
|
||||||
* encoded value to a stream, one token at a time. The stream includes both
|
* encoded value to a stream, one token at a time. The stream includes both
|
||||||
* literal values (strings, numbers, booleans and nulls) as well as the begin
|
* literal values (strings, numbers, booleans and nulls) as well as the begin
|
||||||
* and end delimiters of objects and arrays.
|
* and end delimiters of objects and arrays.
|
||||||
@@ -127,7 +127,7 @@ import static com.squareup.moshi.JsonScope.NONEMPTY_OBJECT;
|
|||||||
public class JsonWriter implements Closeable, Flushable {
|
public class JsonWriter implements Closeable, Flushable {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* From RFC 4627, "All Unicode characters may be placed within the
|
* From RFC 7159, "All Unicode characters may be placed within the
|
||||||
* quotation marks except for the characters that must be escaped:
|
* quotation marks except for the characters that must be escaped:
|
||||||
* quotation mark, reverse solidus, and the control characters
|
* quotation mark, reverse solidus, and the control characters
|
||||||
* (U+0000 through U+001F)."
|
* (U+0000 through U+001F)."
|
||||||
@@ -217,7 +217,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
/**
|
/**
|
||||||
* Configure this writer to relax its syntax rules. By default, this writer
|
* Configure this writer to relax its syntax rules. By default, this writer
|
||||||
* only emits well-formed JSON as specified by <a
|
* only emits well-formed JSON as specified by <a
|
||||||
* href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer
|
* href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer
|
||||||
* to lenient permits the following:
|
* to lenient permits the following:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Top-level values of any type. With strict writing, the top-level
|
* <li>Top-level values of any type. With strict writing, the top-level
|
||||||
@@ -299,7 +299,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
* bracket.
|
* bracket.
|
||||||
*/
|
*/
|
||||||
private JsonWriter open(int empty, String openBracket) throws IOException {
|
private JsonWriter open(int empty, String openBracket) throws IOException {
|
||||||
beforeValue(true);
|
beforeValue();
|
||||||
pathIndices[stackSize] = 0;
|
pathIndices[stackSize] = 0;
|
||||||
push(empty);
|
push(empty);
|
||||||
sink.writeUtf8(openBracket);
|
sink.writeUtf8(openBracket);
|
||||||
@@ -400,7 +400,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
return name(value);
|
return name(value);
|
||||||
}
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue();
|
||||||
string(value);
|
string(value);
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
return this;
|
return this;
|
||||||
@@ -420,7 +420,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
return this; // skip the name and the value
|
return this; // skip the name and the value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
beforeValue(false);
|
beforeValue();
|
||||||
sink.writeUtf8("null");
|
sink.writeUtf8("null");
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
return this;
|
return this;
|
||||||
@@ -433,7 +433,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
*/
|
*/
|
||||||
public JsonWriter value(boolean value) throws IOException {
|
public JsonWriter value(boolean value) throws IOException {
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue();
|
||||||
sink.writeUtf8(value ? "true" : "false");
|
sink.writeUtf8(value ? "true" : "false");
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
return this;
|
return this;
|
||||||
@@ -454,7 +454,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
return name(Double.toString(value));
|
return name(Double.toString(value));
|
||||||
}
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue();
|
||||||
sink.writeUtf8(Double.toString(value));
|
sink.writeUtf8(Double.toString(value));
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
return this;
|
return this;
|
||||||
@@ -470,7 +470,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
return name(Long.toString(value));
|
return name(Long.toString(value));
|
||||||
}
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue();
|
||||||
sink.writeUtf8(Long.toString(value));
|
sink.writeUtf8(Long.toString(value));
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
return this;
|
return this;
|
||||||
@@ -497,7 +497,7 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
return name(string);
|
return name(string);
|
||||||
}
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue();
|
||||||
sink.writeUtf8(string);
|
sink.writeUtf8(string);
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
return this;
|
return this;
|
||||||
@@ -591,12 +591,9 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
* Inserts any necessary separators and whitespace before a literal value,
|
* Inserts any necessary separators and whitespace before a literal value,
|
||||||
* inline array, or inline object. Also adjusts the stack to expect either a
|
* inline array, or inline object. Also adjusts the stack to expect either a
|
||||||
* closing bracket or another element.
|
* closing bracket or another element.
|
||||||
*
|
|
||||||
* @param root true if the value is a new array or object, the two values
|
|
||||||
* permitted as top-level elements.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("fallthrough")
|
@SuppressWarnings("fallthrough")
|
||||||
private void beforeValue(boolean root) throws IOException {
|
private void beforeValue() throws IOException {
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case NONEMPTY_DOCUMENT:
|
case NONEMPTY_DOCUMENT:
|
||||||
if (!lenient) {
|
if (!lenient) {
|
||||||
@@ -605,10 +602,6 @@ public class JsonWriter implements Closeable, Flushable {
|
|||||||
}
|
}
|
||||||
// fall-through
|
// fall-through
|
||||||
case EMPTY_DOCUMENT: // first in document
|
case EMPTY_DOCUMENT: // first in document
|
||||||
if (!lenient && !root) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"JSON must start with an array or an object.");
|
|
||||||
}
|
|
||||||
replaceTop(NONEMPTY_DOCUMENT);
|
replaceTop(NONEMPTY_DOCUMENT);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@@ -264,14 +264,6 @@ public final class JsonReaderTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void noTopLevelObject() {
|
|
||||||
try {
|
|
||||||
newReader("true").nextBoolean();
|
|
||||||
fail();
|
|
||||||
} catch (IOException expected) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test public void characterUnescaping() throws IOException {
|
@Test public void characterUnescaping() throws IOException {
|
||||||
String json = "[\"a\","
|
String json = "[\"a\","
|
||||||
+ "\"a\\\"\","
|
+ "\"a\\\"\","
|
||||||
@@ -1341,44 +1333,36 @@ public final class JsonReaderTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void strictTopLevelString() {
|
@Test public void topLevelValueTypes() throws IOException {
|
||||||
JsonReader reader = newReader("\"a\"");
|
JsonReader reader1 = newReader("true");
|
||||||
try {
|
assertThat(reader1.nextBoolean()).isTrue();
|
||||||
reader.nextString();
|
assertThat(reader1.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
fail();
|
|
||||||
} catch (IOException expected) {
|
JsonReader reader2 = newReader("false");
|
||||||
}
|
assertThat(reader2.nextBoolean()).isFalse();
|
||||||
|
assertThat(reader2.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
|
|
||||||
|
JsonReader reader3 = newReader("null");
|
||||||
|
assertThat(reader3.nextNull()).isNull();
|
||||||
|
assertThat(reader3.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
|
|
||||||
|
JsonReader reader4 = newReader("123");
|
||||||
|
assertThat(reader4.nextInt()).isEqualTo(123);
|
||||||
|
assertThat(reader4.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
|
|
||||||
|
JsonReader reader5 = newReader("123.4");
|
||||||
|
assertThat(reader5.nextDouble()).isEqualTo(123.4);
|
||||||
|
assertThat(reader5.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
|
|
||||||
|
JsonReader reader6 = newReader("\"a\"");
|
||||||
|
assertThat(reader6.nextString()).isEqualTo("a");
|
||||||
|
assertThat(reader6.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void lenientTopLevelString() throws IOException {
|
@Test public void topLevelValueTypeWithSkipValue() throws IOException {
|
||||||
JsonReader reader = newReader("\"a\"");
|
|
||||||
reader.setLenient(true);
|
|
||||||
assertThat(reader.nextString()).isEqualTo("a");
|
|
||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test public void strictTopLevelValueType() {
|
|
||||||
JsonReader reader = newReader("true");
|
JsonReader reader = newReader("true");
|
||||||
try {
|
|
||||||
reader.nextBoolean();
|
|
||||||
fail();
|
|
||||||
} catch (IOException expected) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test public void lenientTopLevelValueType() throws IOException {
|
|
||||||
JsonReader reader = newReader("true");
|
|
||||||
reader.setLenient(true);
|
|
||||||
assertThat(reader.nextBoolean()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test public void strictTopLevelValueTypeWithSkipValue() {
|
|
||||||
JsonReader reader = newReader("true");
|
|
||||||
try {
|
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
fail();
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
} catch (IOException expected) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @Ignore public void bomIgnoredAsFirstCharacterOfDocument() throws IOException {
|
@Test @Ignore public void bomIgnoredAsFirstCharacterOfDocument() throws IOException {
|
||||||
|
@@ -48,11 +48,41 @@ public final class JsonWriterTest {
|
|||||||
assertThat(buffer.readUtf8()).isEqualTo("{\"a\":null}");
|
assertThat(buffer.readUtf8()).isEqualTo("{\"a\":null}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void wrongTopLevelType() throws IOException {
|
@Test public void topLevelValueTypes() throws IOException {
|
||||||
Buffer buffer = new Buffer();
|
Buffer buffer = new Buffer();
|
||||||
JsonWriter jsonWriter = JsonWriter.of(buffer);
|
|
||||||
|
JsonWriter writer1 = JsonWriter.of(buffer);
|
||||||
|
writer1.value(true);
|
||||||
|
writer1.close();
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("true");
|
||||||
|
|
||||||
|
JsonWriter writer2 = JsonWriter.of(buffer);
|
||||||
|
writer2.nullValue();
|
||||||
|
writer2.close();
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("null");
|
||||||
|
|
||||||
|
JsonWriter writer3 = JsonWriter.of(buffer);
|
||||||
|
writer3.value(123);
|
||||||
|
writer3.close();
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("123");
|
||||||
|
|
||||||
|
JsonWriter writer4 = JsonWriter.of(buffer);
|
||||||
|
writer4.value(123.4);
|
||||||
|
writer4.close();
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("123.4");
|
||||||
|
|
||||||
|
JsonWriter writer5 = JsonWriter.of(buffer);
|
||||||
|
writer5.value("a");
|
||||||
|
writer5.close();
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("\"a\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void invalidTopLevelTypes() throws IOException {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = JsonWriter.of(buffer);
|
||||||
|
writer.name("hello");
|
||||||
try {
|
try {
|
||||||
jsonWriter.value("a");
|
writer.value("world");
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalStateException expected) {
|
} catch (IllegalStateException expected) {
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user