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