Merge pull request #773 from google/jw/rfc7159
Update reader and writer for RFC 7159.
This commit is contained in:
commit
af68d70cd5
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user