JsonReader.java revision d07fb882f84e9fa7b758870261747456f2752ba5
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package android.util; 18 19import java.io.IOException; 20import java.io.Reader; 21import java.io.Closeable; 22import java.util.ArrayList; 23import java.util.List; 24 25/** 26 * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) 27 * encoded value as a stream of tokens. This stream includes both literal 28 * values (strings, numbers, booleans, and nulls) as well as the begin and 29 * end delimiters of objects and arrays. The tokens are traversed in 30 * depth-first order, the same order that they appear in the JSON document. 31 * Within JSON objects, name/value pairs are represented by a single token. 32 * 33 * <h3>Parsing JSON</h3> 34 * To create a recursive descent parser your own JSON streams, first create an 35 * entry point method that creates a {@code JsonReader}. 36 * 37 * <p>Next, create handler methods for each structure in your JSON text. You'll 38 * need a method for each object type and for each array type. 39 * <ul> 40 * <li>Within <strong>array handling</strong> methods, first call {@link 41 * #beginArray} to consume the array's opening bracket. Then create a 42 * while loop that accumulates values, terminating when {@link #hasNext} 43 * is false. Finally, read the array's closing bracket by calling {@link 44 * #endArray}. 45 * <li>Within <strong>object handling</strong> methods, first call {@link 46 * #beginObject} to consume the object's opening brace. Then create a 47 * while loop that assigns values to local variables based on their name. 48 * This loop should terminate when {@link #hasNext} is false. Finally, 49 * read the object's closing brace by calling {@link #endObject}. 50 * </ul> 51 * <p>When a nested object or array is encountered, delegate to the 52 * corresponding handler method. 53 * 54 * <p>When an unknown name is encountered, strict parsers should fail with an 55 * exception. Lenient parsers should call {@link #skipValue()} to recursively 56 * skip the value's nested tokens, which may otherwise conflict. 57 * 58 * <p>If a value may be null, you should first check using {@link #peek()}. 59 * Null literals can be consumed using either {@link #nextNull()} or {@link 60 * #skipValue()}. 61 * 62 * <h3>Example</h3> 63 * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code 64 * [ 65 * { 66 * "id": 912345678901, 67 * "text": "How do I read JSON on Android?", 68 * "geo": null, 69 * "user": { 70 * "name": "android_newb", 71 * "followers_count": 41 72 * } 73 * }, 74 * { 75 * "id": 912345678902, 76 * "text": "@android_newb just use android.util.JsonReader!", 77 * "geo": [50.454722, -104.606667], 78 * "user": { 79 * "name": "jesse", 80 * "followers_count": 2 81 * } 82 * } 83 * ]}</pre> 84 * This code implements the parser for the above structure: <pre> {@code 85 * 86 * public List<Message> readJsonStream(InputStream in) throws IOException { 87 * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); 88 * return readMessagesArray(reader); 89 * } 90 * 91 * public List<Message> readMessagesArray(JsonReader reader) throws IOException { 92 * List<Message> messages = new ArrayList<Message>(); 93 * 94 * reader.beginArray(); 95 * while (reader.hasNext()) { 96 * messages.add(readMessage(reader)); 97 * } 98 * reader.endArray(); 99 * return messages; 100 * } 101 * 102 * public Message readMessage(JsonReader reader) throws IOException { 103 * long id = -1; 104 * String text = null; 105 * User user = null; 106 * List<Double> geo = null; 107 * 108 * reader.beginObject(); 109 * while (reader.hasNext()) { 110 * String name = reader.nextName(); 111 * if (name.equals("id")) { 112 * id = reader.nextLong(); 113 * } else if (name.equals("text")) { 114 * text = reader.nextString(); 115 * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) { 116 * geo = readDoublesArray(reader); 117 * } else if (name.equals("user")) { 118 * user = readUser(reader); 119 * } else { 120 * reader.skipValue(); 121 * } 122 * } 123 * reader.endObject(); 124 * return new Message(id, text, user, geo); 125 * } 126 * 127 * public List<Double> readDoublesArray(JsonReader reader) throws IOException { 128 * List<Double> doubles = new ArrayList<Double>(); 129 * 130 * reader.beginArray(); 131 * while (reader.hasNext()) { 132 * doubles.add(reader.nextDouble()); 133 * } 134 * reader.endArray(); 135 * return doubles; 136 * } 137 * 138 * public User readUser(JsonReader reader) throws IOException { 139 * String username = null; 140 * int followersCount = -1; 141 * 142 * reader.beginObject(); 143 * while (reader.hasNext()) { 144 * String name = reader.nextName(); 145 * if (name.equals("name")) { 146 * username = reader.nextString(); 147 * } else if (name.equals("followers_count")) { 148 * followersCount = reader.nextInt(); 149 * } else { 150 * reader.skipValue(); 151 * } 152 * } 153 * reader.endObject(); 154 * return new User(username, followersCount); 155 * }}</pre> 156 * 157 * <h3>Number Handling</h3> 158 * This reader permits numeric values to be read as strings and string values to 159 * be read as numbers. For example, both elements of the JSON array {@code 160 * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}. 161 * This behavior is intended to prevent lossy numeric conversions: double is 162 * JavaScript's only numeric type and very large values like {@code 163 * 9007199254740993} cannot be represented exactly on that platform. To minimize 164 * precision loss, extremely large values should be written and read as strings 165 * in JSON. 166 * 167 * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances 168 * of this class are not thread safe. 169 */ 170public final class JsonReader implements Closeable { 171 172 /** The input JSON. */ 173 private final Reader in; 174 175 /** True to accept non-spec compliant JSON */ 176 private boolean lenient = false; 177 178 /** 179 * Use a manual buffer to easily read and unread upcoming characters, and 180 * also so we can create strings without an intermediate StringBuilder. 181 */ 182 private final char[] buffer = new char[1024]; 183 private int pos = 0; 184 private int limit = 0; 185 186 private final List<JsonScope> stack = new ArrayList<JsonScope>(); 187 { 188 push(JsonScope.EMPTY_DOCUMENT); 189 } 190 191 /** 192 * True if we've already read the next token. If we have, the string value 193 * for that token will be assigned to {@code value} if such a string value 194 * exists. And the token type will be assigned to {@code token} if the token 195 * type is known. The token type may be null for literals, since we derive 196 * that lazily. 197 */ 198 private boolean hasToken; 199 200 /** 201 * The type of the next token to be returned by {@link #peek} and {@link 202 * #advance}, or {@code null} if it is unknown and must first be derived 203 * from {@code value}. This value is undefined if {@code hasToken} is false. 204 */ 205 private JsonToken token; 206 207 /** The text of the next name. */ 208 private String name; 209 210 /** The text of the next literal value. */ 211 private String value; 212 213 /** True if we're currently handling a skipValue() call. */ 214 private boolean skipping = false; 215 216 /** 217 * Creates a new instance that reads a JSON-encoded stream from {@code in}. 218 */ 219 public JsonReader(Reader in) { 220 if (in == null) { 221 throw new NullPointerException("in == null"); 222 } 223 this.in = in; 224 } 225 226 /** 227 * Configure this parser to be be liberal in what it accepts. By default, 228 * this parser is strict and only accepts JSON as specified by <a 229 * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the 230 * parser to lenient causes it to ignore the following syntax errors: 231 * 232 * <ul> 233 * <li>End of line comments starting with {@code //} or {@code #} and 234 * ending with a newline character. 235 * <li>C-style comments starting with {@code /*} and ending with 236 * {@code *}{@code /}. Such comments may not be nested. 237 * <li>Names that are unquoted or {@code 'single quoted'}. 238 * <li>Strings that are unquoted or {@code 'single quoted'}. 239 * <li>Array elements separated by {@code ;} instead of {@code ,}. 240 * <li>Unnecessary array separators. These are interpreted as if null 241 * was the omitted value. 242 * <li>Names and values separated by {@code =} or {@code =>} instead of 243 * {@code :}. 244 * <li>Name/value pairs separated by {@code ;} instead of {@code ,}. 245 * </ul> 246 */ 247 public void setLenient(boolean lenient) { 248 this.lenient = lenient; 249 } 250 251 /** 252 * Consumes the next token from the JSON stream and asserts that it is the 253 * beginning of a new array. 254 */ 255 public void beginArray() throws IOException { 256 expect(JsonToken.BEGIN_ARRAY); 257 } 258 259 /** 260 * Consumes the next token from the JSON stream and asserts that it is the 261 * end of the current array. 262 */ 263 public void endArray() throws IOException { 264 expect(JsonToken.END_ARRAY); 265 } 266 267 /** 268 * Consumes the next token from the JSON stream and asserts that it is the 269 * beginning of a new object. 270 */ 271 public void beginObject() throws IOException { 272 expect(JsonToken.BEGIN_OBJECT); 273 } 274 275 /** 276 * Consumes the next token from the JSON stream and asserts that it is the 277 * end of the current array. 278 */ 279 public void endObject() throws IOException { 280 expect(JsonToken.END_OBJECT); 281 } 282 283 /** 284 * Consumes {@code expected}. 285 */ 286 private void expect(JsonToken expected) throws IOException { 287 quickPeek(); 288 if (token != expected) { 289 throw new IllegalStateException("Expected " + expected + " but was " + peek()); 290 } 291 advance(); 292 } 293 294 /** 295 * Returns true if the current array or object has another element. 296 */ 297 public boolean hasNext() throws IOException { 298 quickPeek(); 299 return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; 300 } 301 302 /** 303 * Returns the type of the next token without consuming it. 304 */ 305 public JsonToken peek() throws IOException { 306 quickPeek(); 307 308 if (token == null) { 309 decodeLiteral(); 310 } 311 312 return token; 313 } 314 315 /** 316 * Ensures that a token is ready. After this call either {@code token} or 317 * {@code value} will be non-null. To ensure {@code token} has a definitive 318 * value, use {@link #peek()} 319 */ 320 private JsonToken quickPeek() throws IOException { 321 if (hasToken) { 322 return token; 323 } 324 325 switch (peekStack()) { 326 case EMPTY_DOCUMENT: 327 replaceTop(JsonScope.NONEMPTY_DOCUMENT); 328 JsonToken firstToken = nextValue(); 329 if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) { 330 throw new IOException( 331 "Expected JSON document to start with '[' or '{' but was " + token); 332 } 333 return firstToken; 334 case EMPTY_ARRAY: 335 return nextInArray(true); 336 case NONEMPTY_ARRAY: 337 return nextInArray(false); 338 case EMPTY_OBJECT: 339 return nextInObject(true); 340 case DANGLING_NAME: 341 return objectValue(); 342 case NONEMPTY_OBJECT: 343 return nextInObject(false); 344 case NONEMPTY_DOCUMENT: 345 hasToken = true; 346 return token = JsonToken.END_DOCUMENT; 347 case CLOSED: 348 throw new IllegalStateException("JsonReader is closed"); 349 default: 350 throw new AssertionError(); 351 } 352 } 353 354 /** 355 * Advances the cursor in the JSON stream to the next token. 356 */ 357 private JsonToken advance() throws IOException { 358 quickPeek(); 359 360 JsonToken result = token; 361 hasToken = false; 362 token = null; 363 value = null; 364 name = null; 365 return result; 366 } 367 368 /** 369 * Returns the next token, a {@link JsonToken#NAME property name}, and 370 * consumes it. 371 * 372 * @throws IOException if the next token in the stream is not a property 373 * name. 374 */ 375 public String nextName() throws IOException { 376 quickPeek(); 377 if (token != JsonToken.NAME) { 378 throw new IllegalStateException("Expected a name but was " + peek()); 379 } 380 String result = name; 381 advance(); 382 return result; 383 } 384 385 /** 386 * Returns the {@link JsonToken#STRING string} value of the next token, 387 * consuming it. If the next token is a number, this method will return its 388 * string form. 389 * 390 * @throws IllegalStateException if the next token is not a string or if 391 * this reader is closed. 392 */ 393 public String nextString() throws IOException { 394 peek(); 395 if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) { 396 throw new IllegalStateException("Expected a string but was " + peek()); 397 } 398 399 String result = value; 400 advance(); 401 return result; 402 } 403 404 /** 405 * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token, 406 * consuming it. 407 * 408 * @throws IllegalStateException if the next token is not a boolean or if 409 * this reader is closed. 410 */ 411 public boolean nextBoolean() throws IOException { 412 quickPeek(); 413 if (value == null || token == JsonToken.STRING) { 414 throw new IllegalStateException("Expected a boolean but was " + peek()); 415 } 416 417 boolean result; 418 if (value.equalsIgnoreCase("true")) { 419 result = true; 420 } else if (value.equalsIgnoreCase("false")) { 421 result = false; 422 } else { 423 throw new IllegalStateException("Not a boolean: " + value); 424 } 425 426 advance(); 427 return result; 428 } 429 430 /** 431 * Consumes the next token from the JSON stream and asserts that it is a 432 * literal null. 433 * 434 * @throws IllegalStateException if the next token is not null or if this 435 * reader is closed. 436 */ 437 public void nextNull() throws IOException { 438 quickPeek(); 439 if (value == null || token == JsonToken.STRING) { 440 throw new IllegalStateException("Expected null but was " + peek()); 441 } 442 443 if (!value.equalsIgnoreCase("null")) { 444 throw new IllegalStateException("Not a null: " + value); 445 } 446 447 advance(); 448 } 449 450 /** 451 * Returns the {@link JsonToken#NUMBER double} value of the next token, 452 * consuming it. If the next token is a string, this method will attempt to 453 * parse it as a double. 454 * 455 * @throws IllegalStateException if the next token is not a literal value. 456 * @throws NumberFormatException if the next literal value cannot be parsed 457 * as a double, or is non-finite. 458 */ 459 public double nextDouble() throws IOException { 460 quickPeek(); 461 if (value == null) { 462 throw new IllegalStateException("Expected a double but was " + peek()); 463 } 464 465 double result = Double.parseDouble(value); 466 467 if ((result >= 1.0d && value.startsWith("0")) 468 || Double.isNaN(result) 469 || Double.isInfinite(result)) { 470 throw new NumberFormatException( 471 "JSON forbids octal prefixes, NaN and infinities: " + value); 472 } 473 474 advance(); 475 return result; 476 } 477 478 /** 479 * Returns the {@link JsonToken#NUMBER long} value of the next token, 480 * consuming it. If the next token is a string, this method will attempt to 481 * parse it as a long. If the next token's numeric value cannot be exactly 482 * represented by a Java {@code long}, this method throws. 483 * 484 * @throws IllegalStateException if the next token is not a literal value. 485 * @throws NumberFormatException if the next literal value cannot be parsed 486 * as a number, or exactly represented as a long. 487 */ 488 public long nextLong() throws IOException { 489 quickPeek(); 490 if (value == null) { 491 throw new IllegalStateException("Expected a long but was " + peek()); 492 } 493 494 long result; 495 try { 496 result = Long.parseLong(value); 497 } catch (NumberFormatException ignored) { 498 double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException 499 result = (long) asDouble; 500 if ((double) result != asDouble) { 501 throw new NumberFormatException(value); 502 } 503 } 504 505 if (result >= 1L && value.startsWith("0")) { 506 throw new NumberFormatException("JSON forbids octal prefixes: " + value); 507 } 508 509 advance(); 510 return result; 511 } 512 513 /** 514 * Returns the {@link JsonToken#NUMBER int} value of the next token, 515 * consuming it. If the next token is a string, this method will attempt to 516 * parse it as an int. If the next token's numeric value cannot be exactly 517 * represented by a Java {@code int}, this method throws. 518 * 519 * @throws IllegalStateException if the next token is not a literal value. 520 * @throws NumberFormatException if the next literal value cannot be parsed 521 * as a number, or exactly represented as an int. 522 */ 523 public int nextInt() throws IOException { 524 quickPeek(); 525 if (value == null) { 526 throw new IllegalStateException("Expected an int but was " + peek()); 527 } 528 529 int result; 530 try { 531 result = Integer.parseInt(value); 532 } catch (NumberFormatException ignored) { 533 double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException 534 result = (int) asDouble; 535 if ((double) result != asDouble) { 536 throw new NumberFormatException(value); 537 } 538 } 539 540 if (result >= 1L && value.startsWith("0")) { 541 throw new NumberFormatException("JSON forbids octal prefixes: " + value); 542 } 543 544 advance(); 545 return result; 546 } 547 548 /** 549 * Closes this JSON reader and the underlying {@link Reader}. 550 */ 551 public void close() throws IOException { 552 hasToken = false; 553 value = null; 554 token = null; 555 stack.clear(); 556 stack.add(JsonScope.CLOSED); 557 in.close(); 558 } 559 560 /** 561 * Skips the next value recursively. If it is an object or array, all nested 562 * elements are skipped. This method is intended for use when the JSON token 563 * stream contains unrecognized or unhandled values. 564 */ 565 public void skipValue() throws IOException { 566 skipping = true; 567 try { 568 int count = 0; 569 do { 570 JsonToken token = advance(); 571 if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) { 572 count++; 573 } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) { 574 count--; 575 } 576 } while (count != 0); 577 } finally { 578 skipping = false; 579 } 580 } 581 582 private JsonScope peekStack() { 583 return stack.get(stack.size() - 1); 584 } 585 586 private JsonScope pop() { 587 return stack.remove(stack.size() - 1); 588 } 589 590 private void push(JsonScope newTop) { 591 stack.add(newTop); 592 } 593 594 /** 595 * Replace the value on the top of the stack with the given value. 596 */ 597 private void replaceTop(JsonScope newTop) { 598 stack.set(stack.size() - 1, newTop); 599 } 600 601 private JsonToken nextInArray(boolean firstElement) throws IOException { 602 if (firstElement) { 603 replaceTop(JsonScope.NONEMPTY_ARRAY); 604 } else { 605 /* Look for a comma before each element after the first element. */ 606 switch (nextNonWhitespace()) { 607 case ']': 608 pop(); 609 hasToken = true; 610 return token = JsonToken.END_ARRAY; 611 case ';': 612 checkLenient(); // fall-through 613 case ',': 614 break; 615 default: 616 throw syntaxError("Unterminated array"); 617 } 618 } 619 620 switch (nextNonWhitespace()) { 621 case ']': 622 if (firstElement) { 623 pop(); 624 hasToken = true; 625 return token = JsonToken.END_ARRAY; 626 } 627 // fall-through to handle ",]" 628 case ';': 629 case ',': 630 /* In lenient mode, a 0-length literal means 'null' */ 631 checkLenient(); 632 pos--; 633 hasToken = true; 634 value = "null"; 635 return token = JsonToken.NULL; 636 default: 637 pos--; 638 return nextValue(); 639 } 640 } 641 642 private JsonToken nextInObject(boolean firstElement) throws IOException { 643 /* 644 * Read delimiters. Either a comma/semicolon separating this and the 645 * previous name-value pair, or a close brace to denote the end of the 646 * object. 647 */ 648 if (firstElement) { 649 /* Peek to see if this is the empty object. */ 650 switch (nextNonWhitespace()) { 651 case '}': 652 pop(); 653 hasToken = true; 654 return token = JsonToken.END_OBJECT; 655 default: 656 pos--; 657 } 658 } else { 659 switch (nextNonWhitespace()) { 660 case '}': 661 pop(); 662 hasToken = true; 663 return token = JsonToken.END_OBJECT; 664 case ';': 665 case ',': 666 break; 667 default: 668 throw syntaxError("Unterminated object"); 669 } 670 } 671 672 /* Read the name. */ 673 int quote = nextNonWhitespace(); 674 switch (quote) { 675 case '\'': 676 checkLenient(); // fall-through 677 case '"': 678 name = nextString((char) quote); 679 break; 680 default: 681 checkLenient(); 682 pos--; 683 name = nextLiteral(); 684 if (name.isEmpty()) { 685 throw syntaxError("Expected name"); 686 } 687 } 688 689 replaceTop(JsonScope.DANGLING_NAME); 690 hasToken = true; 691 return token = JsonToken.NAME; 692 } 693 694 private JsonToken objectValue() throws IOException { 695 /* 696 * Read the name/value separator. Usually a colon ':'. In lenient mode 697 * we also accept an equals sign '=', or an arrow "=>". 698 */ 699 switch (nextNonWhitespace()) { 700 case ':': 701 break; 702 case '=': 703 checkLenient(); 704 if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { 705 pos++; 706 } 707 break; 708 default: 709 throw syntaxError("Expected ':'"); 710 } 711 712 replaceTop(JsonScope.NONEMPTY_OBJECT); 713 return nextValue(); 714 } 715 716 private JsonToken nextValue() throws IOException { 717 int c = nextNonWhitespace(); 718 switch (c) { 719 case '{': 720 push(JsonScope.EMPTY_OBJECT); 721 hasToken = true; 722 return token = JsonToken.BEGIN_OBJECT; 723 724 case '[': 725 push(JsonScope.EMPTY_ARRAY); 726 hasToken = true; 727 return token = JsonToken.BEGIN_ARRAY; 728 729 case '\'': 730 checkLenient(); // fall-through 731 case '"': 732 value = nextString((char) c); 733 hasToken = true; 734 return token = JsonToken.STRING; 735 736 default: 737 pos--; 738 return readLiteral(); 739 } 740 } 741 742 /** 743 * Returns true once {@code limit - pos >= minimum}. If the data is 744 * exhausted before that many characters are available, this returns 745 * false. 746 */ 747 private boolean fillBuffer(int minimum) throws IOException { 748 if (limit != pos) { 749 limit -= pos; 750 System.arraycopy(buffer, pos, buffer, 0, limit); 751 } else { 752 limit = 0; 753 } 754 755 pos = 0; 756 int total; 757 while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) { 758 limit += total; 759 if (limit >= minimum) { 760 return true; 761 } 762 } 763 return false; 764 } 765 766 private int nextNonWhitespace() throws IOException { 767 while (pos < limit || fillBuffer(1)) { 768 int c = buffer[pos++]; 769 switch (c) { 770 case '\t': 771 case ' ': 772 case '\n': 773 case '\r': 774 continue; 775 776 case '/': 777 if (pos == limit && !fillBuffer(1)) { 778 return c; 779 } 780 781 checkLenient(); 782 char peek = buffer[pos]; 783 switch (peek) { 784 case '*': 785 // skip a /* c-style comment */ 786 pos++; 787 if (!skipTo("*/")) { 788 throw syntaxError("Unterminated comment"); 789 } 790 pos += 2; 791 continue; 792 793 case '/': 794 // skip a // end-of-line comment 795 pos++; 796 skipToEndOfLine(); 797 continue; 798 799 default: 800 return c; 801 } 802 803 case '#': 804 /* 805 * Skip a # hash end-of-line comment. The JSON RFC doesn't 806 * specify this behaviour, but it's required to parse 807 * existing documents. See http://b/2571423. 808 */ 809 checkLenient(); 810 skipToEndOfLine(); 811 continue; 812 813 default: 814 return c; 815 } 816 } 817 818 throw syntaxError("End of input"); 819 } 820 821 private void checkLenient() throws IOException { 822 if (!lenient) { 823 throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); 824 } 825 } 826 827 /** 828 * Advances the position until after the next newline character. If the line 829 * is terminated by "\r\n", the '\n' must be consumed as whitespace by the 830 * caller. 831 */ 832 private void skipToEndOfLine() throws IOException { 833 while (pos < limit || fillBuffer(1)) { 834 char c = buffer[pos++]; 835 if (c == '\r' || c == '\n') { 836 break; 837 } 838 } 839 } 840 841 private boolean skipTo(String toFind) throws IOException { 842 outer: 843 for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) { 844 for (int c = 0; c < toFind.length(); c++) { 845 if (buffer[pos + c] != toFind.charAt(c)) { 846 continue outer; 847 } 848 } 849 return true; 850 } 851 return false; 852 } 853 854 /** 855 * Returns the string up to but not including {@code quote}, unescaping any 856 * character escape sequences encountered along the way. The opening quote 857 * should have already been read. This consumes the closing quote, but does 858 * not include it in the returned string. 859 * 860 * @param quote either ' or ". 861 * @throws NumberFormatException if any unicode escape sequences are 862 * malformed. 863 */ 864 private String nextString(char quote) throws IOException { 865 StringBuilder builder = null; 866 do { 867 /* the index of the first character not yet appended to the builder. */ 868 int start = pos; 869 while (pos < limit) { 870 int c = buffer[pos++]; 871 872 if (c == quote) { 873 if (skipping) { 874 return "skipped!"; 875 } else if (builder == null) { 876 return new String(buffer, start, pos - start - 1); 877 } else { 878 builder.append(buffer, start, pos - start - 1); 879 return builder.toString(); 880 } 881 882 } else if (c == '\\') { 883 if (builder == null) { 884 builder = new StringBuilder(); 885 } 886 builder.append(buffer, start, pos - start - 1); 887 builder.append(readEscapeCharacter()); 888 start = pos; 889 } 890 } 891 892 if (builder == null) { 893 builder = new StringBuilder(); 894 } 895 builder.append(buffer, start, pos - start); 896 } while (fillBuffer(1)); 897 898 throw syntaxError("Unterminated string"); 899 } 900 901 /** 902 * Returns the string up to but not including any delimiter characters. This 903 * does not consume the delimiter character. 904 */ 905 private String nextLiteral() throws IOException { 906 StringBuilder builder = null; 907 do { 908 /* the index of the first character not yet appended to the builder. */ 909 int start = pos; 910 while (pos < limit) { 911 int c = buffer[pos++]; 912 switch (c) { 913 case '/': 914 case '\\': 915 case ';': 916 case '#': 917 case '=': 918 checkLenient(); // fall-through 919 920 case '{': 921 case '}': 922 case '[': 923 case ']': 924 case ':': 925 case ',': 926 case ' ': 927 case '\t': 928 case '\f': 929 case '\r': 930 case '\n': 931 pos--; 932 if (skipping) { 933 return "skipped!"; 934 } else if (builder == null) { 935 return new String(buffer, start, pos - start); 936 } else { 937 builder.append(buffer, start, pos - start); 938 return builder.toString(); 939 } 940 } 941 } 942 943 if (builder == null) { 944 builder = new StringBuilder(); 945 } 946 builder.append(buffer, start, pos - start); 947 } while (fillBuffer(1)); 948 949 return builder.toString(); 950 } 951 952 @Override public String toString() { 953 return getClass().getSimpleName() + " near " + getSnippet(); 954 } 955 956 /** 957 * Unescapes the character identified by the character or characters that 958 * immediately follow a backslash. The backslash '\' should have already 959 * been read. This supports both unicode escapes "u000A" and two-character 960 * escapes "\n". 961 * 962 * @throws NumberFormatException if any unicode escape sequences are 963 * malformed. 964 */ 965 private char readEscapeCharacter() throws IOException { 966 if (pos == limit && !fillBuffer(1)) { 967 throw syntaxError("Unterminated escape sequence"); 968 } 969 970 char escaped = buffer[pos++]; 971 switch (escaped) { 972 case 'u': 973 if (pos + 4 > limit && !fillBuffer(4)) { 974 throw syntaxError("Unterminated escape sequence"); 975 } 976 String hex = new String(buffer, pos, 4); 977 pos += 4; 978 return (char) Integer.parseInt(hex, 16); 979 980 case 't': 981 return '\t'; 982 983 case 'b': 984 return '\b'; 985 986 case 'n': 987 return '\n'; 988 989 case 'r': 990 return '\r'; 991 992 case 'f': 993 return '\f'; 994 995 case '\'': 996 case '"': 997 case '\\': 998 default: 999 return escaped; 1000 } 1001 } 1002 1003 /** 1004 * Reads a null, boolean, numeric or unquoted string literal value. 1005 */ 1006 private JsonToken readLiteral() throws IOException { 1007 String literal = nextLiteral(); 1008 if (literal.isEmpty()) { 1009 throw syntaxError("Expected literal value"); 1010 } 1011 value = literal; 1012 hasToken = true; 1013 return token = null; // use decodeLiteral() to get the token type 1014 } 1015 1016 /** 1017 * Assigns {@code nextToken} based on the value of {@code nextValue}. 1018 */ 1019 private void decodeLiteral() throws IOException { 1020 if (value.equalsIgnoreCase("null")) { 1021 token = JsonToken.NULL; 1022 } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { 1023 token = JsonToken.BOOLEAN; 1024 } else { 1025 try { 1026 Double.parseDouble(value); // this work could potentially be cached 1027 token = JsonToken.NUMBER; 1028 } catch (NumberFormatException ignored) { 1029 // this must be an unquoted string 1030 checkLenient(); 1031 token = JsonToken.STRING; 1032 } 1033 } 1034 } 1035 1036 /** 1037 * Throws a new IO exception with the given message and a context snippet 1038 * with this reader's content. 1039 */ 1040 public IOException syntaxError(String message) throws IOException { 1041 throw new JsonSyntaxException(message + " near " + getSnippet()); 1042 } 1043 1044 private CharSequence getSnippet() { 1045 StringBuilder snippet = new StringBuilder(); 1046 int beforePos = Math.min(pos, 20); 1047 snippet.append(buffer, pos - beforePos, beforePos); 1048 int afterPos = Math.min(limit - pos, 20); 1049 snippet.append(buffer, pos, afterPos); 1050 return snippet; 1051 } 1052 1053 private static class JsonSyntaxException extends IOException { 1054 private JsonSyntaxException(String s) { 1055 super(s); 1056 } 1057 } 1058} 1059