1 /* 2 * Copyright (C) 2010 Google Inc. 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 17 package com.google.gson.stream; 18 19 import static com.google.gson.stream.JsonScope.DANGLING_NAME; 20 import static com.google.gson.stream.JsonScope.EMPTY_ARRAY; 21 import static com.google.gson.stream.JsonScope.EMPTY_DOCUMENT; 22 import static com.google.gson.stream.JsonScope.EMPTY_OBJECT; 23 import static com.google.gson.stream.JsonScope.NONEMPTY_ARRAY; 24 import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT; 25 import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT; 26 27 import java.io.Closeable; 28 import java.io.Flushable; 29 import java.io.IOException; 30 import java.io.Writer; 31 import java.math.BigDecimal; 32 import java.math.BigInteger; 33 import java.util.Arrays; 34 import java.util.Objects; 35 import java.util.concurrent.atomic.AtomicInteger; 36 import java.util.concurrent.atomic.AtomicLong; 37 import java.util.regex.Pattern; 38 39 /** 40 * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>) 41 * encoded value to a stream, one token at a time. The stream includes both 42 * literal values (strings, numbers, booleans and nulls) as well as the begin 43 * and end delimiters of objects and arrays. 44 * 45 * <h2>Encoding JSON</h2> 46 * To encode your data as JSON, create a new {@code JsonWriter}. Call methods 47 * on the writer as you walk the structure's contents, nesting arrays and objects 48 * as necessary: 49 * <ul> 50 * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. 51 * Write each of the array's elements with the appropriate {@link #value} 52 * methods or by nesting other arrays and objects. Finally close the array 53 * using {@link #endArray()}. 54 * <li>To write <strong>objects</strong>, first call {@link #beginObject()}. 55 * Write each of the object's properties by alternating calls to 56 * {@link #name} with the property's value. Write property values with the 57 * appropriate {@link #value} method or by nesting other objects or arrays. 58 * Finally close the object using {@link #endObject()}. 59 * </ul> 60 * 61 * <h2>Example</h2> 62 * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code 63 * [ 64 * { 65 * "id": 912345678901, 66 * "text": "How do I stream JSON in Java?", 67 * "geo": null, 68 * "user": { 69 * "name": "json_newb", 70 * "followers_count": 41 71 * } 72 * }, 73 * { 74 * "id": 912345678902, 75 * "text": "@json_newb just use JsonWriter!", 76 * "geo": [50.454722, -104.606667], 77 * "user": { 78 * "name": "jesse", 79 * "followers_count": 2 80 * } 81 * } 82 * ]}</pre> 83 * This code encodes the above structure: <pre> {@code 84 * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException { 85 * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); 86 * writer.setIndent(" "); 87 * writeMessagesArray(writer, messages); 88 * writer.close(); 89 * } 90 * 91 * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException { 92 * writer.beginArray(); 93 * for (Message message : messages) { 94 * writeMessage(writer, message); 95 * } 96 * writer.endArray(); 97 * } 98 * 99 * public void writeMessage(JsonWriter writer, Message message) throws IOException { 100 * writer.beginObject(); 101 * writer.name("id").value(message.getId()); 102 * writer.name("text").value(message.getText()); 103 * if (message.getGeo() != null) { 104 * writer.name("geo"); 105 * writeDoublesArray(writer, message.getGeo()); 106 * } else { 107 * writer.name("geo").nullValue(); 108 * } 109 * writer.name("user"); 110 * writeUser(writer, message.getUser()); 111 * writer.endObject(); 112 * } 113 * 114 * public void writeUser(JsonWriter writer, User user) throws IOException { 115 * writer.beginObject(); 116 * writer.name("name").value(user.getName()); 117 * writer.name("followers_count").value(user.getFollowersCount()); 118 * writer.endObject(); 119 * } 120 * 121 * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException { 122 * writer.beginArray(); 123 * for (Double value : doubles) { 124 * writer.value(value); 125 * } 126 * writer.endArray(); 127 * }}</pre> 128 * 129 * <p>Each {@code JsonWriter} may be used to write a single JSON stream. 130 * Instances of this class are not thread safe. Calls that would result in a 131 * malformed JSON string will fail with an {@link IllegalStateException}. 132 * 133 * @author Jesse Wilson 134 * @since 1.6 135 */ 136 public class JsonWriter implements Closeable, Flushable { 137 138 // Syntax as defined by https://datatracker.ietf.org/doc/html/rfc8259#section-6 139 private static final Pattern VALID_JSON_NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?"); 140 141 /* 142 * From RFC 7159, "All Unicode characters may be placed within the 143 * quotation marks except for the characters that must be escaped: 144 * quotation mark, reverse solidus, and the control characters 145 * (U+0000 through U+001F)." 146 * 147 * We also escape '\u2028' and '\u2029', which JavaScript interprets as 148 * newline characters. This prevents eval() from failing with a syntax 149 * error. http://code.google.com/p/google-gson/issues/detail?id=341 150 */ 151 private static final String[] REPLACEMENT_CHARS; 152 private static final String[] HTML_SAFE_REPLACEMENT_CHARS; 153 static { 154 REPLACEMENT_CHARS = new String[128]; 155 for (int i = 0; i <= 0x1f; i++) { 156 REPLACEMENT_CHARS[i] = String.format("\\u%04x", i); 157 } 158 REPLACEMENT_CHARS['"'] = "\\\""; 159 REPLACEMENT_CHARS['\\'] = "\\\\"; 160 REPLACEMENT_CHARS['\t'] = "\\t"; 161 REPLACEMENT_CHARS['\b'] = "\\b"; 162 REPLACEMENT_CHARS['\n'] = "\\n"; 163 REPLACEMENT_CHARS['\r'] = "\\r"; 164 REPLACEMENT_CHARS['\f'] = "\\f"; 165 HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone(); 166 HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c"; 167 HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e"; 168 HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026"; 169 HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d"; 170 HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027"; 171 } 172 173 /** The JSON output destination */ 174 private final Writer out; 175 176 private int[] stack = new int[32]; 177 private int stackSize = 0; 178 { 179 push(EMPTY_DOCUMENT); 180 } 181 182 /** 183 * A string containing a full set of spaces for a single level of 184 * indentation, or null for no pretty printing. 185 */ 186 private String indent; 187 188 /** 189 * The name/value separator; either ":" or ": ". 190 */ 191 private String separator = ":"; 192 193 private boolean lenient; 194 195 private boolean htmlSafe; 196 197 private String deferredName; 198 199 private boolean serializeNulls = true; 200 201 /** 202 * Creates a new instance that writes a JSON-encoded stream to {@code out}. 203 * For best performance, ensure {@link Writer} is buffered; wrapping in 204 * {@link java.io.BufferedWriter BufferedWriter} if necessary. 205 */ JsonWriter(Writer out)206 public JsonWriter(Writer out) { 207 this.out = Objects.requireNonNull(out, "out == null"); 208 } 209 210 /** 211 * Sets the indentation string to be repeated for each level of indentation 212 * in the encoded document. If {@code indent.isEmpty()} the encoded document 213 * will be compact. Otherwise the encoded document will be more 214 * human-readable. 215 * 216 * @param indent a string containing only whitespace. 217 */ setIndent(String indent)218 public final void setIndent(String indent) { 219 if (indent.length() == 0) { 220 this.indent = null; 221 this.separator = ":"; 222 } else { 223 this.indent = indent; 224 this.separator = ": "; 225 } 226 } 227 228 /** 229 * Configure this writer to relax its syntax rules. By default, this writer 230 * only emits well-formed JSON as specified by <a 231 * href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer 232 * to lenient permits the following: 233 * <ul> 234 * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link 235 * Double#isInfinite() infinities}. 236 * </ul> 237 */ setLenient(boolean lenient)238 public final void setLenient(boolean lenient) { 239 this.lenient = lenient; 240 } 241 242 /** 243 * Returns true if this writer has relaxed syntax rules. 244 */ isLenient()245 public boolean isLenient() { 246 return lenient; 247 } 248 249 /** 250 * Configure this writer to emit JSON that's safe for direct inclusion in HTML 251 * and XML documents. This escapes the HTML characters {@code <}, {@code >}, 252 * {@code &} and {@code =} before writing them to the stream. Without this 253 * setting, your XML/HTML encoder should replace these characters with the 254 * corresponding escape sequences. 255 */ setHtmlSafe(boolean htmlSafe)256 public final void setHtmlSafe(boolean htmlSafe) { 257 this.htmlSafe = htmlSafe; 258 } 259 260 /** 261 * Returns true if this writer writes JSON that's safe for inclusion in HTML 262 * and XML documents. 263 */ isHtmlSafe()264 public final boolean isHtmlSafe() { 265 return htmlSafe; 266 } 267 268 /** 269 * Sets whether object members are serialized when their value is null. 270 * This has no impact on array elements. The default is true. 271 */ setSerializeNulls(boolean serializeNulls)272 public final void setSerializeNulls(boolean serializeNulls) { 273 this.serializeNulls = serializeNulls; 274 } 275 276 /** 277 * Returns true if object members are serialized when their value is null. 278 * This has no impact on array elements. The default is true. 279 */ getSerializeNulls()280 public final boolean getSerializeNulls() { 281 return serializeNulls; 282 } 283 284 /** 285 * Begins encoding a new array. Each call to this method must be paired with 286 * a call to {@link #endArray}. 287 * 288 * @return this writer. 289 */ beginArray()290 public JsonWriter beginArray() throws IOException { 291 writeDeferredName(); 292 return open(EMPTY_ARRAY, '['); 293 } 294 295 /** 296 * Ends encoding the current array. 297 * 298 * @return this writer. 299 */ endArray()300 public JsonWriter endArray() throws IOException { 301 return close(EMPTY_ARRAY, NONEMPTY_ARRAY, ']'); 302 } 303 304 /** 305 * Begins encoding a new object. Each call to this method must be paired 306 * with a call to {@link #endObject}. 307 * 308 * @return this writer. 309 */ beginObject()310 public JsonWriter beginObject() throws IOException { 311 writeDeferredName(); 312 return open(EMPTY_OBJECT, '{'); 313 } 314 315 /** 316 * Ends encoding the current object. 317 * 318 * @return this writer. 319 */ endObject()320 public JsonWriter endObject() throws IOException { 321 return close(EMPTY_OBJECT, NONEMPTY_OBJECT, '}'); 322 } 323 324 /** 325 * Enters a new scope by appending any necessary whitespace and the given 326 * bracket. 327 */ open(int empty, char openBracket)328 private JsonWriter open(int empty, char openBracket) throws IOException { 329 beforeValue(); 330 push(empty); 331 out.write(openBracket); 332 return this; 333 } 334 335 /** 336 * Closes the current scope by appending any necessary whitespace and the 337 * given bracket. 338 */ close(int empty, int nonempty, char closeBracket)339 private JsonWriter close(int empty, int nonempty, char closeBracket) 340 throws IOException { 341 int context = peek(); 342 if (context != nonempty && context != empty) { 343 throw new IllegalStateException("Nesting problem."); 344 } 345 if (deferredName != null) { 346 throw new IllegalStateException("Dangling name: " + deferredName); 347 } 348 349 stackSize--; 350 if (context == nonempty) { 351 newline(); 352 } 353 out.write(closeBracket); 354 return this; 355 } 356 push(int newTop)357 private void push(int newTop) { 358 if (stackSize == stack.length) { 359 stack = Arrays.copyOf(stack, stackSize * 2); 360 } 361 stack[stackSize++] = newTop; 362 } 363 364 /** 365 * Returns the value on the top of the stack. 366 */ peek()367 private int peek() { 368 if (stackSize == 0) { 369 throw new IllegalStateException("JsonWriter is closed."); 370 } 371 return stack[stackSize - 1]; 372 } 373 374 /** 375 * Replace the value on the top of the stack with the given value. 376 */ replaceTop(int topOfStack)377 private void replaceTop(int topOfStack) { 378 stack[stackSize - 1] = topOfStack; 379 } 380 381 /** 382 * Encodes the property name. 383 * 384 * @param name the name of the forthcoming value. May not be null. 385 * @return this writer. 386 */ name(String name)387 public JsonWriter name(String name) throws IOException { 388 Objects.requireNonNull(name, "name == null"); 389 if (deferredName != null) { 390 throw new IllegalStateException(); 391 } 392 if (stackSize == 0) { 393 throw new IllegalStateException("JsonWriter is closed."); 394 } 395 deferredName = name; 396 return this; 397 } 398 writeDeferredName()399 private void writeDeferredName() throws IOException { 400 if (deferredName != null) { 401 beforeName(); 402 string(deferredName); 403 deferredName = null; 404 } 405 } 406 407 /** 408 * Encodes {@code value}. 409 * 410 * @param value the literal string value, or null to encode a null literal. 411 * @return this writer. 412 */ value(String value)413 public JsonWriter value(String value) throws IOException { 414 if (value == null) { 415 return nullValue(); 416 } 417 writeDeferredName(); 418 beforeValue(); 419 string(value); 420 return this; 421 } 422 423 /** 424 * Writes {@code value} directly to the writer without quoting or 425 * escaping. This might not be supported by all implementations, if 426 * not supported an {@code UnsupportedOperationException} is thrown. 427 * 428 * @param value the literal string value, or null to encode a null literal. 429 * @return this writer. 430 * @throws UnsupportedOperationException if this writer does not support 431 * writing raw JSON values. 432 * @since 2.4 433 */ jsonValue(String value)434 public JsonWriter jsonValue(String value) throws IOException { 435 if (value == null) { 436 return nullValue(); 437 } 438 writeDeferredName(); 439 beforeValue(); 440 out.append(value); 441 return this; 442 } 443 444 /** 445 * Encodes {@code null}. 446 * 447 * @return this writer. 448 */ nullValue()449 public JsonWriter nullValue() throws IOException { 450 if (deferredName != null) { 451 if (serializeNulls) { 452 writeDeferredName(); 453 } else { 454 deferredName = null; 455 return this; // skip the name and the value 456 } 457 } 458 beforeValue(); 459 out.write("null"); 460 return this; 461 } 462 463 /** 464 * Encodes {@code value}. 465 * 466 * @return this writer. 467 */ value(boolean value)468 public JsonWriter value(boolean value) throws IOException { 469 writeDeferredName(); 470 beforeValue(); 471 out.write(value ? "true" : "false"); 472 return this; 473 } 474 475 /** 476 * Encodes {@code value}. 477 * 478 * @return this writer. 479 * @since 2.7 480 */ value(Boolean value)481 public JsonWriter value(Boolean value) throws IOException { 482 if (value == null) { 483 return nullValue(); 484 } 485 writeDeferredName(); 486 beforeValue(); 487 out.write(value ? "true" : "false"); 488 return this; 489 } 490 491 /** 492 * Encodes {@code value}. 493 * 494 * @param value a finite value, or if {@link #setLenient(boolean) lenient}, 495 * also {@link Float#isNaN() NaN} or {@link Float#isInfinite() 496 * infinity}. 497 * @return this writer. 498 * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link 499 * #setLenient(boolean) lenient}. 500 * @since 2.9.1 501 */ value(float value)502 public JsonWriter value(float value) throws IOException { 503 writeDeferredName(); 504 if (!lenient && (Float.isNaN(value) || Float.isInfinite(value))) { 505 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 506 } 507 beforeValue(); 508 out.append(Float.toString(value)); 509 return this; 510 } 511 512 /** 513 * Encodes {@code value}. 514 * 515 * @param value a finite value, or if {@link #setLenient(boolean) lenient}, 516 * also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}. 517 * @return this writer. 518 * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is 519 * not {@link #setLenient(boolean) lenient}. 520 */ value(double value)521 public JsonWriter value(double value) throws IOException { 522 writeDeferredName(); 523 if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) { 524 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 525 } 526 beforeValue(); 527 out.append(Double.toString(value)); 528 return this; 529 } 530 531 /** 532 * Encodes {@code value}. 533 * 534 * @return this writer. 535 */ value(long value)536 public JsonWriter value(long value) throws IOException { 537 writeDeferredName(); 538 beforeValue(); 539 out.write(Long.toString(value)); 540 return this; 541 } 542 543 /** 544 * Returns whether the {@code toString()} of {@code c} can be trusted to return 545 * a valid JSON number. 546 */ isTrustedNumberType(Class<? extends Number> c)547 private static boolean isTrustedNumberType(Class<? extends Number> c) { 548 // Note: Don't consider LazilyParsedNumber trusted because it could contain 549 // an arbitrary malformed string 550 return c == Integer.class || c == Long.class || c == Double.class || c == Float.class || c == Byte.class || c == Short.class 551 || c == BigDecimal.class || c == BigInteger.class || c == AtomicInteger.class || c == AtomicLong.class; 552 } 553 554 /** 555 * Encodes {@code value}. The value is written by directly writing the {@link Number#toString()} 556 * result to JSON. Implementations must make sure that the result represents a valid JSON number. 557 * 558 * @param value a finite value, or if {@link #setLenient(boolean) lenient}, 559 * also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}. 560 * @return this writer. 561 * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is 562 * not {@link #setLenient(boolean) lenient}; or if the {@code toString()} result is not a 563 * valid JSON number. 564 */ value(Number value)565 public JsonWriter value(Number value) throws IOException { 566 if (value == null) { 567 return nullValue(); 568 } 569 570 writeDeferredName(); 571 String string = value.toString(); 572 if (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN")) { 573 if (!lenient) { 574 throw new IllegalArgumentException("Numeric values must be finite, but was " + string); 575 } 576 } else { 577 Class<? extends Number> numberClass = value.getClass(); 578 // Validate that string is valid before writing it directly to JSON output 579 if (!isTrustedNumberType(numberClass) && !VALID_JSON_NUMBER_PATTERN.matcher(string).matches()) { 580 throw new IllegalArgumentException("String created by " + numberClass + " is not a valid JSON number: " + string); 581 } 582 } 583 584 beforeValue(); 585 out.append(string); 586 return this; 587 } 588 589 /** 590 * Ensures all buffered data is written to the underlying {@link Writer} 591 * and flushes that writer. 592 */ flush()593 @Override public void flush() throws IOException { 594 if (stackSize == 0) { 595 throw new IllegalStateException("JsonWriter is closed."); 596 } 597 out.flush(); 598 } 599 600 /** 601 * Flushes and closes this writer and the underlying {@link Writer}. 602 * 603 * @throws IOException if the JSON document is incomplete. 604 */ close()605 @Override public void close() throws IOException { 606 out.close(); 607 608 int size = stackSize; 609 if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) { 610 throw new IOException("Incomplete document"); 611 } 612 stackSize = 0; 613 } 614 string(String value)615 private void string(String value) throws IOException { 616 String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS; 617 out.write('\"'); 618 int last = 0; 619 int length = value.length(); 620 for (int i = 0; i < length; i++) { 621 char c = value.charAt(i); 622 String replacement; 623 if (c < 128) { 624 replacement = replacements[c]; 625 if (replacement == null) { 626 continue; 627 } 628 } else if (c == '\u2028') { 629 replacement = "\\u2028"; 630 } else if (c == '\u2029') { 631 replacement = "\\u2029"; 632 } else { 633 continue; 634 } 635 if (last < i) { 636 out.write(value, last, i - last); 637 } 638 out.write(replacement); 639 last = i + 1; 640 } 641 if (last < length) { 642 out.write(value, last, length - last); 643 } 644 out.write('\"'); 645 } 646 newline()647 private void newline() throws IOException { 648 if (indent == null) { 649 return; 650 } 651 652 out.write('\n'); 653 for (int i = 1, size = stackSize; i < size; i++) { 654 out.write(indent); 655 } 656 } 657 658 /** 659 * Inserts any necessary separators and whitespace before a name. Also 660 * adjusts the stack to expect the name's value. 661 */ beforeName()662 private void beforeName() throws IOException { 663 int context = peek(); 664 if (context == NONEMPTY_OBJECT) { // first in object 665 out.write(','); 666 } else if (context != EMPTY_OBJECT) { // not in an object! 667 throw new IllegalStateException("Nesting problem."); 668 } 669 newline(); 670 replaceTop(DANGLING_NAME); 671 } 672 673 /** 674 * Inserts any necessary separators and whitespace before a literal value, 675 * inline array, or inline object. Also adjusts the stack to expect either a 676 * closing bracket or another element. 677 */ 678 @SuppressWarnings("fallthrough") beforeValue()679 private void beforeValue() throws IOException { 680 switch (peek()) { 681 case NONEMPTY_DOCUMENT: 682 if (!lenient) { 683 throw new IllegalStateException( 684 "JSON must have only one top-level value."); 685 } 686 // fall-through 687 case EMPTY_DOCUMENT: // first in document 688 replaceTop(NONEMPTY_DOCUMENT); 689 break; 690 691 case EMPTY_ARRAY: // first in array 692 replaceTop(NONEMPTY_ARRAY); 693 newline(); 694 break; 695 696 case NONEMPTY_ARRAY: // another in array 697 out.append(','); 698 newline(); 699 break; 700 701 case DANGLING_NAME: // value for name 702 out.append(separator); 703 replaceTop(NONEMPTY_OBJECT); 704 break; 705 706 default: 707 throw new IllegalStateException("Nesting problem."); 708 } 709 } 710 } 711