Update reader and writer for RFC 7159.

This commit is contained in:
Jake Wharton 2016-01-18 15:07:33 -05:00
parent 2ab776b5f5
commit c8627c8ab8
4 changed files with 77 additions and 73 deletions

View File

@ -24,7 +24,7 @@ import java.io.IOException;
import java.io.Reader; import java.io.Reader;
/** /**
* 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
@ -571,9 +571,6 @@ public class JsonReader implements Closeable {
checkLenient(); checkLenient();
return peeked = PEEKED_SINGLE_QUOTED; return peeked = PEEKED_SINGLE_QUOTED;
case '"': case '"':
if (stackSize == 1) {
checkLenient();
}
return peeked = PEEKED_DOUBLE_QUOTED; return peeked = PEEKED_DOUBLE_QUOTED;
case '[': case '[':
return peeked = PEEKED_BEGIN_ARRAY; return peeked = PEEKED_BEGIN_ARRAY;
@ -583,10 +580,6 @@ public class JsonReader implements Closeable {
pos--; // Don't consume the first character in a literal value. pos--; // Don't consume the first character in a literal value.
} }
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.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT;
import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT; import static com.google.gson.stream.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.
@ -130,7 +130,7 @@ import static com.google.gson.stream.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)."
@ -222,7 +222,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
@ -322,7 +322,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();
push(empty); push(empty);
out.write(openBracket); out.write(openBracket);
return this; return this;
@ -415,7 +415,7 @@ public class JsonWriter implements Closeable, Flushable {
return nullValue(); return nullValue();
} }
writeDeferredName(); writeDeferredName();
beforeValue(false); beforeValue();
string(value); string(value);
return this; return this;
} }
@ -432,7 +432,7 @@ public class JsonWriter implements Closeable, Flushable {
return nullValue(); return nullValue();
} }
writeDeferredName(); writeDeferredName();
beforeValue(false); beforeValue();
out.append(value); out.append(value);
return this; return this;
} }
@ -451,7 +451,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();
out.write("null"); out.write("null");
return this; return this;
} }
@ -463,7 +463,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();
out.write(value ? "true" : "false"); out.write(value ? "true" : "false");
return this; return this;
} }
@ -480,7 +480,7 @@ public class JsonWriter implements Closeable, Flushable {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value); throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
} }
writeDeferredName(); writeDeferredName();
beforeValue(false); beforeValue();
out.append(Double.toString(value)); out.append(Double.toString(value));
return this; return this;
} }
@ -492,7 +492,7 @@ public class JsonWriter implements Closeable, Flushable {
*/ */
public JsonWriter value(long value) throws IOException { public JsonWriter value(long value) throws IOException {
writeDeferredName(); writeDeferredName();
beforeValue(false); beforeValue();
out.write(Long.toString(value)); out.write(Long.toString(value));
return this; return this;
} }
@ -515,7 +515,7 @@ public class JsonWriter implements Closeable, Flushable {
&& (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value); throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
} }
beforeValue(false); beforeValue();
out.append(string); out.append(string);
return this; return this;
} }
@ -608,12 +608,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) {
@ -622,10 +619,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

@ -195,14 +195,6 @@ public final class JsonReaderTest extends TestCase {
} }
} }
public void testNoTopLevelObject() {
try {
new JsonReader(reader("true")).nextBoolean();
fail();
} catch (IOException expected) {
}
}
public void testCharacterUnescaping() throws IOException { public void testCharacterUnescaping() throws IOException {
String json = "[\"a\"," String json = "[\"a\","
+ "\"a\\\"\"," + "\"a\\\"\","
@ -1227,46 +1219,39 @@ public final class JsonReaderTest extends TestCase {
} }
} }
public void testStrictTopLevelString() { public void testTopLevelValueTypes() throws IOException {
JsonReader reader = new JsonReader(reader("\"a\"")); JsonReader reader1 = new JsonReader(reader("true"));
try { assertTrue(reader1.nextBoolean());
reader.nextString(); assertEquals(JsonToken.END_DOCUMENT, reader1.peek());
fail();
} catch (IOException expected) { JsonReader reader2 = new JsonReader(reader("false"));
} assertFalse(reader2.nextBoolean());
assertEquals(JsonToken.END_DOCUMENT, reader2.peek());
JsonReader reader3 = new JsonReader(reader("null"));
assertEquals(JsonToken.NULL, reader3.peek());
reader3.nextNull();
assertEquals(JsonToken.END_DOCUMENT, reader3.peek());
JsonReader reader4 = new JsonReader(reader("123"));
assertEquals(123, reader4.nextInt());
assertEquals(JsonToken.END_DOCUMENT, reader4.peek());
JsonReader reader5 = new JsonReader(reader("123.4"));
assertEquals(123.4, reader5.nextDouble());
assertEquals(JsonToken.END_DOCUMENT, reader5.peek());
JsonReader reader6 = new JsonReader(reader("\"a\""));
assertEquals("a", reader6.nextString());
assertEquals(JsonToken.END_DOCUMENT, reader6.peek());
} }
public void testLenientTopLevelString() throws IOException { public void testTopLevelValueTypeWithSkipValue() throws IOException {
JsonReader reader = new JsonReader(reader("\"a\"")); JsonReader reader = new JsonReader(reader("true"));
reader.setLenient(true); reader.skipValue();
assertEquals("a", reader.nextString());
assertEquals(JsonToken.END_DOCUMENT, reader.peek()); assertEquals(JsonToken.END_DOCUMENT, reader.peek());
} }
public void testStrictTopLevelValueType() {
JsonReader reader = new JsonReader(reader("true"));
try {
reader.nextBoolean();
fail();
} catch (IOException expected) {
}
}
public void testLenientTopLevelValueType() throws IOException {
JsonReader reader = new JsonReader(reader("true"));
reader.setLenient(true);
assertEquals(true, reader.nextBoolean());
}
public void testStrictTopLevelValueTypeWithSkipValue() {
JsonReader reader = new JsonReader(reader("true"));
try {
reader.skipValue();
fail();
} catch (IOException expected) {
}
}
public void testStrictNonExecutePrefix() { public void testStrictNonExecutePrefix() {
JsonReader reader = new JsonReader(reader(")]}'\n []")); JsonReader reader = new JsonReader(reader(")]}'\n []"));
try { try {
@ -1524,7 +1509,7 @@ public final class JsonReaderTest extends TestCase {
} catch (MalformedJsonException expected) { } catch (MalformedJsonException expected) {
} }
} }
public void testVeryLongQuotedString() throws IOException { public void testVeryLongQuotedString() throws IOException {
char[] stringChars = new char[1024 * 16]; char[] stringChars = new char[1024 * 16];
Arrays.fill(stringChars, 'x'); Arrays.fill(stringChars, 'x');

View File

@ -25,11 +25,44 @@ import junit.framework.TestCase;
@SuppressWarnings("resource") @SuppressWarnings("resource")
public final class JsonWriterTest extends TestCase { public final class JsonWriterTest extends TestCase {
public void testWrongTopLevelType() throws IOException { public void testTopLevelValueTypes() throws IOException {
StringWriter string1 = new StringWriter();
JsonWriter writer1 = new JsonWriter(string1);
writer1.value(true);
writer1.close();
assertEquals("true", string1.toString());
StringWriter string2 = new StringWriter();
JsonWriter writer2 = new JsonWriter(string2);
writer2.nullValue();
writer2.close();
assertEquals("null", string2.toString());
StringWriter string3 = new StringWriter();
JsonWriter writer3 = new JsonWriter(string3);
writer3.value(123);
writer3.close();
assertEquals("123", string3.toString());
StringWriter string4 = new StringWriter();
JsonWriter writer4 = new JsonWriter(string4);
writer4.value(123.4);
writer4.close();
assertEquals("123.4", string4.toString());
StringWriter string5 = new StringWriter();
JsonWriter writert = new JsonWriter(string5);
writert.value("a");
writert.close();
assertEquals("\"a\"", string5.toString());
}
public void testInvalidTopLevelTypes() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.name("hello");
try { try {
jsonWriter.value("a"); jsonWriter.value("world");
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
} }