2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
23 // Note: this class was written without inspecting the non-free org.json sourcecode.
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>
32 * <p>Stringers only encode well-formed JSON strings. In particular:
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.
43 * Calls that would result in a malformed JSON string will fail with a
44 * {@link JSONException}.
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)}.
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
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 overridable methods is not specified. See <i>Effective Java</i>
58 * Item 17, "Design and Document or inheritance or else prohibit it" for further
61 public class JSONStringer {
63 /** The output data, containing at most one top-level array or object. */
64 final StringBuilder out = new StringBuilder();
67 * Lexical scoping elements within this stringer, necessary to insert the
68 * appropriate separator characters (ie. commas and colons) and to detect
74 * An array with no elements requires no separators or newlines before
80 * A array with at least one value requires a comma and newline before
86 * An object with no keys or values requires no separators or newlines
87 * before it is closed.
92 * An object whose most recent element is a key. The next element must
98 * An object with at least one name/value pair requires a comma and
99 * newline before the next element.
104 * A special bracketless array needed by JSONStringer.join() and
105 * JSONObject.quote() only. Not used for JSON encoding.
111 * Unlike the original implementation, this stack isn't limited to 20
114 private final List<Scope> stack = new ArrayList<Scope>();
117 * A string containing a full set of spaces for a single level of
118 * indentation, or null for no pretty printing.
120 private final String indent;
122 public JSONStringer() {
126 JSONStringer(int indentSpaces) {
127 char[] indentChars = new char[indentSpaces];
128 Arrays.fill(indentChars, ' ');
129 indent = new String(indentChars);
133 * Begins encoding a new array. Each call to this method must be paired with
134 * a call to {@link #endArray}.
136 * @return this stringer.
138 public JSONStringer array() throws JSONException {
139 return open(Scope.EMPTY_ARRAY, "[");
143 * Ends encoding the current array.
145 * @return this stringer.
147 public JSONStringer endArray() throws JSONException {
148 return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
152 * Begins encoding a new object. Each call to this method must be paired
153 * with a call to {@link #endObject}.
155 * @return this stringer.
157 public JSONStringer object() throws JSONException {
158 return open(Scope.EMPTY_OBJECT, "{");
162 * Ends encoding the current object.
164 * @return this stringer.
166 public JSONStringer endObject() throws JSONException {
167 return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
171 * Enters a new scope by appending any necessary whitespace and the given
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");
180 out.append(openBracket);
185 * Closes the current scope by appending any necessary whitespace and the
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");
194 stack.remove(stack.size() - 1);
195 if (context == nonempty) {
198 out.append(closeBracket);
203 * Returns the value on the top of the stack.
205 private Scope peek() throws JSONException {
206 if (stack.isEmpty()) {
207 throw new JSONException("Nesting problem");
209 return stack.get(stack.size() - 1);
213 * Replace the value on the top of the stack with the given value.
215 private void replaceTop(Scope topOfStack) {
216 stack.set(stack.size() - 1, topOfStack);
220 * Encodes {@code value}.
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.
227 public JSONStringer value(Object value) throws JSONException {
228 if (stack.isEmpty()) {
229 throw new JSONException("Nesting problem");
232 if (value instanceof JSONArray) {
233 ((JSONArray) value).writeTo(this);
236 } else if (value instanceof JSONObject) {
237 ((JSONObject) value).writeTo(this);
244 || value instanceof Boolean
245 || value == JSONObject.NULL) {
248 } else if (value instanceof Number) {
249 out.append(JSONObject.numberToString((Number) value));
252 string(value.toString());
259 * Encodes {@code value} to this stringer.
261 * @return this stringer.
263 public JSONStringer value(boolean value) throws JSONException {
264 if (stack.isEmpty()) {
265 throw new JSONException("Nesting problem");
273 * Encodes {@code value} to this stringer.
275 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
276 * {@link Double#isInfinite() infinities}.
277 * @return this stringer.
279 public JSONStringer value(double value) throws JSONException {
280 if (stack.isEmpty()) {
281 throw new JSONException("Nesting problem");
284 out.append(JSONObject.numberToString(value));
289 * Encodes {@code value} to this stringer.
291 * @return this stringer.
293 public JSONStringer value(long value) throws JSONException {
294 if (stack.isEmpty()) {
295 throw new JSONException("Nesting problem");
302 private void string(String value) {
304 for (int i = 0, length = value.length(); i < length; i++) {
305 char c = value.charAt(i);
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)."
317 out.append('\\').append(c);
342 out.append(String.format("\\u%04x", (int) c));
353 private void newline() {
354 if (indent == null) {
359 for (int i = 0; i < stack.size(); i++) {
365 * Encodes the key (property name) to this stringer.
367 * @param name the name of the forthcoming value. May not be null.
368 * @return this stringer.
370 public JSONStringer key(String name) throws JSONException {
372 throw new JSONException("Names must be non-null");
380 * Inserts any necessary separators and whitespace before a name. Also
381 * adjusts the stack to expect the key's value.
383 private void beforeKey() throws JSONException {
384 Scope context = peek();
385 if (context == Scope.NONEMPTY_OBJECT) { // first in object
387 } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
388 throw new JSONException("Nesting problem");
391 replaceTop(Scope.DANGLING_KEY);
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.
399 private void beforeValue() throws JSONException {
400 if (stack.isEmpty()) {
404 Scope context = peek();
405 if (context == Scope.EMPTY_ARRAY) { // first in array
406 replaceTop(Scope.NONEMPTY_ARRAY);
408 } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
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");
420 * Returns the encoded JSON string.
422 * <p>If invoked with unterminated arrays or unclosed objects, this method's
423 * return value is undefined.
425 * <p><strong>Warning:</strong> although it contradicts the general contract
426 * of {@link Object#toString}, this method returns null if the stringer
429 @Override public String toString() {
430 return out.length() == 0 ? null : out.toString();