Loading api/current.xml +19 −4 Original line number Diff line number Diff line Loading @@ -180354,6 +180354,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> <method name="setLenient" return="void" abstract="false" native="false" synchronized="false" static="false" final="false" deprecated="not deprecated" visibility="public" > <parameter name="lenient" type="boolean"> </parameter> </method> <method name="skipValue" return="void" abstract="false" Loading Loading @@ -180424,6 +180437,8 @@ deprecated="not deprecated" visibility="public" > <implements name="java.io.Closeable"> </implements> <constructor name="JsonWriter" type="android.util.JsonWriter" static="false" Loading Loading @@ -180540,7 +180555,7 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> <method name="setIndentSpaces" <method name="setIndent" return="void" abstract="false" native="false" Loading @@ -180550,7 +180565,7 @@ deprecated="not deprecated" visibility="public" > <parameter name="indent" type="int"> <parameter name="indent" type="java.lang.String"> </parameter> </method> <method name="value" Loading Loading @@ -272628,7 +272643,7 @@ deprecated="not deprecated" visibility="public" > <parameter name="loop" type="boolean"> <parameter name="disable" type="boolean"> </parameter> <exception name="SocketException" type="java.net.SocketException"> </exception> Loading Loading @@ -274187,7 +274202,7 @@ deprecated="not deprecated" visibility="public" > <parameter name="value" type="boolean"> <parameter name="keepAlive" type="boolean"> </parameter> <exception name="SocketException" type="java.net.SocketException"> </exception> core/java/android/util/JsonReader.java +103 −61 Original line number Diff line number Diff line Loading @@ -172,6 +172,9 @@ public final class JsonReader implements Closeable { /** The input JSON. */ private final Reader in; /** True to accept non-spec compliant JSON */ private boolean lenient = false; /** * Use a manual buffer to easily read and unread upcoming characters, and * also so we can create strings without an intermediate StringBuilder. Loading Loading @@ -207,9 +210,6 @@ public final class JsonReader implements Closeable { /** The text of the next literal value. */ private String value; // TODO: make this parser strict and offer an optional lenient mode? // TODO: document how this reader is non-strict /** * Creates a new instance that reads a JSON-encoded stream from {@code in}. */ Loading @@ -220,6 +220,31 @@ public final class JsonReader implements Closeable { this.in = in; } /** * Configure this parser to be be liberal in what it accepts. By default, * 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 * parser to lenient causes it to ignore the following syntax errors: * * <ul> * <li>End of line comments starting with {@code //} or {@code #} and * ending with a newline character. * <li>C-style comments starting with {@code /*} and ending with * {@code *}{@code /}. Such comments may not be nested. * <li>Names that are unquoted or {@code 'single quoted'}. * <li>Strings that are unquoted or {@code 'single quoted'}. * <li>Array elements separated by {@code ;} instead of {@code ,}. * <li>Unnecessary array separators. These are interpreted as if null * was the omitted value. * <li>Names and values separated by {@code =} or {@code =>} instead of * {@code :}. * <li>Name/value pairs separated by {@code ;} instead of {@code ,}. * </ul> */ public void setLenient(boolean lenient) { this.lenient = lenient; } /** * Consumes the next token from the JSON stream and asserts that it is the * beginning of a new array. Loading Loading @@ -253,11 +278,12 @@ public final class JsonReader implements Closeable { } /** * Consumes {@code token}. * Consumes {@code expected}. */ private void expect(JsonToken token) throws IOException { if (quickPeek() != token) { throw new IllegalStateException("Expected " + token + " but was " + peek()); private void expect(JsonToken expected) throws IOException { quickPeek(); if (token != expected) { throw new IllegalStateException("Expected " + expected + " but was " + peek()); } advance(); } Loading @@ -266,8 +292,8 @@ public final class JsonReader implements Closeable { * Returns true if the current array or object has another element. */ public boolean hasNext() throws IOException { JsonToken peek = quickPeek(); return peek != JsonToken.END_OBJECT && peek != JsonToken.END_ARRAY; quickPeek(); return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; } /** Loading @@ -285,11 +311,8 @@ public final class JsonReader implements Closeable { /** * Ensures that a token is ready. After this call either {@code token} or * {@code value} will be non-null. * * @return the type of the next token, of {@code null} if it is unknown. For * a definitive result, use {@link #peek()} which decodes the token * type. * {@code value} will be non-null. To ensure {@code token} has a definitive * value, use {@link #peek()} */ private JsonToken quickPeek() throws IOException { if (hasToken) { Loading Loading @@ -347,7 +370,8 @@ public final class JsonReader implements Closeable { * name. */ public String nextName() throws IOException { if (quickPeek() != JsonToken.NAME) { quickPeek(); if (token != JsonToken.NAME) { throw new IllegalStateException("Expected a name but was " + peek()); } String result = name; Loading @@ -364,8 +388,8 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public String nextString() throws IOException { JsonToken peek = peek(); if (value == null || (peek != JsonToken.STRING && peek != JsonToken.NUMBER)) { peek(); if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) { throw new IllegalStateException("Expected a string but was " + peek()); } Loading @@ -382,8 +406,8 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public boolean nextBoolean() throws IOException { JsonToken peek = quickPeek(); if (value == null || peek == JsonToken.STRING) { quickPeek(); if (value == null || token == JsonToken.STRING) { throw new IllegalStateException("Expected a boolean but was " + peek()); } Loading @@ -408,8 +432,8 @@ public final class JsonReader implements Closeable { * reader is closed. */ public void nextNull() throws IOException { JsonToken peek = quickPeek(); if (value == null || peek == JsonToken.STRING) { quickPeek(); if (value == null || token == JsonToken.STRING) { throw new IllegalStateException("Expected null but was " + peek()); } Loading Loading @@ -570,37 +594,44 @@ public final class JsonReader implements Closeable { private JsonToken nextInArray(boolean firstElement) throws IOException { if (firstElement) { replaceTop(JsonScope.NONEMPTY_ARRAY); } else { /* Look for a comma before each element after the first element. */ switch (nextNonWhitespace()) { case ']': pop(); hasToken = true; return token = JsonToken.END_ARRAY; case ',': case ';': /* a separator without a value first means "null". */ // TODO: forbid this in strict mode hasToken = true; return token = JsonToken.NULL; checkLenient(); // fall-through case ',': break; default: replaceTop(JsonScope.NONEMPTY_ARRAY); pos--; throw syntaxError("Unterminated array"); } } else { } switch (nextNonWhitespace()) { case ']': if (firstElement) { pop(); hasToken = true; return token = JsonToken.END_ARRAY; case ',': } // fall-through to handle ",]" case ';': break; case ',': /* In lenient mode, a 0-length literal means 'null' */ checkLenient(); pos--; hasToken = true; value = "null"; return token = JsonToken.NULL; default: throw syntaxError("Unterminated array"); } } pos--; return nextValue(); } } private JsonToken nextInObject(boolean firstElement) throws IOException { /* Loading Loading @@ -636,10 +667,12 @@ public final class JsonReader implements Closeable { int quote = nextNonWhitespace(); switch (quote) { case '\'': checkLenient(); // fall-through case '"': name = nextString((char) quote); break; default: checkLenient(); pos--; name = nextLiteral(); if (name.isEmpty()) { Loading @@ -653,20 +686,22 @@ public final class JsonReader implements Closeable { } private JsonToken objectValue() throws IOException { // TODO: accept only ":" in strict mode /* * Read the name/value separator. Usually a colon ':', an equals sign * '=', or an arrow "=>". The last two are bogus but we include them * because that's what org.json does. * Read the name/value separator. Usually a colon ':'. In lenient mode * we also accept an equals sign '=', or an arrow "=>". */ int separator = nextNonWhitespace(); if (separator != ':' && separator != '=') { throw syntaxError("Expected ':'"); } if (separator == '=' && (pos < limit || fillBuffer(1)) && buffer[pos] == '>') { switch (nextNonWhitespace()) { case ':': break; case '=': checkLenient(); if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { pos++; } break; default: throw syntaxError("Expected ':'"); } replaceTop(JsonScope.NONEMPTY_OBJECT); return nextValue(); Loading @@ -686,6 +721,7 @@ public final class JsonReader implements Closeable { return token = JsonToken.BEGIN_ARRAY; case '\'': checkLenient(); // fall-through case '"': value = nextString((char) c); hasToken = true; Loading Loading @@ -722,8 +758,6 @@ public final class JsonReader implements Closeable { } private int nextNonWhitespace() throws IOException { // TODO: no comments in strict mode while (pos < limit || fillBuffer(1)) { int c = buffer[pos++]; switch (c) { Loading @@ -738,6 +772,7 @@ public final class JsonReader implements Closeable { return c; } checkLenient(); char peek = buffer[pos]; switch (peek) { case '*': Loading Loading @@ -765,6 +800,7 @@ public final class JsonReader implements Closeable { * specify this behaviour, but it's required to parse * existing documents. See http://b/2571423. */ checkLenient(); skipToEndOfLine(); continue; Loading @@ -776,6 +812,12 @@ public final class JsonReader implements Closeable { throw syntaxError("End of input"); } private void checkLenient() throws IOException { if (!lenient) { throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); } } /** * Advances the position until after the next newline character. If the line * is terminated by "\r\n", the '\n' must be consumed as whitespace by the Loading Loading @@ -853,9 +895,6 @@ public final class JsonReader implements Closeable { * does not consume the delimiter character. */ private String nextLiteral() throws IOException { // TODO: use a much smaller set of permitted literal characters in strict mode; // these characters are derived from org.json's lenient mode StringBuilder builder = null; do { /* the index of the first character not yet appended to the builder. */ Loading @@ -863,17 +902,19 @@ public final class JsonReader implements Closeable { while (pos < limit) { int c = buffer[pos++]; switch (c) { case '/': case '\\': case ';': case '#': case '=': checkLenient(); // fall-through case '{': case '}': case '[': case ']': case '/': case '\\': case ':': case '=': case ',': case ';': case '#': case ' ': case '\t': case '\f': Loading Loading @@ -965,7 +1006,7 @@ public final class JsonReader implements Closeable { /** * Assigns {@code nextToken} based on the value of {@code nextValue}. */ private void decodeLiteral() { private void decodeLiteral() throws IOException { if (value.equalsIgnoreCase("null")) { token = JsonToken.NULL; } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { Loading @@ -975,7 +1016,8 @@ public final class JsonReader implements Closeable { Double.parseDouble(value); // this work could potentially be cached token = JsonToken.NUMBER; } catch (NumberFormatException ignored) { /* an unquoted string. This document is not well-formed! */ // this must be an unquoted string checkLenient(); token = JsonToken.STRING; } } Loading core/java/android/util/JsonWriter.java +13 −16 Original line number Diff line number Diff line Loading @@ -16,10 +16,10 @@ package android.util; import java.io.Closeable; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** Loading Loading @@ -117,7 +117,7 @@ import java.util.List; * Instances of this class are not thread safe. Calls that would result in a * malformed JSON string will fail with an {@link IllegalStateException}. */ public final class JsonWriter { public final class JsonWriter implements Closeable { /** The output data, containing at most one top-level array or object. */ private final Writer out; Loading Loading @@ -151,22 +151,19 @@ public final class JsonWriter { } /** * Sets the number of spaces to indent each line in the encoded document. * If {@code indent == 0} the encoded document will be compact. If {@code * indent > 0}, the encoded document will be more human-readable. * Sets the indentation string to be repeated for each level of indentation * in the encoded document. If {@code indent.isEmpty()} the encoded document * will be compact. Otherwise the encoded document will be more * human-readable. * * @param indent a string containing only whitespace. */ public void setIndentSpaces(int indent) { if (indent < 0) { throw new IllegalArgumentException("indent < 0"); } if (indent > 0) { char[] indentChars = new char[indent]; Arrays.fill(indentChars, ' '); this.indent = new String(indentChars); public void setIndent(String indent) { if (indent.isEmpty()) { this.indent = null; this.separator = ":"; } else { this.indent = null; this.indent = indent; this.separator = ": "; } } Loading core/tests/coretests/src/android/util/JsonReaderTest.java +268 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,14 @@ public final class JsonReaderTest extends TestCase { assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testReadEmptyArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[]")); reader.beginArray(); assertFalse(reader.hasNext()); reader.endArray(); assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testReadObject() throws IOException { JsonReader reader = new JsonReader(new StringReader( "{\"a\": \"android\", \"b\": \"banana\"}")); Loading @@ -44,6 +52,14 @@ public final class JsonReaderTest extends TestCase { assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testReadEmptyObject() throws IOException { JsonReader reader = new JsonReader(new StringReader("{}")); reader.beginObject(); assertFalse(reader.hasNext()); reader.endObject(); assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testSkipObject() throws IOException { JsonReader reader = new JsonReader(new StringReader( "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}")); Loading Loading @@ -412,4 +428,256 @@ public final class JsonReaderTest extends TestCase { } catch (IllegalStateException expected) { } } public void testStrictNameValueSeparator() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}")); reader.beginObject(); assertEquals("a", reader.nextName()); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("{\"a\"=>true}")); reader.beginObject(); assertEquals("a", reader.nextName()); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } } public void testLenientNameValueSeparator() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); assertEquals(true, reader.nextBoolean()); reader = new JsonReader(new StringReader("{\"a\"=>true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); assertEquals(true, reader.nextBoolean()); } public void testStrictComments() throws IOException { JsonReader reader = new JsonReader(new StringReader("[// comment \n true]")); reader.beginArray(); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[# comment \n true]")); reader.beginArray(); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[/* comment */ true]")); reader.beginArray(); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } } public void testLenientComments() throws IOException { JsonReader reader = new JsonReader(new StringReader("[// comment \n true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader = new JsonReader(new StringReader("[# comment \n true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader = new JsonReader(new StringReader("[/* comment */ true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); } public void testStrictUnquotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{a:true}")); reader.beginObject(); try { reader.nextName(); fail(); } catch (IOException expected) { } } public void testLenientUnquotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{a:true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); } public void testStrictSingleQuotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{'a':true}")); reader.beginObject(); try { reader.nextName(); fail(); } catch (IOException expected) { } } public void testLenientSingleQuotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{'a':true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); } public void testStrictUnquotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("[a]")); reader.beginArray(); try { reader.nextString(); fail(); } catch (IOException expected) { } } public void testLenientUnquotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("[a]")); reader.setLenient(true); reader.beginArray(); assertEquals("a", reader.nextString()); } public void testStrictSingleQuotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("['a']")); reader.beginArray(); try { reader.nextString(); fail(); } catch (IOException expected) { } } public void testLenientSingleQuotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("['a']")); reader.setLenient(true); reader.beginArray(); assertEquals("a", reader.nextString()); } public void testStrictSemicolonDelimitedArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true;true]")); reader.beginArray(); try { reader.nextBoolean(); reader.nextBoolean(); fail(); } catch (IOException expected) { } } public void testLenientSemicolonDelimitedArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true;true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); assertEquals(true, reader.nextBoolean()); } public void testStrictSemicolonDelimitedNameValuePair() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}")); reader.beginObject(); assertEquals("a", reader.nextName()); try { reader.nextBoolean(); reader.nextName(); fail(); } catch (IOException expected) { } } public void testLenientSemicolonDelimitedNameValuePair() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); assertEquals(true, reader.nextBoolean()); assertEquals("b", reader.nextName()); } public void testStrictUnnecessaryArraySeparators() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true,,true]")); reader.beginArray(); assertEquals(true, reader.nextBoolean()); try { reader.nextNull(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[,true]")); reader.beginArray(); try { reader.nextNull(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[true,]")); reader.beginArray(); assertEquals(true, reader.nextBoolean()); try { reader.nextNull(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[,]")); reader.beginArray(); try { reader.nextNull(); fail(); } catch (IOException expected) { } } public void testLenientUnnecessaryArraySeparators() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true,,true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader.nextNull(); assertEquals(true, reader.nextBoolean()); reader.endArray(); reader = new JsonReader(new StringReader("[,true]")); reader.setLenient(true); reader.beginArray(); reader.nextNull(); assertEquals(true, reader.nextBoolean()); reader.endArray(); reader = new JsonReader(new StringReader("[true,]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader.nextNull(); reader.endArray(); reader = new JsonReader(new StringReader("[,]")); reader.setLenient(true); reader.beginArray(); reader.nextNull(); reader.nextNull(); reader.endArray(); } } core/tests/coretests/src/android/util/JsonWriterTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -346,7 +346,7 @@ public final class JsonWriterTest extends TestCase { public void testPrettyPrintObject() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.setIndentSpaces(3); jsonWriter.setIndent(" "); jsonWriter.beginObject(); jsonWriter.name("a").value(true); Loading Loading @@ -383,7 +383,7 @@ public final class JsonWriterTest extends TestCase { public void testPrettyPrintArray() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.setIndentSpaces(3); jsonWriter.setIndent(" "); jsonWriter.beginArray(); jsonWriter.value(true); Loading Loading
api/current.xml +19 −4 Original line number Diff line number Diff line Loading @@ -180354,6 +180354,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> <method name="setLenient" return="void" abstract="false" native="false" synchronized="false" static="false" final="false" deprecated="not deprecated" visibility="public" > <parameter name="lenient" type="boolean"> </parameter> </method> <method name="skipValue" return="void" abstract="false" Loading Loading @@ -180424,6 +180437,8 @@ deprecated="not deprecated" visibility="public" > <implements name="java.io.Closeable"> </implements> <constructor name="JsonWriter" type="android.util.JsonWriter" static="false" Loading Loading @@ -180540,7 +180555,7 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> <method name="setIndentSpaces" <method name="setIndent" return="void" abstract="false" native="false" Loading @@ -180550,7 +180565,7 @@ deprecated="not deprecated" visibility="public" > <parameter name="indent" type="int"> <parameter name="indent" type="java.lang.String"> </parameter> </method> <method name="value" Loading Loading @@ -272628,7 +272643,7 @@ deprecated="not deprecated" visibility="public" > <parameter name="loop" type="boolean"> <parameter name="disable" type="boolean"> </parameter> <exception name="SocketException" type="java.net.SocketException"> </exception> Loading Loading @@ -274187,7 +274202,7 @@ deprecated="not deprecated" visibility="public" > <parameter name="value" type="boolean"> <parameter name="keepAlive" type="boolean"> </parameter> <exception name="SocketException" type="java.net.SocketException"> </exception>
core/java/android/util/JsonReader.java +103 −61 Original line number Diff line number Diff line Loading @@ -172,6 +172,9 @@ public final class JsonReader implements Closeable { /** The input JSON. */ private final Reader in; /** True to accept non-spec compliant JSON */ private boolean lenient = false; /** * Use a manual buffer to easily read and unread upcoming characters, and * also so we can create strings without an intermediate StringBuilder. Loading Loading @@ -207,9 +210,6 @@ public final class JsonReader implements Closeable { /** The text of the next literal value. */ private String value; // TODO: make this parser strict and offer an optional lenient mode? // TODO: document how this reader is non-strict /** * Creates a new instance that reads a JSON-encoded stream from {@code in}. */ Loading @@ -220,6 +220,31 @@ public final class JsonReader implements Closeable { this.in = in; } /** * Configure this parser to be be liberal in what it accepts. By default, * 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 * parser to lenient causes it to ignore the following syntax errors: * * <ul> * <li>End of line comments starting with {@code //} or {@code #} and * ending with a newline character. * <li>C-style comments starting with {@code /*} and ending with * {@code *}{@code /}. Such comments may not be nested. * <li>Names that are unquoted or {@code 'single quoted'}. * <li>Strings that are unquoted or {@code 'single quoted'}. * <li>Array elements separated by {@code ;} instead of {@code ,}. * <li>Unnecessary array separators. These are interpreted as if null * was the omitted value. * <li>Names and values separated by {@code =} or {@code =>} instead of * {@code :}. * <li>Name/value pairs separated by {@code ;} instead of {@code ,}. * </ul> */ public void setLenient(boolean lenient) { this.lenient = lenient; } /** * Consumes the next token from the JSON stream and asserts that it is the * beginning of a new array. Loading Loading @@ -253,11 +278,12 @@ public final class JsonReader implements Closeable { } /** * Consumes {@code token}. * Consumes {@code expected}. */ private void expect(JsonToken token) throws IOException { if (quickPeek() != token) { throw new IllegalStateException("Expected " + token + " but was " + peek()); private void expect(JsonToken expected) throws IOException { quickPeek(); if (token != expected) { throw new IllegalStateException("Expected " + expected + " but was " + peek()); } advance(); } Loading @@ -266,8 +292,8 @@ public final class JsonReader implements Closeable { * Returns true if the current array or object has another element. */ public boolean hasNext() throws IOException { JsonToken peek = quickPeek(); return peek != JsonToken.END_OBJECT && peek != JsonToken.END_ARRAY; quickPeek(); return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; } /** Loading @@ -285,11 +311,8 @@ public final class JsonReader implements Closeable { /** * Ensures that a token is ready. After this call either {@code token} or * {@code value} will be non-null. * * @return the type of the next token, of {@code null} if it is unknown. For * a definitive result, use {@link #peek()} which decodes the token * type. * {@code value} will be non-null. To ensure {@code token} has a definitive * value, use {@link #peek()} */ private JsonToken quickPeek() throws IOException { if (hasToken) { Loading Loading @@ -347,7 +370,8 @@ public final class JsonReader implements Closeable { * name. */ public String nextName() throws IOException { if (quickPeek() != JsonToken.NAME) { quickPeek(); if (token != JsonToken.NAME) { throw new IllegalStateException("Expected a name but was " + peek()); } String result = name; Loading @@ -364,8 +388,8 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public String nextString() throws IOException { JsonToken peek = peek(); if (value == null || (peek != JsonToken.STRING && peek != JsonToken.NUMBER)) { peek(); if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) { throw new IllegalStateException("Expected a string but was " + peek()); } Loading @@ -382,8 +406,8 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public boolean nextBoolean() throws IOException { JsonToken peek = quickPeek(); if (value == null || peek == JsonToken.STRING) { quickPeek(); if (value == null || token == JsonToken.STRING) { throw new IllegalStateException("Expected a boolean but was " + peek()); } Loading @@ -408,8 +432,8 @@ public final class JsonReader implements Closeable { * reader is closed. */ public void nextNull() throws IOException { JsonToken peek = quickPeek(); if (value == null || peek == JsonToken.STRING) { quickPeek(); if (value == null || token == JsonToken.STRING) { throw new IllegalStateException("Expected null but was " + peek()); } Loading Loading @@ -570,37 +594,44 @@ public final class JsonReader implements Closeable { private JsonToken nextInArray(boolean firstElement) throws IOException { if (firstElement) { replaceTop(JsonScope.NONEMPTY_ARRAY); } else { /* Look for a comma before each element after the first element. */ switch (nextNonWhitespace()) { case ']': pop(); hasToken = true; return token = JsonToken.END_ARRAY; case ',': case ';': /* a separator without a value first means "null". */ // TODO: forbid this in strict mode hasToken = true; return token = JsonToken.NULL; checkLenient(); // fall-through case ',': break; default: replaceTop(JsonScope.NONEMPTY_ARRAY); pos--; throw syntaxError("Unterminated array"); } } else { } switch (nextNonWhitespace()) { case ']': if (firstElement) { pop(); hasToken = true; return token = JsonToken.END_ARRAY; case ',': } // fall-through to handle ",]" case ';': break; case ',': /* In lenient mode, a 0-length literal means 'null' */ checkLenient(); pos--; hasToken = true; value = "null"; return token = JsonToken.NULL; default: throw syntaxError("Unterminated array"); } } pos--; return nextValue(); } } private JsonToken nextInObject(boolean firstElement) throws IOException { /* Loading Loading @@ -636,10 +667,12 @@ public final class JsonReader implements Closeable { int quote = nextNonWhitespace(); switch (quote) { case '\'': checkLenient(); // fall-through case '"': name = nextString((char) quote); break; default: checkLenient(); pos--; name = nextLiteral(); if (name.isEmpty()) { Loading @@ -653,20 +686,22 @@ public final class JsonReader implements Closeable { } private JsonToken objectValue() throws IOException { // TODO: accept only ":" in strict mode /* * Read the name/value separator. Usually a colon ':', an equals sign * '=', or an arrow "=>". The last two are bogus but we include them * because that's what org.json does. * Read the name/value separator. Usually a colon ':'. In lenient mode * we also accept an equals sign '=', or an arrow "=>". */ int separator = nextNonWhitespace(); if (separator != ':' && separator != '=') { throw syntaxError("Expected ':'"); } if (separator == '=' && (pos < limit || fillBuffer(1)) && buffer[pos] == '>') { switch (nextNonWhitespace()) { case ':': break; case '=': checkLenient(); if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { pos++; } break; default: throw syntaxError("Expected ':'"); } replaceTop(JsonScope.NONEMPTY_OBJECT); return nextValue(); Loading @@ -686,6 +721,7 @@ public final class JsonReader implements Closeable { return token = JsonToken.BEGIN_ARRAY; case '\'': checkLenient(); // fall-through case '"': value = nextString((char) c); hasToken = true; Loading Loading @@ -722,8 +758,6 @@ public final class JsonReader implements Closeable { } private int nextNonWhitespace() throws IOException { // TODO: no comments in strict mode while (pos < limit || fillBuffer(1)) { int c = buffer[pos++]; switch (c) { Loading @@ -738,6 +772,7 @@ public final class JsonReader implements Closeable { return c; } checkLenient(); char peek = buffer[pos]; switch (peek) { case '*': Loading Loading @@ -765,6 +800,7 @@ public final class JsonReader implements Closeable { * specify this behaviour, but it's required to parse * existing documents. See http://b/2571423. */ checkLenient(); skipToEndOfLine(); continue; Loading @@ -776,6 +812,12 @@ public final class JsonReader implements Closeable { throw syntaxError("End of input"); } private void checkLenient() throws IOException { if (!lenient) { throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); } } /** * Advances the position until after the next newline character. If the line * is terminated by "\r\n", the '\n' must be consumed as whitespace by the Loading Loading @@ -853,9 +895,6 @@ public final class JsonReader implements Closeable { * does not consume the delimiter character. */ private String nextLiteral() throws IOException { // TODO: use a much smaller set of permitted literal characters in strict mode; // these characters are derived from org.json's lenient mode StringBuilder builder = null; do { /* the index of the first character not yet appended to the builder. */ Loading @@ -863,17 +902,19 @@ public final class JsonReader implements Closeable { while (pos < limit) { int c = buffer[pos++]; switch (c) { case '/': case '\\': case ';': case '#': case '=': checkLenient(); // fall-through case '{': case '}': case '[': case ']': case '/': case '\\': case ':': case '=': case ',': case ';': case '#': case ' ': case '\t': case '\f': Loading Loading @@ -965,7 +1006,7 @@ public final class JsonReader implements Closeable { /** * Assigns {@code nextToken} based on the value of {@code nextValue}. */ private void decodeLiteral() { private void decodeLiteral() throws IOException { if (value.equalsIgnoreCase("null")) { token = JsonToken.NULL; } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { Loading @@ -975,7 +1016,8 @@ public final class JsonReader implements Closeable { Double.parseDouble(value); // this work could potentially be cached token = JsonToken.NUMBER; } catch (NumberFormatException ignored) { /* an unquoted string. This document is not well-formed! */ // this must be an unquoted string checkLenient(); token = JsonToken.STRING; } } Loading
core/java/android/util/JsonWriter.java +13 −16 Original line number Diff line number Diff line Loading @@ -16,10 +16,10 @@ package android.util; import java.io.Closeable; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** Loading Loading @@ -117,7 +117,7 @@ import java.util.List; * Instances of this class are not thread safe. Calls that would result in a * malformed JSON string will fail with an {@link IllegalStateException}. */ public final class JsonWriter { public final class JsonWriter implements Closeable { /** The output data, containing at most one top-level array or object. */ private final Writer out; Loading Loading @@ -151,22 +151,19 @@ public final class JsonWriter { } /** * Sets the number of spaces to indent each line in the encoded document. * If {@code indent == 0} the encoded document will be compact. If {@code * indent > 0}, the encoded document will be more human-readable. * Sets the indentation string to be repeated for each level of indentation * in the encoded document. If {@code indent.isEmpty()} the encoded document * will be compact. Otherwise the encoded document will be more * human-readable. * * @param indent a string containing only whitespace. */ public void setIndentSpaces(int indent) { if (indent < 0) { throw new IllegalArgumentException("indent < 0"); } if (indent > 0) { char[] indentChars = new char[indent]; Arrays.fill(indentChars, ' '); this.indent = new String(indentChars); public void setIndent(String indent) { if (indent.isEmpty()) { this.indent = null; this.separator = ":"; } else { this.indent = null; this.indent = indent; this.separator = ": "; } } Loading
core/tests/coretests/src/android/util/JsonReaderTest.java +268 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,14 @@ public final class JsonReaderTest extends TestCase { assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testReadEmptyArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[]")); reader.beginArray(); assertFalse(reader.hasNext()); reader.endArray(); assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testReadObject() throws IOException { JsonReader reader = new JsonReader(new StringReader( "{\"a\": \"android\", \"b\": \"banana\"}")); Loading @@ -44,6 +52,14 @@ public final class JsonReaderTest extends TestCase { assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testReadEmptyObject() throws IOException { JsonReader reader = new JsonReader(new StringReader("{}")); reader.beginObject(); assertFalse(reader.hasNext()); reader.endObject(); assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testSkipObject() throws IOException { JsonReader reader = new JsonReader(new StringReader( "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}")); Loading Loading @@ -412,4 +428,256 @@ public final class JsonReaderTest extends TestCase { } catch (IllegalStateException expected) { } } public void testStrictNameValueSeparator() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}")); reader.beginObject(); assertEquals("a", reader.nextName()); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("{\"a\"=>true}")); reader.beginObject(); assertEquals("a", reader.nextName()); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } } public void testLenientNameValueSeparator() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); assertEquals(true, reader.nextBoolean()); reader = new JsonReader(new StringReader("{\"a\"=>true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); assertEquals(true, reader.nextBoolean()); } public void testStrictComments() throws IOException { JsonReader reader = new JsonReader(new StringReader("[// comment \n true]")); reader.beginArray(); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[# comment \n true]")); reader.beginArray(); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[/* comment */ true]")); reader.beginArray(); try { reader.nextBoolean(); fail(); } catch (IOException expected) { } } public void testLenientComments() throws IOException { JsonReader reader = new JsonReader(new StringReader("[// comment \n true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader = new JsonReader(new StringReader("[# comment \n true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader = new JsonReader(new StringReader("[/* comment */ true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); } public void testStrictUnquotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{a:true}")); reader.beginObject(); try { reader.nextName(); fail(); } catch (IOException expected) { } } public void testLenientUnquotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{a:true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); } public void testStrictSingleQuotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{'a':true}")); reader.beginObject(); try { reader.nextName(); fail(); } catch (IOException expected) { } } public void testLenientSingleQuotedNames() throws IOException { JsonReader reader = new JsonReader(new StringReader("{'a':true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); } public void testStrictUnquotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("[a]")); reader.beginArray(); try { reader.nextString(); fail(); } catch (IOException expected) { } } public void testLenientUnquotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("[a]")); reader.setLenient(true); reader.beginArray(); assertEquals("a", reader.nextString()); } public void testStrictSingleQuotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("['a']")); reader.beginArray(); try { reader.nextString(); fail(); } catch (IOException expected) { } } public void testLenientSingleQuotedStrings() throws IOException { JsonReader reader = new JsonReader(new StringReader("['a']")); reader.setLenient(true); reader.beginArray(); assertEquals("a", reader.nextString()); } public void testStrictSemicolonDelimitedArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true;true]")); reader.beginArray(); try { reader.nextBoolean(); reader.nextBoolean(); fail(); } catch (IOException expected) { } } public void testLenientSemicolonDelimitedArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true;true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); assertEquals(true, reader.nextBoolean()); } public void testStrictSemicolonDelimitedNameValuePair() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}")); reader.beginObject(); assertEquals("a", reader.nextName()); try { reader.nextBoolean(); reader.nextName(); fail(); } catch (IOException expected) { } } public void testLenientSemicolonDelimitedNameValuePair() throws IOException { JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}")); reader.setLenient(true); reader.beginObject(); assertEquals("a", reader.nextName()); assertEquals(true, reader.nextBoolean()); assertEquals("b", reader.nextName()); } public void testStrictUnnecessaryArraySeparators() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true,,true]")); reader.beginArray(); assertEquals(true, reader.nextBoolean()); try { reader.nextNull(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[,true]")); reader.beginArray(); try { reader.nextNull(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[true,]")); reader.beginArray(); assertEquals(true, reader.nextBoolean()); try { reader.nextNull(); fail(); } catch (IOException expected) { } reader = new JsonReader(new StringReader("[,]")); reader.beginArray(); try { reader.nextNull(); fail(); } catch (IOException expected) { } } public void testLenientUnnecessaryArraySeparators() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true,,true]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader.nextNull(); assertEquals(true, reader.nextBoolean()); reader.endArray(); reader = new JsonReader(new StringReader("[,true]")); reader.setLenient(true); reader.beginArray(); reader.nextNull(); assertEquals(true, reader.nextBoolean()); reader.endArray(); reader = new JsonReader(new StringReader("[true,]")); reader.setLenient(true); reader.beginArray(); assertEquals(true, reader.nextBoolean()); reader.nextNull(); reader.endArray(); reader = new JsonReader(new StringReader("[,]")); reader.setLenient(true); reader.beginArray(); reader.nextNull(); reader.nextNull(); reader.endArray(); } }
core/tests/coretests/src/android/util/JsonWriterTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -346,7 +346,7 @@ public final class JsonWriterTest extends TestCase { public void testPrettyPrintObject() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.setIndentSpaces(3); jsonWriter.setIndent(" "); jsonWriter.beginObject(); jsonWriter.name("a").value(true); Loading Loading @@ -383,7 +383,7 @@ public final class JsonWriterTest extends TestCase { public void testPrettyPrintArray() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.setIndentSpaces(3); jsonWriter.setIndent(" "); jsonWriter.beginArray(); jsonWriter.value(true); Loading