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