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 android.util; 18 19 import java.io.Closeable; 20 import java.io.IOException; 21 import java.io.Writer; 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) 27 * encoded value to a stream, one token at a time. The stream includes both 28 * literal values (strings, numbers, booleans and nulls) as well as the begin 29 * and end delimiters of objects and arrays. 30 * 31 * <h3>Encoding JSON</h3> 32 * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON 33 * document must contain one top-level array or object. Call methods on the 34 * writer as you walk the structure's contents, nesting arrays and objects as 35 * necessary: 36 * <ul> 37 * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. 38 * Write each of the array's elements with the appropriate {@link #value} 39 * methods or by nesting other arrays and objects. Finally close the array 40 * using {@link #endArray()}. 41 * <li>To write <strong>objects</strong>, first call {@link #beginObject()}. 42 * Write each of the object's properties by alternating calls to 43 * {@link #name} with the property's value. Write property values with the 44 * appropriate {@link #value} method or by nesting other objects or arrays. 45 * Finally close the object using {@link #endObject()}. 46 * </ul> 47 * 48 * <h3>Example</h3> 49 * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code 50 * [ 51 * { 52 * "id": 912345678901, 53 * "text": "How do I write JSON on Android?", 54 * "geo": null, 55 * "user": { 56 * "name": "android_newb", 57 * "followers_count": 41 58 * } 59 * }, 60 * { 61 * "id": 912345678902, 62 * "text": "@android_newb just use android.util.JsonWriter!", 63 * "geo": [50.454722, -104.606667], 64 * "user": { 65 * "name": "jesse", 66 * "followers_count": 2 67 * } 68 * } 69 * ]}</pre> 70 * This code encodes the above structure: <pre> {@code 71 * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException { 72 * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); 73 * writer.setIndent(" "); 74 * writeMessagesArray(writer, messages); 75 * writer.close(); 76 * } 77 * 78 * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException { 79 * writer.beginArray(); 80 * for (Message message : messages) { 81 * writeMessage(writer, message); 82 * } 83 * writer.endArray(); 84 * } 85 * 86 * public void writeMessage(JsonWriter writer, Message message) throws IOException { 87 * writer.beginObject(); 88 * writer.name("id").value(message.getId()); 89 * writer.name("text").value(message.getText()); 90 * if (message.getGeo() != null) { 91 * writer.name("geo"); 92 * writeDoublesArray(writer, message.getGeo()); 93 * } else { 94 * writer.name("geo").nullValue(); 95 * } 96 * writer.name("user"); 97 * writeUser(writer, message.getUser()); 98 * writer.endObject(); 99 * } 100 * 101 * public void writeUser(JsonWriter writer, User user) throws IOException { 102 * writer.beginObject(); 103 * writer.name("name").value(user.getName()); 104 * writer.name("followers_count").value(user.getFollowersCount()); 105 * writer.endObject(); 106 * } 107 * 108 * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException { 109 * writer.beginArray(); 110 * for (Double value : doubles) { 111 * writer.value(value); 112 * } 113 * writer.endArray(); 114 * }}</pre> 115 * 116 * <p>Each {@code JsonWriter} may be used to write a single JSON stream. 117 * Instances of this class are not thread safe. Calls that would result in a 118 * malformed JSON string will fail with an {@link IllegalStateException}. 119 */ 120 @android.ravenwood.annotation.RavenwoodKeepWholeClass 121 public final class JsonWriter implements Closeable { 122 123 /** The output data, containing at most one top-level array or object. */ 124 private final Writer out; 125 126 private final List<JsonScope> stack = new ArrayList<JsonScope>(); 127 { 128 stack.add(JsonScope.EMPTY_DOCUMENT); 129 } 130 131 /** 132 * A string containing a full set of spaces for a single level of 133 * indentation, or null for no pretty printing. 134 */ 135 private String indent; 136 137 /** 138 * The name/value separator; either ":" or ": ". 139 */ 140 private String separator = ":"; 141 142 private boolean lenient; 143 144 /** 145 * Creates a new instance that writes a JSON-encoded stream to {@code out}. 146 * For best performance, ensure {@link Writer} is buffered; wrapping in 147 * {@link java.io.BufferedWriter BufferedWriter} if necessary. 148 */ JsonWriter(Writer out)149 public JsonWriter(Writer out) { 150 if (out == null) { 151 throw new NullPointerException("out == null"); 152 } 153 this.out = out; 154 } 155 156 /** 157 * Sets the indentation string to be repeated for each level of indentation 158 * in the encoded document. If {@code indent.isEmpty()} the encoded document 159 * will be compact. Otherwise the encoded document will be more 160 * human-readable. 161 * 162 * @param indent a string containing only whitespace. 163 */ setIndent(String indent)164 public void setIndent(String indent) { 165 if (indent.isEmpty()) { 166 this.indent = null; 167 this.separator = ":"; 168 } else { 169 this.indent = indent; 170 this.separator = ": "; 171 } 172 } 173 174 /** 175 * Configure this writer to relax its syntax rules. By default, this writer 176 * only emits well-formed JSON as specified by <a 177 * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer 178 * to lenient permits the following: 179 * <ul> 180 * <li>Top-level values of any type. With strict writing, the top-level 181 * value must be an object or an array. 182 * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link 183 * Double#isInfinite() infinities}. 184 * </ul> 185 */ setLenient(boolean lenient)186 public void setLenient(boolean lenient) { 187 this.lenient = lenient; 188 } 189 190 /** 191 * Returns true if this writer has relaxed syntax rules. 192 */ isLenient()193 public boolean isLenient() { 194 return lenient; 195 } 196 197 /** 198 * Begins encoding a new array. Each call to this method must be paired with 199 * a call to {@link #endArray}. 200 * 201 * @return this writer. 202 */ beginArray()203 public JsonWriter beginArray() throws IOException { 204 return open(JsonScope.EMPTY_ARRAY, "["); 205 } 206 207 /** 208 * Ends encoding the current array. 209 * 210 * @return this writer. 211 */ endArray()212 public JsonWriter endArray() throws IOException { 213 return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]"); 214 } 215 216 /** 217 * Begins encoding a new object. Each call to this method must be paired 218 * with a call to {@link #endObject}. 219 * 220 * @return this writer. 221 */ beginObject()222 public JsonWriter beginObject() throws IOException { 223 return open(JsonScope.EMPTY_OBJECT, "{"); 224 } 225 226 /** 227 * Ends encoding the current object. 228 * 229 * @return this writer. 230 */ endObject()231 public JsonWriter endObject() throws IOException { 232 return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}"); 233 } 234 235 /** 236 * Enters a new scope by appending any necessary whitespace and the given 237 * bracket. 238 */ open(JsonScope empty, String openBracket)239 private JsonWriter open(JsonScope empty, String openBracket) throws IOException { 240 beforeValue(true); 241 stack.add(empty); 242 out.write(openBracket); 243 return this; 244 } 245 246 /** 247 * Closes the current scope by appending any necessary whitespace and the 248 * given bracket. 249 */ close(JsonScope empty, JsonScope nonempty, String closeBracket)250 private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket) 251 throws IOException { 252 JsonScope context = peek(); 253 if (context != nonempty && context != empty) { 254 throw new IllegalStateException("Nesting problem: " + stack); 255 } 256 257 stack.remove(stack.size() - 1); 258 if (context == nonempty) { 259 newline(); 260 } 261 out.write(closeBracket); 262 return this; 263 } 264 265 /** 266 * Returns the value on the top of the stack. 267 */ peek()268 private JsonScope peek() { 269 return stack.get(stack.size() - 1); 270 } 271 272 /** 273 * Replace the value on the top of the stack with the given value. 274 */ replaceTop(JsonScope topOfStack)275 private void replaceTop(JsonScope topOfStack) { 276 stack.set(stack.size() - 1, topOfStack); 277 } 278 279 /** 280 * Encodes the property name. 281 * 282 * @param name the name of the forthcoming value. May not be null. 283 * @return this writer. 284 */ name(String name)285 public JsonWriter name(String name) throws IOException { 286 if (name == null) { 287 throw new NullPointerException("name == null"); 288 } 289 beforeName(); 290 string(name); 291 return this; 292 } 293 294 /** 295 * Encodes {@code value}. 296 * 297 * @param value the literal string value, or null to encode a null literal. 298 * @return this writer. 299 */ value(String value)300 public JsonWriter value(String value) throws IOException { 301 if (value == null) { 302 return nullValue(); 303 } 304 beforeValue(false); 305 string(value); 306 return this; 307 } 308 309 /** 310 * Encodes {@code null}. 311 * 312 * @return this writer. 313 */ nullValue()314 public JsonWriter nullValue() throws IOException { 315 beforeValue(false); 316 out.write("null"); 317 return this; 318 } 319 320 /** 321 * Encodes {@code value}. 322 * 323 * @return this writer. 324 */ value(boolean value)325 public JsonWriter value(boolean value) throws IOException { 326 beforeValue(false); 327 out.write(value ? "true" : "false"); 328 return this; 329 } 330 331 /** 332 * Encodes {@code value}. 333 * 334 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 335 * {@link Double#isInfinite() infinities} unless this writer is lenient. 336 * @return this writer. 337 */ value(double value)338 public JsonWriter value(double value) throws IOException { 339 if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) { 340 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 341 } 342 beforeValue(false); 343 out.append(Double.toString(value)); 344 return this; 345 } 346 347 /** 348 * Encodes {@code value}. 349 * 350 * @return this writer. 351 */ value(long value)352 public JsonWriter value(long value) throws IOException { 353 beforeValue(false); 354 out.write(Long.toString(value)); 355 return this; 356 } 357 358 /** 359 * Encodes {@code value}. 360 * 361 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 362 * {@link Double#isInfinite() infinities} unless this writer is lenient. 363 * @return this writer. 364 */ value(Number value)365 public JsonWriter value(Number value) throws IOException { 366 if (value == null) { 367 return nullValue(); 368 } 369 370 String string = value.toString(); 371 if (!lenient && 372 (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { 373 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 374 } 375 beforeValue(false); 376 out.append(string); 377 return this; 378 } 379 380 /** 381 * Ensures all buffered data is written to the underlying {@link Writer} 382 * and flushes that writer. 383 */ flush()384 public void flush() throws IOException { 385 out.flush(); 386 } 387 388 /** 389 * Flushes and closes this writer and the underlying {@link Writer}. 390 * 391 * @throws IOException if the JSON document is incomplete. 392 */ close()393 public void close() throws IOException { 394 out.close(); 395 396 if (peek() != JsonScope.NONEMPTY_DOCUMENT) { 397 throw new IOException("Incomplete document"); 398 } 399 } 400 string(String value)401 private void string(String value) throws IOException { 402 out.write("\""); 403 for (int i = 0, length = value.length(); i < length; i++) { 404 char c = value.charAt(i); 405 406 /* 407 * From RFC 4627, "All Unicode characters may be placed within the 408 * quotation marks except for the characters that must be escaped: 409 * quotation mark, reverse solidus, and the control characters 410 * (U+0000 through U+001F)." 411 * 412 * We also escape '\u2028' and '\u2029', which JavaScript interprets 413 * as newline characters. This prevents eval() from failing with a 414 * syntax error. 415 * http://code.google.com/p/google-gson/issues/detail?id=341 416 */ 417 switch (c) { 418 case '"': 419 case '\\': 420 out.write('\\'); 421 out.write(c); 422 break; 423 424 case '\t': 425 out.write("\\t"); 426 break; 427 428 case '\b': 429 out.write("\\b"); 430 break; 431 432 case '\n': 433 out.write("\\n"); 434 break; 435 436 case '\r': 437 out.write("\\r"); 438 break; 439 440 case '\f': 441 out.write("\\f"); 442 break; 443 444 case '\u2028': 445 case '\u2029': 446 out.write(String.format("\\u%04x", (int) c)); 447 break; 448 449 default: 450 if (c <= 0x1F) { 451 out.write(String.format("\\u%04x", (int) c)); 452 } else { 453 out.write(c); 454 } 455 break; 456 } 457 458 } 459 out.write("\""); 460 } 461 newline()462 private void newline() throws IOException { 463 if (indent == null) { 464 return; 465 } 466 467 out.write("\n"); 468 for (int i = 1; i < stack.size(); i++) { 469 out.write(indent); 470 } 471 } 472 473 /** 474 * Inserts any necessary separators and whitespace before a name. Also 475 * adjusts the stack to expect the name's value. 476 */ beforeName()477 private void beforeName() throws IOException { 478 JsonScope context = peek(); 479 if (context == JsonScope.NONEMPTY_OBJECT) { // first in object 480 out.write(','); 481 } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object! 482 throw new IllegalStateException("Nesting problem: " + stack); 483 } 484 newline(); 485 replaceTop(JsonScope.DANGLING_NAME); 486 } 487 488 /** 489 * Inserts any necessary separators and whitespace before a literal value, 490 * inline array, or inline object. Also adjusts the stack to expect either a 491 * closing bracket or another element. 492 * 493 * @param root true if the value is a new array or object, the two values 494 * permitted as top-level elements. 495 */ beforeValue(boolean root)496 private void beforeValue(boolean root) throws IOException { 497 switch (peek()) { 498 case EMPTY_DOCUMENT: // first in document 499 if (!lenient && !root) { 500 throw new IllegalStateException( 501 "JSON must start with an array or an object."); 502 } 503 replaceTop(JsonScope.NONEMPTY_DOCUMENT); 504 break; 505 506 case EMPTY_ARRAY: // first in array 507 replaceTop(JsonScope.NONEMPTY_ARRAY); 508 newline(); 509 break; 510 511 case NONEMPTY_ARRAY: // another in array 512 out.append(','); 513 newline(); 514 break; 515 516 case DANGLING_NAME: // value for name 517 out.append(separator); 518 replaceTop(JsonScope.NONEMPTY_OBJECT); 519 break; 520 521 case NONEMPTY_DOCUMENT: 522 throw new IllegalStateException( 523 "JSON must have only one top-level value."); 524 525 default: 526 throw new IllegalStateException("Nesting problem: " + stack); 527 } 528 } 529 } 530