+
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
index 80255452ca6ec..8f44895bab01e 100644
--- a/core/java/android/util/JsonReader.java
+++ b/core/java/android/util/JsonReader.java
@@ -16,6 +16,7 @@
package android.util;
+import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.Closeable;
@@ -248,6 +249,13 @@ public final class JsonReader implements Closeable {
this.lenient = lenient;
}
+ /**
+ * Returns true if this parser is liberal in what it accepts.
+ */
+ public boolean isLenient() {
+ return lenient;
+ }
+
/**
* Consumes the next token from the JSON stream and asserts that it is the
* beginning of a new array.
@@ -311,7 +319,7 @@ public final class JsonReader implements Closeable {
case EMPTY_DOCUMENT:
replaceTop(JsonScope.NONEMPTY_DOCUMENT);
JsonToken firstToken = nextValue();
- if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+ if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
throw new IOException(
"Expected JSON document to start with '[' or '{' but was " + token);
}
@@ -327,7 +335,15 @@ public final class JsonReader implements Closeable {
case NONEMPTY_OBJECT:
return nextInObject(false);
case NONEMPTY_DOCUMENT:
- return token = JsonToken.END_DOCUMENT;
+ try {
+ JsonToken token = nextValue();
+ if (lenient) {
+ return token;
+ }
+ throw syntaxError("Expected EOF");
+ } catch (EOFException e) {
+ return token = JsonToken.END_DOCUMENT; // TODO: avoid throwing here?
+ }
case CLOSED:
throw new IllegalStateException("JsonReader is closed");
default:
@@ -758,7 +774,7 @@ public final class JsonReader implements Closeable {
}
}
- throw syntaxError("End of input");
+ throw new EOFException("End of input");
}
private void checkLenient() throws IOException {
@@ -1030,8 +1046,6 @@ public final class JsonReader implements Closeable {
* form -12.34e+56. Fractional and exponential parts are optional. Leading
* zeroes are not allowed in the value or exponential part, but are allowed
* in the fraction.
- *
- * This has a side effect of setting isInteger.
*/
private JsonToken decodeNumber(char[] chars, int offset, int length) {
int i = offset;
@@ -1085,8 +1099,8 @@ public final class JsonReader implements Closeable {
* Throws a new IO exception with the given message and a context snippet
* with this reader's content.
*/
- public IOException syntaxError(String message) throws IOException {
- throw new JsonSyntaxException(message + " near " + getSnippet());
+ private IOException syntaxError(String message) throws IOException {
+ throw new MalformedJsonException(message + " near " + getSnippet());
}
private CharSequence getSnippet() {
@@ -1097,10 +1111,4 @@ public final class JsonReader implements Closeable {
snippet.append(buffer, pos, afterPos);
return snippet;
}
-
- private static class JsonSyntaxException extends IOException {
- private JsonSyntaxException(String s) {
- super(s);
- }
- }
}
diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java
index 89cad79eb8589..47e84c51dd253 100644
--- a/core/java/android/util/JsonWriter.java
+++ b/core/java/android/util/JsonWriter.java
@@ -138,6 +138,8 @@ public final class JsonWriter implements Closeable {
*/
private String separator = ":";
+ private boolean lenient;
+
/**
* Creates a new instance that writes a JSON-encoded stream to {@code out}.
* For best performance, ensure {@link Writer} is buffered; wrapping in
@@ -168,6 +170,29 @@ public final class JsonWriter implements Closeable {
}
}
+ /**
+ * Configure this writer to relax its syntax rules. By default, this writer
+ * only emits well-formed JSON as specified by RFC 4627. Setting the writer
+ * to lenient permits the following:
+ *
+ * - Top-level values of any type. With strict writing, the top-level
+ * value must be an object or an array.
+ *
- Numbers may be {@link Double#isNaN() NaNs} or {@link
+ * Double#isInfinite() infinities}.
+ *
+ */
+ public void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ }
+
+ /**
+ * Returns true if this writer has relaxed syntax rules.
+ */
+ public boolean isLenient() {
+ return lenient;
+ }
+
/**
* Begins encoding a new array. Each call to this method must be paired with
* a call to {@link #endArray}.
@@ -306,11 +331,11 @@ public final class JsonWriter implements Closeable {
* Encodes {@code value}.
*
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
- * {@link Double#isInfinite() infinities}.
+ * {@link Double#isInfinite() infinities} unless this writer is lenient.
* @return this writer.
*/
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);
}
beforeValue(false);
@@ -329,6 +354,28 @@ public final class JsonWriter implements Closeable {
return this;
}
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities} unless this writer is lenient.
+ * @return this writer.
+ */
+ public JsonWriter value(Number value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+
+ String string = value.toString();
+ if (!lenient &&
+ (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
+ throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+ }
+ beforeValue(false);
+ out.append(string);
+ return this;
+ }
+
/**
* Ensures all buffered data is written to the underlying {@link Writer}
* and flushes that writer.
@@ -364,7 +411,6 @@ public final class JsonWriter implements Closeable {
switch (c) {
case '"':
case '\\':
- case '/':
out.write('\\');
out.write(c);
break;
@@ -439,7 +485,7 @@ public final class JsonWriter implements Closeable {
private void beforeValue(boolean root) throws IOException {
switch (peek()) {
case EMPTY_DOCUMENT: // first in document
- if (!root) {
+ if (!lenient && !root) {
throw new IllegalStateException(
"JSON must start with an array or an object.");
}
diff --git a/core/java/android/util/MalformedJsonException.java b/core/java/android/util/MalformedJsonException.java
new file mode 100644
index 0000000000000..63c19ff86cd4e
--- /dev/null
+++ b/core/java/android/util/MalformedJsonException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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 android.util;
+
+import java.io.IOException;
+
+/**
+ * Thrown when a reader encounters malformed JSON. Some syntax errors can be
+ * ignored by calling {@link JsonReader#setLenient(boolean)}.
+ */
+public final class MalformedJsonException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ public MalformedJsonException(String message) {
+ super(message);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java
index 216a772dc4121..b5c2c2743f333 100644
--- a/core/tests/coretests/src/android/util/JsonReaderTest.java
+++ b/core/tests/coretests/src/android/util/JsonReaderTest.java
@@ -16,11 +16,10 @@
package android.util;
-import java.util.Arrays;
-import junit.framework.TestCase;
-
import java.io.IOException;
import java.io.StringReader;
+import java.util.Arrays;
+import junit.framework.TestCase;
public final class JsonReaderTest extends TestCase {
@@ -677,7 +676,7 @@ public final class JsonReaderTest extends TestCase {
try {
reader.nextString();
fail();
- } catch (IOException expected) {
+ } catch (MalformedJsonException expected) {
}
}
@@ -811,4 +810,50 @@ public final class JsonReaderTest extends TestCase {
reader.nextNull();
reader.endArray();
}
+
+ public void testStrictMultipleTopLevelValues() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[] []"));
+ reader.beginArray();
+ reader.endArray();
+ try {
+ reader.peek();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientMultipleTopLevelValues() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[] true {}"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.endArray();
+ assertEquals(true, reader.nextBoolean());
+ reader.beginObject();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testStrictTopLevelValueType() {
+ JsonReader reader = new JsonReader(new StringReader("true"));
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientTopLevelValueType() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("true"));
+ reader.setLenient(true);
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictNonExecutePrefix() {
+ JsonReader reader = new JsonReader(new StringReader(")]}'\n []"));
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java
index fa840239d7b80..b29e2fdde08ca 100644
--- a/core/tests/coretests/src/android/util/JsonWriterTest.java
+++ b/core/tests/coretests/src/android/util/JsonWriterTest.java
@@ -16,10 +16,11 @@
package android.util;
-import junit.framework.TestCase;
-
import java.io.IOException;
import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import junit.framework.TestCase;
public final class JsonWriterTest extends TestCase {
@@ -119,7 +120,7 @@ public final class JsonWriterTest extends TestCase {
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject();
jsonWriter.name("a");
- jsonWriter.value(null);
+ jsonWriter.value((String) null);
jsonWriter.endObject();
assertEquals("{\"a\":null}", stringWriter.toString());
}
@@ -145,6 +146,27 @@ public final class JsonWriterTest extends TestCase {
}
}
+ public void testNonFiniteBoxedDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.value(new Double(Double.NaN));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(new Double(Double.NEGATIVE_INFINITY));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(new Double(Double.POSITIVE_INFINITY));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
public void testDoubles() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
@@ -189,6 +211,22 @@ public final class JsonWriterTest extends TestCase {
+ "9223372036854775807]", stringWriter.toString());
}
+ public void testNumbers() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(new BigInteger("0"));
+ jsonWriter.value(new BigInteger("9223372036854775808"));
+ jsonWriter.value(new BigInteger("-9223372036854775809"));
+ jsonWriter.value(new BigDecimal("3.141592653589793238462643383"));
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[0,"
+ + "9223372036854775808,"
+ + "-9223372036854775809,"
+ + "3.141592653589793238462643383]", stringWriter.toString());
+ }
+
public void testBooleans() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);