• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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