• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package org.json;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.List;
23 
24 // Note: this class was written without inspecting the non-free org.json sourcecode.
25 
26 /**
27  * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most
28  * application developers should use those methods directly and disregard this
29  * API. For example:<pre>
30  * JSONObject object = ...
31  * String json = object.toString();</pre>
32  *
33  * <p>Stringers only encode well-formed JSON strings. In particular:
34  * <ul>
35  *   <li>The stringer must have exactly one top-level array or object.
36  *   <li>Lexical scopes must be balanced: every call to {@link #array} must
37  *       have a matching call to {@link #endArray} and every call to {@link
38  *       #object} must have a matching call to {@link #endObject}.
39  *   <li>Arrays may not contain keys (property names).
40  *   <li>Objects must alternate keys (property names) and values.
41  *   <li>Values are inserted with either literal {@link #value(Object) value}
42  *       calls, or by nesting arrays or objects.
43  * </ul>
44  * Calls that would result in a malformed JSON string will fail with a
45  * {@link JSONException}.
46  *
47  * <p>This class provides no facility for pretty-printing (ie. indenting)
48  * output. To encode indented output, use {@link JSONObject#toString(int)} or
49  * {@link JSONArray#toString(int)}.
50  *
51  * <p>Some implementations of the API support at most 20 levels of nesting.
52  * Attempts to create more than 20 levels of nesting may fail with a {@link
53  * JSONException}.
54  *
55  * <p>Each stringer may be used to encode a single top level value. Instances of
56  * this class are not thread safe. Although this class is nonfinal, it was not
57  * designed for inheritance and should not be subclassed. In particular,
58  * self-use by overrideable methods is not specified. See <i>Effective Java</i>
59  * Item 17, "Design and Document or inheritance or else prohibit it" for further
60  * information.
61  */
62 public class JSONStringer {
63 
64     /** The output data, containing at most one top-level array or object. */
65     @UnsupportedAppUsage
66     final StringBuilder out = new StringBuilder();
67 
68     /**
69      * Lexical scoping elements within this stringer, necessary to insert the
70      * appropriate separator characters (ie. commas and colons) and to detect
71      * nesting errors.
72      */
73     enum Scope {
74 
75         /**
76          * An array with no elements requires no separators or newlines before
77          * it is closed.
78          */
79         EMPTY_ARRAY,
80 
81         /**
82          * A array with at least one value requires a comma and newline before
83          * the next element.
84          */
85         NONEMPTY_ARRAY,
86 
87         /**
88          * An object with no keys or values requires no separators or newlines
89          * before it is closed.
90          */
91         EMPTY_OBJECT,
92 
93         /**
94          * An object whose most recent element is a key. The next element must
95          * be a value.
96          */
97         DANGLING_KEY,
98 
99         /**
100          * An object with at least one name/value pair requires a comma and
101          * newline before the next element.
102          */
103         NONEMPTY_OBJECT,
104 
105         /**
106          * A special bracketless array needed by JSONStringer.join() and
107          * JSONObject.quote() only. Not used for JSON encoding.
108          */
109         NULL,
110     }
111 
112     /**
113      * Unlike the original implementation, this stack isn't limited to 20
114      * levels of nesting.
115      */
116     @UnsupportedAppUsage
117     private final List<Scope> stack = new ArrayList<Scope>();
118 
119     /**
120      * A string containing a full set of spaces for a single level of
121      * indentation, or null for no pretty printing.
122      */
123     @UnsupportedAppUsage
124     private final String indent;
125 
JSONStringer()126     public JSONStringer() {
127         indent = null;
128     }
129 
130     @UnsupportedAppUsage
JSONStringer(int indentSpaces)131     JSONStringer(int indentSpaces) {
132         char[] indentChars = new char[indentSpaces];
133         Arrays.fill(indentChars, ' ');
134         indent = new String(indentChars);
135     }
136 
137     /**
138      * Begins encoding a new array. Each call to this method must be paired with
139      * a call to {@link #endArray}.
140      *
141      * @return this stringer.
142      */
array()143     public JSONStringer array() throws JSONException {
144         return open(Scope.EMPTY_ARRAY, "[");
145     }
146 
147     /**
148      * Ends encoding the current array.
149      *
150      * @return this stringer.
151      */
endArray()152     public JSONStringer endArray() throws JSONException {
153         return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
154     }
155 
156     /**
157      * Begins encoding a new object. Each call to this method must be paired
158      * with a call to {@link #endObject}.
159      *
160      * @return this stringer.
161      */
object()162     public JSONStringer object() throws JSONException {
163         return open(Scope.EMPTY_OBJECT, "{");
164     }
165 
166     /**
167      * Ends encoding the current object.
168      *
169      * @return this stringer.
170      */
endObject()171     public JSONStringer endObject() throws JSONException {
172         return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
173     }
174 
175     /**
176      * Enters a new scope by appending any necessary whitespace and the given
177      * bracket.
178      */
179     @UnsupportedAppUsage
open(Scope empty, String openBracket)180     JSONStringer open(Scope empty, String openBracket) throws JSONException {
181         if (stack.isEmpty() && out.length() > 0) {
182             throw new JSONException("Nesting problem: multiple top-level roots");
183         }
184         beforeValue();
185         stack.add(empty);
186         out.append(openBracket);
187         return this;
188     }
189 
190     /**
191      * Closes the current scope by appending any necessary whitespace and the
192      * given bracket.
193      */
194     @UnsupportedAppUsage
close(Scope empty, Scope nonempty, String closeBracket)195     JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
196         Scope context = peek();
197         if (context != nonempty && context != empty) {
198             throw new JSONException("Nesting problem");
199         }
200 
201         stack.remove(stack.size() - 1);
202         if (context == nonempty) {
203             newline();
204         }
205         out.append(closeBracket);
206         return this;
207     }
208 
209     /**
210      * Returns the value on the top of the stack.
211      */
212     @UnsupportedAppUsage
peek()213     private Scope peek() throws JSONException {
214         if (stack.isEmpty()) {
215             throw new JSONException("Nesting problem");
216         }
217         return stack.get(stack.size() - 1);
218     }
219 
220     /**
221      * Replace the value on the top of the stack with the given value.
222      */
223     @UnsupportedAppUsage
replaceTop(Scope topOfStack)224     private void replaceTop(Scope topOfStack) {
225         stack.set(stack.size() - 1, topOfStack);
226     }
227 
228     /**
229      * Encodes {@code value}.
230      *
231      * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
232      *     Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
233      *     or {@link Double#isInfinite() infinities}.
234      * @return this stringer.
235      */
value(Object value)236     public JSONStringer value(Object value) throws JSONException {
237         if (stack.isEmpty()) {
238             throw new JSONException("Nesting problem");
239         }
240 
241         if (value instanceof JSONArray) {
242             ((JSONArray) value).writeTo(this);
243             return this;
244 
245         } else if (value instanceof JSONObject) {
246             ((JSONObject) value).writeTo(this);
247             return this;
248         }
249 
250         beforeValue();
251 
252         if (value == null
253                 || value instanceof Boolean
254                 || value == JSONObject.NULL) {
255             out.append(value);
256 
257         } else if (value instanceof Number) {
258             out.append(JSONObject.numberToString((Number) value));
259 
260         } else {
261             string(value.toString());
262         }
263 
264         return this;
265     }
266 
267     /**
268      * Encodes {@code value} to this stringer.
269      *
270      * @return this stringer.
271      */
value(boolean value)272     public JSONStringer value(boolean value) throws JSONException {
273         if (stack.isEmpty()) {
274             throw new JSONException("Nesting problem");
275         }
276         beforeValue();
277         out.append(value);
278         return this;
279     }
280 
281     /**
282      * Encodes {@code value} to this stringer.
283      *
284      * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
285      *     {@link Double#isInfinite() infinities}.
286      * @return this stringer.
287      */
value(double value)288     public JSONStringer value(double value) throws JSONException {
289         if (stack.isEmpty()) {
290             throw new JSONException("Nesting problem");
291         }
292         beforeValue();
293         out.append(JSONObject.numberToString(value));
294         return this;
295     }
296 
297     /**
298      * Encodes {@code value} to this stringer.
299      *
300      * @return this stringer.
301      */
value(long value)302     public JSONStringer value(long value) throws JSONException {
303         if (stack.isEmpty()) {
304             throw new JSONException("Nesting problem");
305         }
306         beforeValue();
307         out.append(value);
308         return this;
309     }
310 
311     @UnsupportedAppUsage
string(String value)312     private void string(String value) {
313         out.append("\"");
314         for (int i = 0, length = value.length(); i < length; i++) {
315             char c = value.charAt(i);
316 
317             /*
318              * From RFC 4627, "All Unicode characters may be placed within the
319              * quotation marks except for the characters that must be escaped:
320              * quotation mark, reverse solidus, and the control characters
321              * (U+0000 through U+001F)."
322              */
323             switch (c) {
324                 case '"':
325                 case '\\':
326                 case '/':
327                     out.append('\\').append(c);
328                     break;
329 
330                 case '\t':
331                     out.append("\\t");
332                     break;
333 
334                 case '\b':
335                     out.append("\\b");
336                     break;
337 
338                 case '\n':
339                     out.append("\\n");
340                     break;
341 
342                 case '\r':
343                     out.append("\\r");
344                     break;
345 
346                 case '\f':
347                     out.append("\\f");
348                     break;
349 
350                 default:
351                     if (c <= 0x1F) {
352                         out.append(String.format("\\u%04x", (int) c));
353                     } else {
354                         out.append(c);
355                     }
356                     break;
357             }
358 
359         }
360         out.append("\"");
361     }
362 
363     @UnsupportedAppUsage
newline()364     private void newline() {
365         if (indent == null) {
366             return;
367         }
368 
369         out.append("\n");
370         for (int i = 0; i < stack.size(); i++) {
371             out.append(indent);
372         }
373     }
374 
375     /**
376      * Encodes the key (property name) to this stringer.
377      *
378      * @param name the name of the forthcoming value. May not be null.
379      * @return this stringer.
380      */
key(String name)381     public JSONStringer key(String name) throws JSONException {
382         if (name == null) {
383             throw new JSONException("Names must be non-null");
384         }
385         beforeKey();
386         string(name);
387         return this;
388     }
389 
390     /**
391      * Inserts any necessary separators and whitespace before a name. Also
392      * adjusts the stack to expect the key's value.
393      */
394     @UnsupportedAppUsage
beforeKey()395     private void beforeKey() throws JSONException {
396         Scope context = peek();
397         if (context == Scope.NONEMPTY_OBJECT) { // first in object
398             out.append(',');
399         } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
400             throw new JSONException("Nesting problem");
401         }
402         newline();
403         replaceTop(Scope.DANGLING_KEY);
404     }
405 
406     /**
407      * Inserts any necessary separators and whitespace before a literal value,
408      * inline array, or inline object. Also adjusts the stack to expect either a
409      * closing bracket or another element.
410      */
411     @UnsupportedAppUsage
beforeValue()412     private void beforeValue() throws JSONException {
413         if (stack.isEmpty()) {
414             return;
415         }
416 
417         Scope context = peek();
418         if (context == Scope.EMPTY_ARRAY) { // first in array
419             replaceTop(Scope.NONEMPTY_ARRAY);
420             newline();
421         } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
422             out.append(',');
423             newline();
424         } else if (context == Scope.DANGLING_KEY) { // value for key
425             out.append(indent == null ? ":" : ": ");
426             replaceTop(Scope.NONEMPTY_OBJECT);
427         } else if (context != Scope.NULL) {
428             throw new JSONException("Nesting problem");
429         }
430     }
431 
432     /**
433      * Returns the encoded JSON string.
434      *
435      * <p>If invoked with unterminated arrays or unclosed objects, this method's
436      * return value is undefined.
437      *
438      * <p><strong>Warning:</strong> although it contradicts the general contract
439      * of {@link Object#toString}, this method returns null if the stringer
440      * contains no data.
441      */
toString()442     @Override public String toString() {
443         return out.length() == 0 ? null : out.toString();
444     }
445 }
446