Methods Summary |
---|
private JsonToken | advance()Advances the cursor in the JSON stream to the next token.
peek();
JsonToken result = token;
token = null;
value = null;
name = null;
return result;
|
public void | beginArray()Consumes the next token from the JSON stream and asserts that it is the
beginning of a new array.
expect(JsonToken.BEGIN_ARRAY);
|
public void | beginObject()Consumes the next token from the JSON stream and asserts that it is the
beginning of a new object.
expect(JsonToken.BEGIN_OBJECT);
|
private void | checkLenient()
if (!lenient) {
throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
}
|
public void | close()Closes this JSON reader and the underlying {@link Reader}.
value = null;
token = null;
stack.clear();
stack.add(JsonScope.CLOSED);
in.close();
|
private JsonToken | decodeLiteral()Assigns {@code nextToken} based on the value of {@code nextValue}.
if (valuePos == -1) {
// it was too long to fit in the buffer so it can only be a string
return JsonToken.STRING;
} else if (valueLength == 4
&& ('n" == buffer[valuePos ] || 'N" == buffer[valuePos ])
&& ('u" == buffer[valuePos + 1] || 'U" == buffer[valuePos + 1])
&& ('l" == buffer[valuePos + 2] || 'L" == buffer[valuePos + 2])
&& ('l" == buffer[valuePos + 3] || 'L" == buffer[valuePos + 3])) {
value = "null";
return JsonToken.NULL;
} else if (valueLength == 4
&& ('t" == buffer[valuePos ] || 'T" == buffer[valuePos ])
&& ('r" == buffer[valuePos + 1] || 'R" == buffer[valuePos + 1])
&& ('u" == buffer[valuePos + 2] || 'U" == buffer[valuePos + 2])
&& ('e" == buffer[valuePos + 3] || 'E" == buffer[valuePos + 3])) {
value = TRUE;
return JsonToken.BOOLEAN;
} else if (valueLength == 5
&& ('f" == buffer[valuePos ] || 'F" == buffer[valuePos ])
&& ('a" == buffer[valuePos + 1] || 'A" == buffer[valuePos + 1])
&& ('l" == buffer[valuePos + 2] || 'L" == buffer[valuePos + 2])
&& ('s" == buffer[valuePos + 3] || 'S" == buffer[valuePos + 3])
&& ('e" == buffer[valuePos + 4] || 'E" == buffer[valuePos + 4])) {
value = FALSE;
return JsonToken.BOOLEAN;
} else {
value = stringPool.get(buffer, valuePos, valueLength);
return decodeNumber(buffer, valuePos, valueLength);
}
|
private JsonToken | decodeNumber(char[] chars, int offset, int length)Determine whether the characters is a JSON number. Numbers are of the
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.
int i = offset;
int c = chars[i];
if (c == '-") {
c = chars[++i];
}
if (c == '0") {
c = chars[++i];
} else if (c >= '1" && c <= '9") {
c = chars[++i];
while (c >= '0" && c <= '9") {
c = chars[++i];
}
} else {
return JsonToken.STRING;
}
if (c == '.") {
c = chars[++i];
while (c >= '0" && c <= '9") {
c = chars[++i];
}
}
if (c == 'e" || c == 'E") {
c = chars[++i];
if (c == '+" || c == '-") {
c = chars[++i];
}
if (c >= '0" && c <= '9") {
c = chars[++i];
while (c >= '0" && c <= '9") {
c = chars[++i];
}
} else {
return JsonToken.STRING;
}
}
if (i == offset + length) {
return JsonToken.NUMBER;
} else {
return JsonToken.STRING;
}
|
public void | endArray()Consumes the next token from the JSON stream and asserts that it is the
end of the current array.
expect(JsonToken.END_ARRAY);
|
public void | endObject()Consumes the next token from the JSON stream and asserts that it is the
end of the current array.
expect(JsonToken.END_OBJECT);
|
private void | expect(JsonToken expected)Consumes {@code expected}.
peek();
if (token != expected) {
throw new IllegalStateException("Expected " + expected + " but was " + peek());
}
advance();
|
private boolean | fillBuffer(int minimum)Returns true once {@code limit - pos >= minimum}. If the data is
exhausted before that many characters are available, this returns
false.
// Before clobbering the old characters, update where buffer starts
for (int i = 0; i < pos; i++) {
if (buffer[i] == '\n") {
bufferStartLine++;
bufferStartColumn = 1;
} else {
bufferStartColumn++;
}
}
if (limit != pos) {
limit -= pos;
System.arraycopy(buffer, pos, buffer, 0, limit);
} else {
limit = 0;
}
pos = 0;
int total;
while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
limit += total;
// if this is the first read, consume an optional byte order mark (BOM) if it exists
if (bufferStartLine == 1 && bufferStartColumn == 1
&& limit > 0 && buffer[0] == '\ufeff") {
pos++;
bufferStartColumn--;
}
if (limit >= minimum) {
return true;
}
}
return false;
|
private int | getColumnNumber()
int result = bufferStartColumn;
for (int i = 0; i < pos; i++) {
if (buffer[i] == '\n") {
result = 1;
} else {
result++;
}
}
return result;
|
private int | getLineNumber()
int result = bufferStartLine;
for (int i = 0; i < pos; i++) {
if (buffer[i] == '\n") {
result++;
}
}
return result;
|
private java.lang.CharSequence | getSnippet()
StringBuilder snippet = new StringBuilder();
int beforePos = Math.min(pos, 20);
snippet.append(buffer, pos - beforePos, beforePos);
int afterPos = Math.min(limit - pos, 20);
snippet.append(buffer, pos, afterPos);
return snippet;
|
public boolean | hasNext()Returns true if the current array or object has another element.
peek();
return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
|
public boolean | isLenient()Returns true if this parser is liberal in what it accepts.
return lenient;
|
public boolean | nextBoolean()Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
consuming it.
peek();
if (token != JsonToken.BOOLEAN) {
throw new IllegalStateException("Expected a boolean but was " + token);
}
boolean result = (value == TRUE);
advance();
return result;
|
public double | nextDouble()Returns the {@link JsonToken#NUMBER double} value of the next token,
consuming it. If the next token is a string, this method will attempt to
parse it as a double using {@link Double#parseDouble(String)}.
peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException("Expected a double but was " + token);
}
double result = Double.parseDouble(value);
advance();
return result;
|
private JsonToken | nextInArray(boolean firstElement)
if (firstElement) {
replaceTop(JsonScope.NONEMPTY_ARRAY);
} else {
/* Look for a comma before each element after the first element. */
switch (nextNonWhitespace()) {
case ']":
pop();
return token = JsonToken.END_ARRAY;
case ';":
checkLenient(); // fall-through
case ',":
break;
default:
throw syntaxError("Unterminated array");
}
}
switch (nextNonWhitespace()) {
case ']":
if (firstElement) {
pop();
return token = JsonToken.END_ARRAY;
}
// fall-through to handle ",]"
case ';":
case ',":
/* In lenient mode, a 0-length literal means 'null' */
checkLenient();
pos--;
value = "null";
return token = JsonToken.NULL;
default:
pos--;
return nextValue();
}
|
private JsonToken | nextInObject(boolean firstElement)
/*
* Read delimiters. Either a comma/semicolon separating this and the
* previous name-value pair, or a close brace to denote the end of the
* object.
*/
if (firstElement) {
/* Peek to see if this is the empty object. */
switch (nextNonWhitespace()) {
case '}":
pop();
return token = JsonToken.END_OBJECT;
default:
pos--;
}
} else {
switch (nextNonWhitespace()) {
case '}":
pop();
return token = JsonToken.END_OBJECT;
case ';":
case ',":
break;
default:
throw syntaxError("Unterminated object");
}
}
/* Read the name. */
int quote = nextNonWhitespace();
switch (quote) {
case '\'":
checkLenient(); // fall-through
case '"":
name = nextString((char) quote);
break;
default:
checkLenient();
pos--;
name = nextLiteral(false);
if (name.isEmpty()) {
throw syntaxError("Expected name");
}
}
replaceTop(JsonScope.DANGLING_NAME);
return token = JsonToken.NAME;
|
public int | nextInt()Returns the {@link JsonToken#NUMBER int} value of the next token,
consuming it. If the next token is a string, this method will attempt to
parse it as an int. If the next token's numeric value cannot be exactly
represented by a Java {@code int}, this method throws.
peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException("Expected an int but was " + token);
}
int result;
try {
result = Integer.parseInt(value);
} catch (NumberFormatException ignored) {
double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
result = (int) asDouble;
if ((double) result != asDouble) {
throw new NumberFormatException(value);
}
}
advance();
return result;
|
private java.lang.String | nextLiteral(boolean assignOffsetsOnly)Reads the value up to but not including any delimiter characters. This
does not consume the delimiter character.
StringBuilder builder = null;
valuePos = -1;
valueLength = 0;
int i = 0;
findNonLiteralCharacter:
while (true) {
for (; pos + i < limit; i++) {
switch (buffer[pos + i]) {
case '/":
case '\\":
case ';":
case '#":
case '=":
checkLenient(); // fall-through
case '{":
case '}":
case '[":
case ']":
case ':":
case ',":
case ' ":
case '\t":
case '\f":
case '\r":
case '\n":
break findNonLiteralCharacter;
}
}
/*
* Attempt to load the entire literal into the buffer at once. If
* we run out of input, add a non-literal character at the end so
* that decoding doesn't need to do bounds checks.
*/
if (i < buffer.length) {
if (fillBuffer(i + 1)) {
continue;
} else {
buffer[limit] = '\0";
break;
}
}
// use a StringBuilder when the value is too long. It must be an unquoted string.
if (builder == null) {
builder = new StringBuilder();
}
builder.append(buffer, pos, i);
valueLength += i;
pos += i;
i = 0;
if (!fillBuffer(1)) {
break;
}
}
String result;
if (assignOffsetsOnly && builder == null) {
valuePos = pos;
result = null;
} else if (skipping) {
result = "skipped!";
} else if (builder == null) {
result = stringPool.get(buffer, pos, i);
} else {
builder.append(buffer, pos, i);
result = builder.toString();
}
valueLength += i;
pos += i;
return result;
|
public long | nextLong()Returns the {@link JsonToken#NUMBER long} value of the next token,
consuming it. If the next token is a string, this method will attempt to
parse it as a long. If the next token's numeric value cannot be exactly
represented by a Java {@code long}, this method throws.
peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException("Expected a long but was " + token);
}
long result;
try {
result = Long.parseLong(value);
} catch (NumberFormatException ignored) {
double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
result = (long) asDouble;
if ((double) result != asDouble) {
throw new NumberFormatException(value);
}
}
advance();
return result;
|
public java.lang.String | nextName()Returns the next token, a {@link JsonToken#NAME property name}, and
consumes it.
peek();
if (token != JsonToken.NAME) {
throw new IllegalStateException("Expected a name but was " + peek());
}
String result = name;
advance();
return result;
|
private int | nextNonWhitespace()
while (pos < limit || fillBuffer(1)) {
int c = buffer[pos++];
switch (c) {
case '\t":
case ' ":
case '\n":
case '\r":
continue;
case '/":
if (pos == limit && !fillBuffer(1)) {
return c;
}
checkLenient();
char peek = buffer[pos];
switch (peek) {
case '*":
// skip a /* c-style comment */
pos++;
if (!skipTo("*/")) {
throw syntaxError("Unterminated comment");
}
pos += 2;
continue;
case '/":
// skip a // end-of-line comment
pos++;
skipToEndOfLine();
continue;
default:
return c;
}
case '#":
/*
* Skip a # hash end-of-line comment. The JSON RFC doesn't
* specify this behaviour, but it's required to parse
* existing documents. See http://b/2571423.
*/
checkLenient();
skipToEndOfLine();
continue;
default:
return c;
}
}
throw new EOFException("End of input");
|
public void | nextNull()Consumes the next token from the JSON stream and asserts that it is a
literal null.
peek();
if (token != JsonToken.NULL) {
throw new IllegalStateException("Expected null but was " + token);
}
advance();
|
public java.lang.String | nextString()Returns the {@link JsonToken#STRING string} value of the next token,
consuming it. If the next token is a number, this method will return its
string form.
peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException("Expected a string but was " + peek());
}
String result = value;
advance();
return result;
|
private java.lang.String | nextString(char quote)Returns the string up to but not including {@code quote}, unescaping any
character escape sequences encountered along the way. The opening quote
should have already been read. This consumes the closing quote, but does
not include it in the returned string.
StringBuilder builder = null;
do {
/* the index of the first character not yet appended to the builder. */
int start = pos;
while (pos < limit) {
int c = buffer[pos++];
if (c == quote) {
if (skipping) {
return "skipped!";
} else if (builder == null) {
return stringPool.get(buffer, start, pos - start - 1);
} else {
builder.append(buffer, start, pos - start - 1);
return builder.toString();
}
} else if (c == '\\") {
if (builder == null) {
builder = new StringBuilder();
}
builder.append(buffer, start, pos - start - 1);
builder.append(readEscapeCharacter());
start = pos;
}
}
if (builder == null) {
builder = new StringBuilder();
}
builder.append(buffer, start, pos - start);
} while (fillBuffer(1));
throw syntaxError("Unterminated string");
|
private JsonToken | nextValue()
int c = nextNonWhitespace();
switch (c) {
case '{":
push(JsonScope.EMPTY_OBJECT);
return token = JsonToken.BEGIN_OBJECT;
case '[":
push(JsonScope.EMPTY_ARRAY);
return token = JsonToken.BEGIN_ARRAY;
case '\'":
checkLenient(); // fall-through
case '"":
value = nextString((char) c);
return token = JsonToken.STRING;
default:
pos--;
return readLiteral();
}
|
private JsonToken | objectValue()
/*
* Read the name/value separator. Usually a colon ':'. In lenient mode
* we also accept an equals sign '=', or an arrow "=>".
*/
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();
|
public JsonToken | peek()Returns the type of the next token without consuming it.
if (token != null) {
return token;
}
switch (peekStack()) {
case EMPTY_DOCUMENT:
replaceTop(JsonScope.NONEMPTY_DOCUMENT);
JsonToken firstToken = nextValue();
if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
throw new IOException(
"Expected JSON document to start with '[' or '{' but was " + token);
}
return firstToken;
case EMPTY_ARRAY:
return nextInArray(true);
case NONEMPTY_ARRAY:
return nextInArray(false);
case EMPTY_OBJECT:
return nextInObject(true);
case DANGLING_NAME:
return objectValue();
case NONEMPTY_OBJECT:
return nextInObject(false);
case NONEMPTY_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:
throw new AssertionError();
}
|
private JsonScope | peekStack()
return stack.get(stack.size() - 1);
|
private JsonScope | pop()
return stack.remove(stack.size() - 1);
|
private void | push(JsonScope newTop)
stack.add(newTop);
|
private char | readEscapeCharacter()Unescapes the character identified by the character or characters that
immediately follow a backslash. The backslash '\' should have already
been read. This supports both unicode escapes "u000A" and two-character
escapes "\n".
if (pos == limit && !fillBuffer(1)) {
throw syntaxError("Unterminated escape sequence");
}
char escaped = buffer[pos++];
switch (escaped) {
case 'u":
if (pos + 4 > limit && !fillBuffer(4)) {
throw syntaxError("Unterminated escape sequence");
}
String hex = stringPool.get(buffer, pos, 4);
pos += 4;
return (char) Integer.parseInt(hex, 16);
case 't":
return '\t";
case 'b":
return '\b";
case 'n":
return '\n";
case 'r":
return '\r";
case 'f":
return '\f";
case '\'":
case '"":
case '\\":
default:
return escaped;
}
|
private JsonToken | readLiteral()Reads a null, boolean, numeric or unquoted string literal value.
value = nextLiteral(true);
if (valueLength == 0) {
throw syntaxError("Expected literal value");
}
token = decodeLiteral();
if (token == JsonToken.STRING) {
checkLenient();
}
return token;
|
private void | replaceTop(JsonScope newTop)Replace the value on the top of the stack with the given value.
stack.set(stack.size() - 1, newTop);
|
public void | setLenient(boolean lenient)Configure this parser to be be liberal in what it accepts. By default,
this parser is strict and only accepts JSON as specified by RFC 4627. Setting the
parser to lenient causes it to ignore the following syntax errors:
- End of line comments starting with {@code //} or {@code #} and
ending with a newline character.
- C-style comments starting with {@code /*} and ending with
{@code *}{@code /}. Such comments may not be nested.
- Names that are unquoted or {@code 'single quoted'}.
- Strings that are unquoted or {@code 'single quoted'}.
- Array elements separated by {@code ;} instead of {@code ,}.
- Unnecessary array separators. These are interpreted as if null
was the omitted value.
- Names and values separated by {@code =} or {@code =>} instead of
{@code :}.
- Name/value pairs separated by {@code ;} instead of {@code ,}.
this.lenient = lenient;
|
private boolean | skipTo(java.lang.String toFind)
outer:
for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) {
for (int c = 0; c < toFind.length(); c++) {
if (buffer[pos + c] != toFind.charAt(c)) {
continue outer;
}
}
return true;
}
return false;
|
private void | skipToEndOfLine()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
caller.
while (pos < limit || fillBuffer(1)) {
char c = buffer[pos++];
if (c == '\r" || c == '\n") {
break;
}
}
|
public void | skipValue()Skips the next value recursively. If it is an object or array, all nested
elements are skipped. This method is intended for use when the JSON token
stream contains unrecognized or unhandled values.
skipping = true;
try {
if (!hasNext() || peek() == JsonToken.END_DOCUMENT) {
throw new IllegalStateException("No element left to skip");
}
int count = 0;
do {
JsonToken token = advance();
if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
count++;
} else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
count--;
}
} while (count != 0);
} finally {
skipping = false;
}
|
private java.io.IOException | syntaxError(java.lang.String message)Throws a new IO exception with the given message and a context snippet
with this reader's content.
throw new MalformedJsonException(message
+ " at line " + getLineNumber() + " column " + getColumnNumber());
|
public java.lang.String | toString()
return getClass().getSimpleName() + " near " + getSnippet();
|