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 com.android.json.stream; 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 public final class JsonWriter implements Closeable { 121 122 /** The output data, containing at most one top-level array or object. */ 123 private final Writer out; 124 125 private final List<JsonScope> stack = new ArrayList<JsonScope>(); 126 { 127 stack.add(JsonScope.EMPTY_DOCUMENT); 128 } 129 130 /** 131 * A string containing a full set of spaces for a single level of 132 * indentation, or null for no pretty printing. 133 */ 134 private String indent; 135 136 /** 137 * The name/value separator; either ":" or ": ". 138 */ 139 private String separator = ":"; 140 141 /** 142 * Creates a new instance that writes a JSON-encoded stream to {@code out}. 143 * For best performance, ensure {@link Writer} is buffered; wrapping in 144 * {@link java.io.BufferedWriter BufferedWriter} if necessary. 145 */ JsonWriter(Writer out)146 public JsonWriter(Writer out) { 147 if (out == null) { 148 throw new NullPointerException("out == null"); 149 } 150 this.out = out; 151 } 152 153 /** 154 * Sets the indentation string to be repeated for each level of indentation 155 * in the encoded document. If {@code indent.isEmpty()} the encoded document 156 * will be compact. Otherwise the encoded document will be more 157 * human-readable. 158 * 159 * @param indent a string containing only whitespace. 160 */ setIndent(String indent)161 public void setIndent(String indent) { 162 if (indent.isEmpty()) { 163 this.indent = null; 164 this.separator = ":"; 165 } else { 166 this.indent = indent; 167 this.separator = ": "; 168 } 169 } 170 171 /** 172 * Begins encoding a new array. Each call to this method must be paired with 173 * a call to {@link #endArray}. 174 * 175 * @return this writer. 176 */ beginArray()177 public JsonWriter beginArray() throws IOException { 178 return open(JsonScope.EMPTY_ARRAY, "["); 179 } 180 181 /** 182 * Ends encoding the current array. 183 * 184 * @return this writer. 185 */ endArray()186 public JsonWriter endArray() throws IOException { 187 return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]"); 188 } 189 190 /** 191 * Begins encoding a new object. Each call to this method must be paired 192 * with a call to {@link #endObject}. 193 * 194 * @return this writer. 195 */ beginObject()196 public JsonWriter beginObject() throws IOException { 197 return open(JsonScope.EMPTY_OBJECT, "{"); 198 } 199 200 /** 201 * Ends encoding the current object. 202 * 203 * @return this writer. 204 */ endObject()205 public JsonWriter endObject() throws IOException { 206 return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}"); 207 } 208 209 /** 210 * Enters a new scope by appending any necessary whitespace and the given 211 * bracket. 212 */ open(JsonScope empty, String openBracket)213 private JsonWriter open(JsonScope empty, String openBracket) throws IOException { 214 beforeValue(true); 215 stack.add(empty); 216 out.write(openBracket); 217 return this; 218 } 219 220 /** 221 * Closes the current scope by appending any necessary whitespace and the 222 * given bracket. 223 */ close(JsonScope empty, JsonScope nonempty, String closeBracket)224 private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket) 225 throws IOException { 226 JsonScope context = peek(); 227 if (context != nonempty && context != empty) { 228 throw new IllegalStateException("Nesting problem: " + stack); 229 } 230 231 stack.remove(stack.size() - 1); 232 if (context == nonempty) { 233 newline(); 234 } 235 out.write(closeBracket); 236 return this; 237 } 238 239 /** 240 * Returns the value on the top of the stack. 241 */ peek()242 private JsonScope peek() { 243 return stack.get(stack.size() - 1); 244 } 245 246 /** 247 * Replace the value on the top of the stack with the given value. 248 */ replaceTop(JsonScope topOfStack)249 private void replaceTop(JsonScope topOfStack) { 250 stack.set(stack.size() - 1, topOfStack); 251 } 252 253 /** 254 * Encodes the property name. 255 * 256 * @param name the name of the forthcoming value. May not be null. 257 * @return this writer. 258 */ name(String name)259 public JsonWriter name(String name) throws IOException { 260 if (name == null) { 261 throw new NullPointerException("name == null"); 262 } 263 beforeName(); 264 string(name); 265 return this; 266 } 267 268 /** 269 * Encodes {@code value}. 270 * 271 * @param value the literal string value, or null to encode a null literal. 272 * @return this writer. 273 */ value(String value)274 public JsonWriter value(String value) throws IOException { 275 if (value == null) { 276 return nullValue(); 277 } 278 beforeValue(false); 279 string(value); 280 return this; 281 } 282 283 /** 284 * Encodes {@code null}. 285 * 286 * @return this writer. 287 */ nullValue()288 public JsonWriter nullValue() throws IOException { 289 beforeValue(false); 290 out.write("null"); 291 return this; 292 } 293 294 /** 295 * Encodes {@code value}. 296 * 297 * @return this writer. 298 */ value(boolean value)299 public JsonWriter value(boolean value) throws IOException { 300 beforeValue(false); 301 out.write(value ? "true" : "false"); 302 return this; 303 } 304 305 /** 306 * Encodes {@code value}. 307 * 308 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 309 * {@link Double#isInfinite() infinities}. 310 * @return this writer. 311 */ value(double value)312 public JsonWriter value(double value) throws IOException { 313 if (Double.isNaN(value) || Double.isInfinite(value)) { 314 throw new IllegalArgumentException("Numeric values must be finite, but was " + value); 315 } 316 beforeValue(false); 317 out.append(Double.toString(value)); 318 return this; 319 } 320 321 /** 322 * Encodes {@code value}. 323 * 324 * @return this writer. 325 */ value(long value)326 public JsonWriter value(long value) throws IOException { 327 beforeValue(false); 328 out.write(Long.toString(value)); 329 return this; 330 } 331 332 /** 333 * Ensures all buffered data is written to the underlying {@link Writer} 334 * and flushes that writer. 335 */ flush()336 public void flush() throws IOException { 337 out.flush(); 338 } 339 340 /** 341 * Flushes and closes this writer and the underlying {@link Writer}. 342 * 343 * @throws IOException if the JSON document is incomplete. 344 */ close()345 public void close() throws IOException { 346 out.close(); 347 348 if (peek() != JsonScope.NONEMPTY_DOCUMENT) { 349 throw new IOException("Incomplete document"); 350 } 351 } 352 string(String value)353 private void string(String value) throws IOException { 354 out.write("\""); 355 for (int i = 0, length = value.length(); i < length; i++) { 356 char c = value.charAt(i); 357 358 /* 359 * From RFC 4627, "All Unicode characters may be placed within the 360 * quotation marks except for the characters that must be escaped: 361 * quotation mark, reverse solidus, and the control characters 362 * (U+0000 through U+001F)." 363 */ 364 switch (c) { 365 case '"': 366 case '\\': 367 case '/': 368 out.write('\\'); 369 out.write(c); 370 break; 371 372 case '\t': 373 out.write("\\t"); 374 break; 375 376 case '\b': 377 out.write("\\b"); 378 break; 379 380 case '\n': 381 out.write("\\n"); 382 break; 383 384 case '\r': 385 out.write("\\r"); 386 break; 387 388 case '\f': 389 out.write("\\f"); 390 break; 391 392 default: 393 if (c <= 0x1F) { 394 out.write(String.format("\\u%04x", (int) c)); 395 } else { 396 out.write(c); 397 } 398 break; 399 } 400 401 } 402 out.write("\""); 403 } 404 newline()405 private void newline() throws IOException { 406 if (indent == null) { 407 return; 408 } 409 410 out.write("\n"); 411 for (int i = 1; i < stack.size(); i++) { 412 out.write(indent); 413 } 414 } 415 416 /** 417 * Inserts any necessary separators and whitespace before a name. Also 418 * adjusts the stack to expect the name's value. 419 */ beforeName()420 private void beforeName() throws IOException { 421 JsonScope context = peek(); 422 if (context == JsonScope.NONEMPTY_OBJECT) { // first in object 423 out.write(','); 424 } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object! 425 throw new IllegalStateException("Nesting problem: " + stack); 426 } 427 newline(); 428 replaceTop(JsonScope.DANGLING_NAME); 429 } 430 431 /** 432 * Inserts any necessary separators and whitespace before a literal value, 433 * inline array, or inline object. Also adjusts the stack to expect either a 434 * closing bracket or another element. 435 * 436 * @param root true if the value is a new array or object, the two values 437 * permitted as top-level elements. 438 */ beforeValue(boolean root)439 private void beforeValue(boolean root) throws IOException { 440 switch (peek()) { 441 case EMPTY_DOCUMENT: // first in document 442 if (!root) { 443 throw new IllegalStateException( 444 "JSON must start with an array or an object."); 445 } 446 replaceTop(JsonScope.NONEMPTY_DOCUMENT); 447 break; 448 449 case EMPTY_ARRAY: // first in array 450 replaceTop(JsonScope.NONEMPTY_ARRAY); 451 newline(); 452 break; 453 454 case NONEMPTY_ARRAY: // another in array 455 out.append(','); 456 newline(); 457 break; 458 459 case DANGLING_NAME: // value for name 460 out.append(separator); 461 replaceTop(JsonScope.NONEMPTY_OBJECT); 462 break; 463 464 case NONEMPTY_DOCUMENT: 465 throw new IllegalStateException( 466 "JSON must have only one top-level value."); 467 468 default: 469 throw new IllegalStateException("Nesting problem: " + stack); 470 } 471 } 472 } 473