2 * Copyright (C) 2010 Google Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.google
.gson
.stream
;
19 import com
.google
.gson
.internal
.JsonReaderInternalAccess
;
20 import com
.google
.gson
.internal
.bind
.JsonTreeReader
;
21 import java
.io
.Closeable
;
22 import java
.io
.EOFException
;
23 import java
.io
.IOException
;
24 import java
.io
.Reader
;
27 * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
28 * encoded value as a stream of tokens. This stream includes both literal
29 * values (strings, numbers, booleans, and nulls) as well as the begin and
30 * end delimiters of objects and arrays. The tokens are traversed in
31 * depth-first order, the same order that they appear in the JSON document.
32 * Within JSON objects, name/value pairs are represented by a single token.
34 * <h3>Parsing JSON</h3>
35 * To create a recursive descent parser for your own JSON streams, first create
36 * an entry point method that creates a {@code JsonReader}.
38 * <p>Next, create handler methods for each structure in your JSON text. You'll
39 * need a method for each object type and for each array type.
41 * <li>Within <strong>array handling</strong> methods, first call {@link
42 * #beginArray} to consume the array's opening bracket. Then create a
43 * while loop that accumulates values, terminating when {@link #hasNext}
44 * is false. Finally, read the array's closing bracket by calling {@link
46 * <li>Within <strong>object handling</strong> methods, first call {@link
47 * #beginObject} to consume the object's opening brace. Then create a
48 * while loop that assigns values to local variables based on their name.
49 * This loop should terminate when {@link #hasNext} is false. Finally,
50 * read the object's closing brace by calling {@link #endObject}.
52 * <p>When a nested object or array is encountered, delegate to the
53 * corresponding handler method.
55 * <p>When an unknown name is encountered, strict parsers should fail with an
56 * exception. Lenient parsers should call {@link #skipValue()} to recursively
57 * skip the value's nested tokens, which may otherwise conflict.
59 * <p>If a value may be null, you should first check using {@link #peek()}.
60 * Null literals can be consumed using either {@link #nextNull()} or {@link
64 * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
68 * "text": "How do I read a JSON stream in Java?",
71 * "name": "json_newb",
72 * "followers_count": 41
77 * "text": "@json_newb just use JsonReader!",
78 * "geo": [50.454722, -104.606667],
81 * "followers_count": 2
85 * This code implements the parser for the above structure: <pre> {@code
87 * public List<Message> readJsonStream(InputStream in) throws IOException {
88 * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
90 * return readMessagesArray(reader);
96 * public List<Message> readMessagesArray(JsonReader reader) throws IOException {
97 * List<Message> messages = new ArrayList<Message>();
99 * reader.beginArray();
100 * while (reader.hasNext()) {
101 * messages.add(readMessage(reader));
107 * public Message readMessage(JsonReader reader) throws IOException {
109 * String text = null;
111 * List<Double> geo = null;
113 * reader.beginObject();
114 * while (reader.hasNext()) {
115 * String name = reader.nextName();
116 * if (name.equals("id")) {
117 * id = reader.nextLong();
118 * } else if (name.equals("text")) {
119 * text = reader.nextString();
120 * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
121 * geo = readDoublesArray(reader);
122 * } else if (name.equals("user")) {
123 * user = readUser(reader);
125 * reader.skipValue();
128 * reader.endObject();
129 * return new Message(id, text, user, geo);
132 * public List<Double> readDoublesArray(JsonReader reader) throws IOException {
133 * List<Double> doubles = new ArrayList<Double>();
135 * reader.beginArray();
136 * while (reader.hasNext()) {
137 * doubles.add(reader.nextDouble());
143 * public User readUser(JsonReader reader) throws IOException {
144 * String username = null;
145 * int followersCount = -1;
147 * reader.beginObject();
148 * while (reader.hasNext()) {
149 * String name = reader.nextName();
150 * if (name.equals("name")) {
151 * username = reader.nextString();
152 * } else if (name.equals("followers_count")) {
153 * followersCount = reader.nextInt();
155 * reader.skipValue();
158 * reader.endObject();
159 * return new User(username, followersCount);
162 * <h3>Number Handling</h3>
163 * This reader permits numeric values to be read as strings and string values to
164 * be read as numbers. For example, both elements of the JSON array {@code
165 * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
166 * This behavior is intended to prevent lossy numeric conversions: double is
167 * JavaScript's only numeric type and very large values like {@code
168 * 9007199254740993} cannot be represented exactly on that platform. To minimize
169 * precision loss, extremely large values should be written and read as strings
172 * <a name="nonexecuteprefix"/><h3>Non-Execute Prefix</h3>
173 * Web servers that serve private data using JSON may be vulnerable to <a
174 * href="http://en.wikipedia.org/wiki/JSON#Cross-site_request_forgery">Cross-site
175 * request forgery</a> attacks. In such an attack, a malicious site gains access
176 * to a private JSON file by executing it with an HTML {@code <script>} tag.
178 * <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable
179 * by {@code <script>} tags, disarming the attack. Since the prefix is malformed
180 * JSON, strict parsing fails when it is encountered. This class permits the
181 * non-execute prefix when {@link #setLenient(boolean) lenient parsing} is
184 * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
185 * of this class are not thread safe.
187 * @author Jesse Wilson
190 public class JsonReader
implements Closeable
{
191 /** The only non-execute prefix this parser permits */
192 private static final char[] NON_EXECUTE_PREFIX
= ")]}'\n".toCharArray();
193 private static final long MIN_INCOMPLETE_INTEGER
= Long
.MIN_VALUE
/ 10;
195 private static final int PEEKED_NONE
= 0;
196 private static final int PEEKED_BEGIN_OBJECT
= 1;
197 private static final int PEEKED_END_OBJECT
= 2;
198 private static final int PEEKED_BEGIN_ARRAY
= 3;
199 private static final int PEEKED_END_ARRAY
= 4;
200 private static final int PEEKED_TRUE
= 5;
201 private static final int PEEKED_FALSE
= 6;
202 private static final int PEEKED_NULL
= 7;
203 private static final int PEEKED_SINGLE_QUOTED
= 8;
204 private static final int PEEKED_DOUBLE_QUOTED
= 9;
205 private static final int PEEKED_UNQUOTED
= 10;
206 /** When this is returned, the string value is stored in peekedString. */
207 private static final int PEEKED_BUFFERED
= 11;
208 private static final int PEEKED_SINGLE_QUOTED_NAME
= 12;
209 private static final int PEEKED_DOUBLE_QUOTED_NAME
= 13;
210 private static final int PEEKED_UNQUOTED_NAME
= 14;
211 /** When this is returned, the integer value is stored in peekedLong. */
212 private static final int PEEKED_LONG
= 15;
213 private static final int PEEKED_NUMBER
= 16;
214 private static final int PEEKED_EOF
= 17;
216 /* State machine when parsing numbers */
217 private static final int NUMBER_CHAR_NONE
= 0;
218 private static final int NUMBER_CHAR_SIGN
= 1;
219 private static final int NUMBER_CHAR_DIGIT
= 2;
220 private static final int NUMBER_CHAR_DECIMAL
= 3;
221 private static final int NUMBER_CHAR_FRACTION_DIGIT
= 4;
222 private static final int NUMBER_CHAR_EXP_E
= 5;
223 private static final int NUMBER_CHAR_EXP_SIGN
= 6;
224 private static final int NUMBER_CHAR_EXP_DIGIT
= 7;
226 /** The input JSON. */
227 private final Reader in
;
229 /** True to accept non-spec compliant JSON */
230 private boolean lenient
= false;
233 * Use a manual buffer to easily read and unread upcoming characters, and
234 * also so we can create strings without an intermediate StringBuilder.
235 * We decode literals directly out of this buffer, so it must be at least as
236 * long as the longest token that can be reported as a number.
238 private final char[] buffer
= new char[1024];
240 private int limit
= 0;
242 private int lineNumber
= 0;
243 private int lineStart
= 0;
245 private int peeked
= PEEKED_NONE
;
248 * A peeked value that was composed entirely of digits with an optional
249 * leading dash. Positive values may not have a leading 0.
251 private long peekedLong
;
254 * The number of characters in a peeked number literal. Increment 'pos' by
255 * this after reading a number.
257 private int peekedNumberLength
;
260 * A peeked string that should be parsed on the next double, long or string.
261 * This is populated before a numeric value is parsed and used if that parsing
264 private String peekedString
;
267 * The nesting stack. Using a manual array rather than an ArrayList saves 20%.
269 private int[] stack
= new int[32];
270 private int stackSize
= 0;
272 stack
[stackSize
++] = JsonScope
.EMPTY_DOCUMENT
;
276 * Creates a new instance that reads a JSON-encoded stream from {@code in}.
278 public JsonReader(Reader in
) {
280 throw new NullPointerException("in == null");
286 * Configure this parser to be be liberal in what it accepts. By default,
287 * this parser is strict and only accepts JSON as specified by <a
288 * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
289 * parser to lenient causes it to ignore the following syntax errors:
292 * <li>Streams that start with the <a href="#nonexecuteprefix">non-execute
293 * prefix</a>, <code>")]}'\n"</code>.
294 * <li>Streams that include multiple top-level values. With strict parsing,
295 * each stream must contain exactly one top-level value.
296 * <li>Top-level values of any type. With strict parsing, the top-level
297 * value must be an object or an array.
298 * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
299 * Double#isInfinite() infinities}.
300 * <li>End of line comments starting with {@code //} or {@code #} and
301 * ending with a newline character.
302 * <li>C-style comments starting with {@code /*} and ending with
303 * {@code *}{@code /}. Such comments may not be nested.
304 * <li>Names that are unquoted or {@code 'single quoted'}.
305 * <li>Strings that are unquoted or {@code 'single quoted'}.
306 * <li>Array elements separated by {@code ;} instead of {@code ,}.
307 * <li>Unnecessary array separators. These are interpreted as if null
308 * was the omitted value.
309 * <li>Names and values separated by {@code =} or {@code =>} instead of
311 * <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
314 public final void setLenient(boolean lenient
) {
315 this.lenient
= lenient
;
319 * Returns true if this parser is liberal in what it accepts.
321 public final boolean isLenient() {
326 * Consumes the next token from the JSON stream and asserts that it is the
327 * beginning of a new array.
329 public void beginArray() throws IOException
{
331 if (p
== PEEKED_NONE
) {
334 if (p
== PEEKED_BEGIN_ARRAY
) {
335 push(JsonScope
.EMPTY_ARRAY
);
336 peeked
= PEEKED_NONE
;
338 throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek()
339 + " at line " + getLineNumber() + " column " + getColumnNumber());
344 * Consumes the next token from the JSON stream and asserts that it is the
345 * end of the current array.
347 public void endArray() throws IOException
{
349 if (p
== PEEKED_NONE
) {
352 if (p
== PEEKED_END_ARRAY
) {
354 peeked
= PEEKED_NONE
;
356 throw new IllegalStateException("Expected END_ARRAY but was " + peek()
357 + " at line " + getLineNumber() + " column " + getColumnNumber());
362 * Consumes the next token from the JSON stream and asserts that it is the
363 * beginning of a new object.
365 public void beginObject() throws IOException
{
367 if (p
== PEEKED_NONE
) {
370 if (p
== PEEKED_BEGIN_OBJECT
) {
371 push(JsonScope
.EMPTY_OBJECT
);
372 peeked
= PEEKED_NONE
;
374 throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek()
375 + " at line " + getLineNumber() + " column " + getColumnNumber());
380 * Consumes the next token from the JSON stream and asserts that it is the
381 * end of the current object.
383 public void endObject() throws IOException
{
385 if (p
== PEEKED_NONE
) {
388 if (p
== PEEKED_END_OBJECT
) {
390 peeked
= PEEKED_NONE
;
392 throw new IllegalStateException("Expected END_OBJECT but was " + peek()
393 + " at line " + getLineNumber() + " column " + getColumnNumber());
398 * Returns true if the current array or object has another element.
400 public boolean hasNext() throws IOException
{
402 if (p
== PEEKED_NONE
) {
405 return p
!= PEEKED_END_OBJECT
&& p
!= PEEKED_END_ARRAY
;
409 * Returns the type of the next token without consuming it.
411 public JsonToken
peek() throws IOException
{
413 if (p
== PEEKED_NONE
) {
418 case PEEKED_BEGIN_OBJECT
:
419 return JsonToken
.BEGIN_OBJECT
;
420 case PEEKED_END_OBJECT
:
421 return JsonToken
.END_OBJECT
;
422 case PEEKED_BEGIN_ARRAY
:
423 return JsonToken
.BEGIN_ARRAY
;
424 case PEEKED_END_ARRAY
:
425 return JsonToken
.END_ARRAY
;
426 case PEEKED_SINGLE_QUOTED_NAME
:
427 case PEEKED_DOUBLE_QUOTED_NAME
:
428 case PEEKED_UNQUOTED_NAME
:
429 return JsonToken
.NAME
;
432 return JsonToken
.BOOLEAN
;
434 return JsonToken
.NULL
;
435 case PEEKED_SINGLE_QUOTED
:
436 case PEEKED_DOUBLE_QUOTED
:
437 case PEEKED_UNQUOTED
:
438 case PEEKED_BUFFERED
:
439 return JsonToken
.STRING
;
442 return JsonToken
.NUMBER
;
444 return JsonToken
.END_DOCUMENT
;
446 throw new AssertionError();
450 private int doPeek() throws IOException
{
451 int peekStack
= stack
[stackSize
- 1];
452 if (peekStack
== JsonScope
.EMPTY_ARRAY
) {
453 stack
[stackSize
- 1] = JsonScope
.NONEMPTY_ARRAY
;
454 } else if (peekStack
== JsonScope
.NONEMPTY_ARRAY
) {
455 // Look for a comma before the next element.
456 int c
= nextNonWhitespace(true);
459 return peeked
= PEEKED_END_ARRAY
;
461 checkLenient(); // fall-through
465 throw syntaxError("Unterminated array");
467 } else if (peekStack
== JsonScope
.EMPTY_OBJECT
|| peekStack
== JsonScope
.NONEMPTY_OBJECT
) {
468 stack
[stackSize
- 1] = JsonScope
.DANGLING_NAME
;
469 // Look for a comma before the next element.
470 if (peekStack
== JsonScope
.NONEMPTY_OBJECT
) {
471 int c
= nextNonWhitespace(true);
474 return peeked
= PEEKED_END_OBJECT
;
476 checkLenient(); // fall-through
480 throw syntaxError("Unterminated object");
483 int c
= nextNonWhitespace(true);
486 return peeked
= PEEKED_DOUBLE_QUOTED_NAME
;
489 return peeked
= PEEKED_SINGLE_QUOTED_NAME
;
491 if (peekStack
!= JsonScope
.NONEMPTY_OBJECT
) {
492 return peeked
= PEEKED_END_OBJECT
;
494 throw syntaxError("Expected name");
498 pos
--; // Don't consume the first character in an unquoted string.
499 if (isLiteral((char) c
)) {
500 return peeked
= PEEKED_UNQUOTED_NAME
;
502 throw syntaxError("Expected name");
505 } else if (peekStack
== JsonScope
.DANGLING_NAME
) {
506 stack
[stackSize
- 1] = JsonScope
.NONEMPTY_OBJECT
;
507 // Look for a colon before the value.
508 int c
= nextNonWhitespace(true);
514 if ((pos
< limit
|| fillBuffer(1)) && buffer
[pos
] == '>') {
519 throw syntaxError("Expected ':'");
521 } else if (peekStack
== JsonScope
.EMPTY_DOCUMENT
) {
523 consumeNonExecutePrefix();
525 stack
[stackSize
- 1] = JsonScope
.NONEMPTY_DOCUMENT
;
526 } else if (peekStack
== JsonScope
.NONEMPTY_DOCUMENT
) {
527 int c
= nextNonWhitespace(false);
529 return peeked
= PEEKED_EOF
;
534 } else if (peekStack
== JsonScope
.CLOSED
) {
535 throw new IllegalStateException("JsonReader is closed");
538 int c
= nextNonWhitespace(true);
541 if (peekStack
== JsonScope
.EMPTY_ARRAY
) {
542 return peeked
= PEEKED_END_ARRAY
;
544 // fall-through to handle ",]"
547 // In lenient mode, a 0-length literal in an array means 'null'.
548 if (peekStack
== JsonScope
.EMPTY_ARRAY
|| peekStack
== JsonScope
.NONEMPTY_ARRAY
) {
551 return peeked
= PEEKED_NULL
;
553 throw syntaxError("Unexpected value");
557 return peeked
= PEEKED_SINGLE_QUOTED
;
559 if (stackSize
== 1) {
562 return peeked
= PEEKED_DOUBLE_QUOTED
;
564 return peeked
= PEEKED_BEGIN_ARRAY
;
566 return peeked
= PEEKED_BEGIN_OBJECT
;
568 pos
--; // Don't consume the first character in a literal value.
571 if (stackSize
== 1) {
572 checkLenient(); // Top-level value isn't an array or an object.
575 int result
= peekKeyword();
576 if (result
!= PEEKED_NONE
) {
580 result
= peekNumber();
581 if (result
!= PEEKED_NONE
) {
585 if (!isLiteral(buffer
[pos
])) {
586 throw syntaxError("Expected value");
590 return peeked
= PEEKED_UNQUOTED
;
593 private int peekKeyword() throws IOException
{
594 // Figure out which keyword we're matching against by its first character.
595 char c
= buffer
[pos
];
599 if (c
== 't' || c
== 'T') {
601 keywordUpper
= "TRUE";
602 peeking
= PEEKED_TRUE
;
603 } else if (c
== 'f' || c
== 'F') {
605 keywordUpper
= "FALSE";
606 peeking
= PEEKED_FALSE
;
607 } else if (c
== 'n' || c
== 'N') {
609 keywordUpper
= "NULL";
610 peeking
= PEEKED_NULL
;
615 // Confirm that chars [1..length) match the keyword.
616 int length
= keyword
.length();
617 for (int i
= 1; i
< length
; i
++) {
618 if (pos
+ i
>= limit
&& !fillBuffer(i
+ 1)) {
622 if (c
!= keyword
.charAt(i
) && c
!= keywordUpper
.charAt(i
)) {
627 if ((pos
+ length
< limit
|| fillBuffer(length
+ 1))
628 && isLiteral(buffer
[pos
+ length
])) {
629 return PEEKED_NONE
; // Don't match trues, falsey or nullsoft!
632 // We've found the keyword followed either by EOF or by a non-literal character.
634 return peeked
= peeking
;
637 private int peekNumber() throws IOException
{
638 // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
639 char[] buffer
= this.buffer
;
643 long value
= 0; // Negative to accommodate Long.MIN_VALUE more easily.
644 boolean negative
= false;
645 boolean fitsInLong
= true;
646 int last
= NUMBER_CHAR_NONE
;
653 if (i
== buffer
.length
) {
654 // Though this looks like a well-formed number, it's too long to continue reading. Give up
655 // and let the application handle this as an unquoted literal.
658 if (!fillBuffer(i
+ 1)) {
665 char c
= buffer
[p
+ i
];
668 if (last
== NUMBER_CHAR_NONE
) {
670 last
= NUMBER_CHAR_SIGN
;
672 } else if (last
== NUMBER_CHAR_EXP_E
) {
673 last
= NUMBER_CHAR_EXP_SIGN
;
679 if (last
== NUMBER_CHAR_EXP_E
) {
680 last
= NUMBER_CHAR_EXP_SIGN
;
687 if (last
== NUMBER_CHAR_DIGIT
|| last
== NUMBER_CHAR_FRACTION_DIGIT
) {
688 last
= NUMBER_CHAR_EXP_E
;
694 if (last
== NUMBER_CHAR_DIGIT
) {
695 last
= NUMBER_CHAR_DECIMAL
;
701 if (c
< '0' || c
> '9') {
703 break charactersOfNumber
;
707 if (last
== NUMBER_CHAR_SIGN
|| last
== NUMBER_CHAR_NONE
) {
709 last
= NUMBER_CHAR_DIGIT
;
710 } else if (last
== NUMBER_CHAR_DIGIT
) {
712 return PEEKED_NONE
; // Leading '0' prefix is not allowed (since it could be octal).
714 long newValue
= value
* 10 - (c
- '0');
715 fitsInLong
&= value
> MIN_INCOMPLETE_INTEGER
716 || (value
== MIN_INCOMPLETE_INTEGER
&& newValue
< value
);
718 } else if (last
== NUMBER_CHAR_DECIMAL
) {
719 last
= NUMBER_CHAR_FRACTION_DIGIT
;
720 } else if (last
== NUMBER_CHAR_EXP_E
|| last
== NUMBER_CHAR_EXP_SIGN
) {
721 last
= NUMBER_CHAR_EXP_DIGIT
;
726 // We've read a complete number. Decide if it's a PEEKED_LONG or a PEEKED_NUMBER.
727 if (last
== NUMBER_CHAR_DIGIT
&& fitsInLong
&& (value
!= Long
.MIN_VALUE
|| negative
)) {
728 peekedLong
= negative ? value
: -value
;
730 return peeked
= PEEKED_LONG
;
731 } else if (last
== NUMBER_CHAR_DIGIT
|| last
== NUMBER_CHAR_FRACTION_DIGIT
732 || last
== NUMBER_CHAR_EXP_DIGIT
) {
733 peekedNumberLength
= i
;
734 return peeked
= PEEKED_NUMBER
;
740 private boolean isLiteral(char c
) throws IOException
{
747 checkLenient(); // fall-through
766 * Returns the next token, a {@link com.google.gson.stream.JsonToken#NAME property name}, and
769 * @throws java.io.IOException if the next token in the stream is not a property
772 public String
nextName() throws IOException
{
774 if (p
== PEEKED_NONE
) {
778 if (p
== PEEKED_UNQUOTED_NAME
) {
779 result
= nextUnquotedValue();
780 } else if (p
== PEEKED_SINGLE_QUOTED_NAME
) {
781 result
= nextQuotedValue('\'');
782 } else if (p
== PEEKED_DOUBLE_QUOTED_NAME
) {
783 result
= nextQuotedValue('"');
785 throw new IllegalStateException("Expected a name but was " + peek()
786 + " at line " + getLineNumber() + " column " + getColumnNumber());
788 peeked
= PEEKED_NONE
;
793 * Returns the {@link com.google.gson.stream.JsonToken#STRING string} value of the next token,
794 * consuming it. If the next token is a number, this method will return its
797 * @throws IllegalStateException if the next token is not a string or if
798 * this reader is closed.
800 public String
nextString() throws IOException
{
802 if (p
== PEEKED_NONE
) {
806 if (p
== PEEKED_UNQUOTED
) {
807 result
= nextUnquotedValue();
808 } else if (p
== PEEKED_SINGLE_QUOTED
) {
809 result
= nextQuotedValue('\'');
810 } else if (p
== PEEKED_DOUBLE_QUOTED
) {
811 result
= nextQuotedValue('"');
812 } else if (p
== PEEKED_BUFFERED
) {
813 result
= peekedString
;
815 } else if (p
== PEEKED_LONG
) {
816 result
= Long
.toString(peekedLong
);
817 } else if (p
== PEEKED_NUMBER
) {
818 result
= new String(buffer
, pos
, peekedNumberLength
);
819 pos
+= peekedNumberLength
;
821 throw new IllegalStateException("Expected a string but was " + peek()
822 + " at line " + getLineNumber() + " column " + getColumnNumber());
824 peeked
= PEEKED_NONE
;
829 * Returns the {@link com.google.gson.stream.JsonToken#BOOLEAN boolean} value of the next token,
832 * @throws IllegalStateException if the next token is not a boolean or if
833 * this reader is closed.
835 public boolean nextBoolean() throws IOException
{
837 if (p
== PEEKED_NONE
) {
840 if (p
== PEEKED_TRUE
) {
841 peeked
= PEEKED_NONE
;
843 } else if (p
== PEEKED_FALSE
) {
844 peeked
= PEEKED_NONE
;
847 throw new IllegalStateException("Expected a boolean but was " + peek()
848 + " at line " + getLineNumber() + " column " + getColumnNumber());
852 * Consumes the next token from the JSON stream and asserts that it is a
855 * @throws IllegalStateException if the next token is not null or if this
858 public void nextNull() throws IOException
{
860 if (p
== PEEKED_NONE
) {
863 if (p
== PEEKED_NULL
) {
864 peeked
= PEEKED_NONE
;
866 throw new IllegalStateException("Expected null but was " + peek()
867 + " at line " + getLineNumber() + " column " + getColumnNumber());
872 * Returns the {@link com.google.gson.stream.JsonToken#NUMBER double} value of the next token,
873 * consuming it. If the next token is a string, this method will attempt to
874 * parse it as a double using {@link Double#parseDouble(String)}.
876 * @throws IllegalStateException if the next token is not a literal value.
877 * @throws NumberFormatException if the next literal value cannot be parsed
878 * as a double, or is non-finite.
880 public double nextDouble() throws IOException
{
882 if (p
== PEEKED_NONE
) {
886 if (p
== PEEKED_LONG
) {
887 peeked
= PEEKED_NONE
;
888 return (double) peekedLong
;
891 if (p
== PEEKED_NUMBER
) {
892 peekedString
= new String(buffer
, pos
, peekedNumberLength
);
893 pos
+= peekedNumberLength
;
894 } else if (p
== PEEKED_SINGLE_QUOTED
|| p
== PEEKED_DOUBLE_QUOTED
) {
895 peekedString
= nextQuotedValue(p
== PEEKED_SINGLE_QUOTED ?
'\'' : '"');
896 } else if (p
== PEEKED_UNQUOTED
) {
897 peekedString
= nextUnquotedValue();
898 } else if (p
!= PEEKED_BUFFERED
) {
899 throw new IllegalStateException("Expected a double but was " + peek()
900 + " at line " + getLineNumber() + " column " + getColumnNumber());
903 peeked
= PEEKED_BUFFERED
;
904 double result
= Double
.parseDouble(peekedString
); // don't catch this NumberFormatException.
905 if (!lenient
&& (Double
.isNaN(result
) || Double
.isInfinite(result
))) {
906 throw new MalformedJsonException("JSON forbids NaN and infinities: " + result
907 + " at line " + getLineNumber() + " column " + getColumnNumber());
910 peeked
= PEEKED_NONE
;
915 * Returns the {@link com.google.gson.stream.JsonToken#NUMBER long} value of the next token,
916 * consuming it. If the next token is a string, this method will attempt to
917 * parse it as a long. If the next token's numeric value cannot be exactly
918 * represented by a Java {@code long}, this method throws.
920 * @throws IllegalStateException if the next token is not a literal value.
921 * @throws NumberFormatException if the next literal value cannot be parsed
922 * as a number, or exactly represented as a long.
924 public long nextLong() throws IOException
{
926 if (p
== PEEKED_NONE
) {
930 if (p
== PEEKED_LONG
) {
931 peeked
= PEEKED_NONE
;
935 if (p
== PEEKED_NUMBER
) {
936 peekedString
= new String(buffer
, pos
, peekedNumberLength
);
937 pos
+= peekedNumberLength
;
938 } else if (p
== PEEKED_SINGLE_QUOTED
|| p
== PEEKED_DOUBLE_QUOTED
) {
939 peekedString
= nextQuotedValue(p
== PEEKED_SINGLE_QUOTED ?
'\'' : '"');
941 long result
= Long
.parseLong(peekedString
);
942 peeked
= PEEKED_NONE
;
944 } catch (NumberFormatException ignored
) {
945 // Fall back to parse as a double below.
948 throw new IllegalStateException("Expected a long but was " + peek()
949 + " at line " + getLineNumber() + " column " + getColumnNumber());
952 peeked
= PEEKED_BUFFERED
;
953 double asDouble
= Double
.parseDouble(peekedString
); // don't catch this NumberFormatException.
954 long result
= (long) asDouble
;
955 if (result
!= asDouble
) { // Make sure no precision was lost casting to 'long'.
956 throw new NumberFormatException("Expected a long but was " + peekedString
957 + " at line " + getLineNumber() + " column " + getColumnNumber());
960 peeked
= PEEKED_NONE
;
965 * Returns the string up to but not including {@code quote}, unescaping any
966 * character escape sequences encountered along the way. The opening quote
967 * should have already been read. This consumes the closing quote, but does
968 * not include it in the returned string.
970 * @param quote either ' or ".
971 * @throws NumberFormatException if any unicode escape sequences are
974 private String
nextQuotedValue(char quote
) throws IOException
{
975 // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
976 char[] buffer
= this.buffer
;
977 StringBuilder builder
= new StringBuilder();
981 /* the index of the first character not yet appended to the builder. */
988 builder
.append(buffer
, start
, p
- start
- 1);
989 return builder
.toString();
990 } else if (c
== '\\') {
992 builder
.append(buffer
, start
, p
- start
- 1);
993 builder
.append(readEscapeCharacter());
997 } else if (c
== '\n') {
1003 builder
.append(buffer
, start
, p
- start
);
1005 if (!fillBuffer(1)) {
1006 throw syntaxError("Unterminated string");
1012 * Returns an unquoted value as a string.
1014 @SuppressWarnings("fallthrough")
1015 private String
nextUnquotedValue() throws IOException
{
1016 StringBuilder builder
= null;
1019 findNonLiteralCharacter
:
1021 for (; pos
+ i
< limit
; i
++) {
1022 switch (buffer
[pos
+ i
]) {
1028 checkLenient(); // fall-through
1040 break findNonLiteralCharacter
;
1044 // Attempt to load the entire literal into the buffer at once.
1045 if (i
< buffer
.length
) {
1046 if (fillBuffer(i
+ 1)) {
1053 // use a StringBuilder when the value is too long. This is too long to be a number!
1054 if (builder
== null) {
1055 builder
= new StringBuilder();
1057 builder
.append(buffer
, pos
, i
);
1060 if (!fillBuffer(1)) {
1066 if (builder
== null) {
1067 result
= new String(buffer
, pos
, i
);
1069 builder
.append(buffer
, pos
, i
);
1070 result
= builder
.toString();
1076 private void skipQuotedValue(char quote
) throws IOException
{
1077 // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
1078 char[] buffer
= this.buffer
;
1082 /* the index of the first character not yet appended to the builder. */
1084 int c
= buffer
[p
++];
1088 } else if (c
== '\\') {
1090 readEscapeCharacter();
1093 } else if (c
== '\n') {
1099 } while (fillBuffer(1));
1100 throw syntaxError("Unterminated string");
1103 private void skipUnquotedValue() throws IOException
{
1106 for (; pos
+ i
< limit
; i
++) {
1107 switch (buffer
[pos
+ i
]) {
1113 checkLenient(); // fall-through
1130 } while (fillBuffer(1));
1134 * Returns the {@link com.google.gson.stream.JsonToken#NUMBER int} value of the next token,
1135 * consuming it. If the next token is a string, this method will attempt to
1136 * parse it as an int. If the next token's numeric value cannot be exactly
1137 * represented by a Java {@code int}, this method throws.
1139 * @throws IllegalStateException if the next token is not a literal value.
1140 * @throws NumberFormatException if the next literal value cannot be parsed
1141 * as a number, or exactly represented as an int.
1143 public int nextInt() throws IOException
{
1145 if (p
== PEEKED_NONE
) {
1150 if (p
== PEEKED_LONG
) {
1151 result
= (int) peekedLong
;
1152 if (peekedLong
!= result
) { // Make sure no precision was lost casting to 'int'.
1153 throw new NumberFormatException("Expected an int but was " + peekedLong
1154 + " at line " + getLineNumber() + " column " + getColumnNumber());
1156 peeked
= PEEKED_NONE
;
1160 if (p
== PEEKED_NUMBER
) {
1161 peekedString
= new String(buffer
, pos
, peekedNumberLength
);
1162 pos
+= peekedNumberLength
;
1163 } else if (p
== PEEKED_SINGLE_QUOTED
|| p
== PEEKED_DOUBLE_QUOTED
) {
1164 peekedString
= nextQuotedValue(p
== PEEKED_SINGLE_QUOTED ?
'\'' : '"');
1166 result
= Integer
.parseInt(peekedString
);
1167 peeked
= PEEKED_NONE
;
1169 } catch (NumberFormatException ignored
) {
1170 // Fall back to parse as a double below.
1173 throw new IllegalStateException("Expected an int but was " + peek()
1174 + " at line " + getLineNumber() + " column " + getColumnNumber());
1177 peeked
= PEEKED_BUFFERED
;
1178 double asDouble
= Double
.parseDouble(peekedString
); // don't catch this NumberFormatException.
1179 result
= (int) asDouble
;
1180 if (result
!= asDouble
) { // Make sure no precision was lost casting to 'int'.
1181 throw new NumberFormatException("Expected an int but was " + peekedString
1182 + " at line " + getLineNumber() + " column " + getColumnNumber());
1184 peekedString
= null;
1185 peeked
= PEEKED_NONE
;
1190 * Closes this JSON reader and the underlying {@link java.io.Reader}.
1192 public void close() throws IOException
{
1193 peeked
= PEEKED_NONE
;
1194 stack
[0] = JsonScope
.CLOSED
;
1200 * Skips the next value recursively. If it is an object or array, all nested
1201 * elements are skipped. This method is intended for use when the JSON token
1202 * stream contains unrecognized or unhandled values.
1204 public void skipValue() throws IOException
{
1208 if (p
== PEEKED_NONE
) {
1212 if (p
== PEEKED_BEGIN_ARRAY
) {
1213 push(JsonScope
.EMPTY_ARRAY
);
1215 } else if (p
== PEEKED_BEGIN_OBJECT
) {
1216 push(JsonScope
.EMPTY_OBJECT
);
1218 } else if (p
== PEEKED_END_ARRAY
) {
1221 } else if (p
== PEEKED_END_OBJECT
) {
1224 } else if (p
== PEEKED_UNQUOTED_NAME
|| p
== PEEKED_UNQUOTED
) {
1225 skipUnquotedValue();
1226 } else if (p
== PEEKED_SINGLE_QUOTED
|| p
== PEEKED_SINGLE_QUOTED_NAME
) {
1227 skipQuotedValue('\'');
1228 } else if (p
== PEEKED_DOUBLE_QUOTED
|| p
== PEEKED_DOUBLE_QUOTED_NAME
) {
1229 skipQuotedValue('"');
1230 } else if (p
== PEEKED_NUMBER
) {
1231 pos
+= peekedNumberLength
;
1233 peeked
= PEEKED_NONE
;
1234 } while (count
!= 0);
1237 private void push(int newTop
) {
1238 if (stackSize
== stack
.length
) {
1239 int[] newStack
= new int[stackSize
* 2];
1240 System
.arraycopy(stack
, 0, newStack
, 0, stackSize
);
1243 stack
[stackSize
++] = newTop
;
1247 * Returns true once {@code limit - pos >= minimum}. If the data is
1248 * exhausted before that many characters are available, this returns
1251 private boolean fillBuffer(int minimum
) throws IOException
{
1252 char[] buffer
= this.buffer
;
1256 System
.arraycopy(buffer
, pos
, buffer
, 0, limit
);
1263 while ((total
= in
.read(buffer
, limit
, buffer
.length
- limit
)) != -1) {
1266 // if this is the first read, consume an optional byte order mark (BOM) if it exists
1267 if (lineNumber
== 0 && lineStart
== 0 && limit
> 0 && buffer
[0] == '\ufeff') {
1273 if (limit
>= minimum
) {
1280 private int getLineNumber() {
1281 return lineNumber
+ 1;
1284 private int getColumnNumber() {
1285 return pos
- lineStart
+ 1;
1289 * Returns the next character in the stream that is neither whitespace nor a
1290 * part of a comment. When this returns, the returned character is always at
1291 * {@code buffer[pos-1]}; this means the caller can always push back the
1292 * returned character by decrementing {@code pos}.
1294 private int nextNonWhitespace(boolean throwOnEof
) throws IOException
{
1296 * This code uses ugly local variables 'p' and 'l' representing the 'pos'
1297 * and 'limit' fields respectively. Using locals rather than fields saves
1298 * a few field reads for each whitespace character in a pretty-printed
1299 * document, resulting in a 5% speedup. We need to flush 'p' to its field
1300 * before any (potentially indirect) call to fillBuffer() and reread both
1301 * 'p' and 'l' after any (potentially indirect) call to the same method.
1303 char[] buffer
= this.buffer
;
1309 if (!fillBuffer(1)) {
1316 int c
= buffer
[p
++];
1321 } else if (c
== ' ' || c
== '\r' || c
== '\t') {
1328 pos
--; // push back '/' so it's still in the buffer when this method returns
1329 boolean charsLoaded
= fillBuffer(2);
1330 pos
++; // consume the '/' again
1337 char peek
= buffer
[pos
];
1340 // skip a /* c-style comment */
1342 if (!skipTo("*/")) {
1343 throw syntaxError("Unterminated comment");
1350 // skip a // end-of-line comment
1360 } else if (c
== '#') {
1363 * Skip a # hash end-of-line comment. The JSON RFC doesn't
1364 * specify this behaviour, but it's required to parse
1365 * existing documents. See http://b/2571423.
1377 throw new EOFException("End of input"
1378 + " at line " + getLineNumber() + " column " + getColumnNumber());
1384 private void checkLenient() throws IOException
{
1386 throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
1391 * Advances the position until after the next newline character. If the line
1392 * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
1395 private void skipToEndOfLine() throws IOException
{
1396 while (pos
< limit
|| fillBuffer(1)) {
1397 char c
= buffer
[pos
++];
1402 } else if (c
== '\r') {
1409 * @param toFind a string to search for. Must not contain a newline.
1411 private boolean skipTo(String toFind
) throws IOException
{
1413 for (; pos
+ toFind
.length() <= limit
|| fillBuffer(toFind
.length()); pos
++) {
1414 if (buffer
[pos
] == '\n') {
1416 lineStart
= pos
+ 1;
1419 for (int c
= 0; c
< toFind
.length(); c
++) {
1420 if (buffer
[pos
+ c
] != toFind
.charAt(c
)) {
1429 @Override public String
toString() {
1430 return getClass().getSimpleName()
1431 + " at line " + getLineNumber() + " column " + getColumnNumber();
1435 * Unescapes the character identified by the character or characters that
1436 * immediately follow a backslash. The backslash '\' should have already
1437 * been read. This supports both unicode escapes "u000A" and two-character
1440 * @throws NumberFormatException if any unicode escape sequences are
1443 private char readEscapeCharacter() throws IOException
{
1444 if (pos
== limit
&& !fillBuffer(1)) {
1445 throw syntaxError("Unterminated escape sequence");
1448 char escaped
= buffer
[pos
++];
1451 if (pos
+ 4 > limit
&& !fillBuffer(4)) {
1452 throw syntaxError("Unterminated escape sequence");
1454 // Equivalent to Integer.parseInt(stringPool.get(buffer, pos, 4), 16);
1456 for (int i
= pos
, end
= i
+ 4; i
< end
; i
++) {
1459 if (c
>= '0' && c
<= '9') {
1460 result
+= (c
- '0');
1461 } else if (c
>= 'a' && c
<= 'f') {
1462 result
+= (c
- 'a' + 10);
1463 } else if (c
>= 'A' && c
<= 'F') {
1464 result
+= (c
- 'A' + 10);
1466 throw new NumberFormatException("\\u" + new String(buffer
, pos
, 4));
1501 * Throws a new IO exception with the given message and a context snippet
1502 * with this reader's content.
1504 private IOException
syntaxError(String message
) throws IOException
{
1505 throw new MalformedJsonException(message
1506 + " at line " + getLineNumber() + " column " + getColumnNumber());
1510 * Consumes the non-execute prefix if it exists.
1512 private void consumeNonExecutePrefix() throws IOException
{
1513 // fast forward through the leading whitespace
1514 nextNonWhitespace(true);
1517 if (pos
+ NON_EXECUTE_PREFIX
.length
> limit
&& !fillBuffer(NON_EXECUTE_PREFIX
.length
)) {
1521 for (int i
= 0; i
< NON_EXECUTE_PREFIX
.length
; i
++) {
1522 if (buffer
[pos
+ i
] != NON_EXECUTE_PREFIX
[i
]) {
1523 return; // not a security token!
1527 // we consumed a security token!
1528 pos
+= NON_EXECUTE_PREFIX
.length
;
1532 JsonReaderInternalAccess
.INSTANCE
= new JsonReaderInternalAccess() {
1533 @Override public void promoteNameToValue(JsonReader reader
) throws IOException
{
1534 if (reader
instanceof JsonTreeReader
) {
1535 ((JsonTreeReader
)reader
).promoteNameToValue();
1538 int p
= reader
.peeked
;
1539 if (p
== PEEKED_NONE
) {
1540 p
= reader
.doPeek();
1542 if (p
== PEEKED_DOUBLE_QUOTED_NAME
) {
1543 reader
.peeked
= PEEKED_DOUBLE_QUOTED
;
1544 } else if (p
== PEEKED_SINGLE_QUOTED_NAME
) {
1545 reader
.peeked
= PEEKED_SINGLE_QUOTED
;
1546 } else if (p
== PEEKED_UNQUOTED_NAME
) {
1547 reader
.peeked
= PEEKED_UNQUOTED
;
1549 throw new IllegalStateException("Expected a name but was " + reader
.peek() + " "
1550 + " at line " + reader
.getLineNumber() + " column " + reader
.getColumnNumber());