Merge pull request #121 from square/jw/rfc7159

Update reader and writer to RFC 7159.
This commit is contained in:
Jesse Wilson
2016-01-18 10:47:36 -05:00
4 changed files with 73 additions and 73 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,46 +1333,38 @@ 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\""); JsonReader reader = newReader("true");
reader.setLenient(true); reader.skipValue();
assertThat(reader.nextString()).isEqualTo("a");
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
} }
@Test public void strictTopLevelValueType() {
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();
fail();
} catch (IOException expected) {
}
}
@Test @Ignore public void bomIgnoredAsFirstCharacterOfDocument() throws IOException { @Test @Ignore public void bomIgnoredAsFirstCharacterOfDocument() throws IOException {
JsonReader reader = newReader("\ufeff[]"); JsonReader reader = newReader("\ufeff[]");
reader.beginArray(); reader.beginArray();

View File

@@ -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) {
} }