Writes a JSON (RFC 4627)
encoded value to a stream, one token at a time. The stream includes both
literal values (strings, numbers, booleans and nulls) as well as the begin
and end delimiters of objects and arrays.
Encoding JSON
To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
document must contain one top-level array or object. Call methods on the
writer as you walk the structure's contents, nesting arrays and objects as
necessary:
- To write arrays, first call {@link #beginArray()}.
Write each of the array's elements with the appropriate {@link #value}
methods or by nesting other arrays and objects. Finally close the array
using {@link #endArray()}.
- To write objects, first call {@link #beginObject()}.
Write each of the object's properties by alternating calls to
{@link #name} with the property's value. Write property values with the
appropriate {@link #value} method or by nesting other objects or arrays.
Finally close the object using {@link #endObject()}.
Example
Suppose we'd like to encode a stream of messages such as the following: {@code
[
{
"id": 912345678901,
"text": "How do I write JSON on Android?",
"geo": null,
"user": {
"name": "android_newb",
"followers_count": 41
}
},
{
"id": 912345678902,
"text": "@android_newb just use android.util.JsonWriter!",
"geo": [50.454722, -104.606667],
"user": {
"name": "jesse",
"followers_count": 2
}
}
]}
This code encodes the above structure: {@code
public void writeJsonStream(OutputStream out, List messages) throws IOException {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
writer.setIndent(" ");
writeMessagesArray(writer, messages);
writer.close();
}
public void writeMessagesArray(JsonWriter writer, List messages) throws IOException {
writer.beginArray();
for (Message message : messages) {
writeMessage(writer, message);
}
writer.endArray();
}
public void writeMessage(JsonWriter writer, Message message) throws IOException {
writer.beginObject();
writer.name("id").value(message.getId());
writer.name("text").value(message.getText());
if (message.getGeo() != null) {
writer.name("geo");
writeDoublesArray(writer, message.getGeo());
} else {
writer.name("geo").nullValue();
}
writer.name("user");
writeUser(writer, message.getUser());
writer.endObject();
}
public void writeUser(JsonWriter writer, User user) throws IOException {
writer.beginObject();
writer.name("name").value(user.getName());
writer.name("followers_count").value(user.getFollowersCount());
writer.endObject();
}
public void writeDoublesArray(JsonWriter writer, List doubles) throws IOException {
writer.beginArray();
for (Double value : doubles) {
writer.value(value);
}
writer.endArray();
}}
Each {@code JsonWriter} may be used to write a single JSON stream.
Instances of this class are not thread safe. Calls that would result in a
malformed JSON string will fail with an {@link IllegalStateException}. |
Methods Summary |
---|
private void | beforeName()Inserts any necessary separators and whitespace before a name. Also
adjusts the stack to expect the name's value.
JsonScope context = peek();
if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
out.write(',");
} else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
throw new IllegalStateException("Nesting problem: " + stack);
}
newline();
replaceTop(JsonScope.DANGLING_NAME);
|
private void | beforeValue(boolean root)Inserts any necessary separators and whitespace before a literal value,
inline array, or inline object. Also adjusts the stack to expect either a
closing bracket or another element.
switch (peek()) {
case EMPTY_DOCUMENT: // first in document
if (!lenient && !root) {
throw new IllegalStateException(
"JSON must start with an array or an object.");
}
replaceTop(JsonScope.NONEMPTY_DOCUMENT);
break;
case EMPTY_ARRAY: // first in array
replaceTop(JsonScope.NONEMPTY_ARRAY);
newline();
break;
case NONEMPTY_ARRAY: // another in array
out.append(',");
newline();
break;
case DANGLING_NAME: // value for name
out.append(separator);
replaceTop(JsonScope.NONEMPTY_OBJECT);
break;
case NONEMPTY_DOCUMENT:
throw new IllegalStateException(
"JSON must have only one top-level value.");
default:
throw new IllegalStateException("Nesting problem: " + stack);
}
|
public android.util.JsonWriter | beginArray()Begins encoding a new array. Each call to this method must be paired with
a call to {@link #endArray}.
return open(JsonScope.EMPTY_ARRAY, "[");
|
public android.util.JsonWriter | beginObject()Begins encoding a new object. Each call to this method must be paired
with a call to {@link #endObject}.
return open(JsonScope.EMPTY_OBJECT, "{");
|
private android.util.JsonWriter | close(JsonScope empty, JsonScope nonempty, java.lang.String closeBracket)Closes the current scope by appending any necessary whitespace and the
given bracket.
JsonScope context = peek();
if (context != nonempty && context != empty) {
throw new IllegalStateException("Nesting problem: " + stack);
}
stack.remove(stack.size() - 1);
if (context == nonempty) {
newline();
}
out.write(closeBracket);
return this;
|
public void | close()Flushes and closes this writer and the underlying {@link Writer}.
out.close();
if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
throw new IOException("Incomplete document");
}
|
public android.util.JsonWriter | endArray()Ends encoding the current array.
return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
|
public android.util.JsonWriter | endObject()Ends encoding the current object.
return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
|
public void | flush()Ensures all buffered data is written to the underlying {@link Writer}
and flushes that writer.
out.flush();
|
public boolean | isLenient()Returns true if this writer has relaxed syntax rules.
return lenient;
|
public android.util.JsonWriter | name(java.lang.String name)Encodes the property name.
if (name == null) {
throw new NullPointerException("name == null");
}
beforeName();
string(name);
return this;
|
private void | newline()
if (indent == null) {
return;
}
out.write("\n");
for (int i = 1; i < stack.size(); i++) {
out.write(indent);
}
|
public android.util.JsonWriter | nullValue()Encodes {@code null}.
beforeValue(false);
out.write("null");
return this;
|
private android.util.JsonWriter | open(JsonScope empty, java.lang.String openBracket)Enters a new scope by appending any necessary whitespace and the given
bracket.
beforeValue(true);
stack.add(empty);
out.write(openBracket);
return this;
|
private JsonScope | peek()Returns the value on the top of the stack.
return stack.get(stack.size() - 1);
|
private void | replaceTop(JsonScope topOfStack)Replace the value on the top of the stack with the given value.
stack.set(stack.size() - 1, topOfStack);
|
public void | setIndent(java.lang.String indent)Sets the indentation string to be repeated for each level of indentation
in the encoded document. If {@code indent.isEmpty()} the encoded document
will be compact. Otherwise the encoded document will be more
human-readable.
if (indent.isEmpty()) {
this.indent = null;
this.separator = ":";
} else {
this.indent = indent;
this.separator = ": ";
}
|
public void | setLenient(boolean lenient)Configure this writer to relax its syntax rules. By default, this writer
only emits well-formed JSON as specified by RFC 4627. Setting the writer
to lenient permits the following:
- Top-level values of any type. With strict writing, the top-level
value must be an object or an array.
- Numbers may be {@link Double#isNaN() NaNs} or {@link
Double#isInfinite() infinities}.
this.lenient = lenient;
|
private void | string(java.lang.String value)
out.write("\"");
for (int i = 0, length = value.length(); i < length; i++) {
char c = value.charAt(i);
/*
* From RFC 4627, "All Unicode characters may be placed within the
* quotation marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)."
*
* We also escape '\u2028' and '\u2029', which JavaScript interprets
* as newline characters. This prevents eval() from failing with a
* syntax error.
* http://code.google.com/p/google-gson/issues/detail?id=341
*/
switch (c) {
case '"":
case '\\":
out.write('\\");
out.write(c);
break;
case '\t":
out.write("\\t");
break;
case '\b":
out.write("\\b");
break;
case '\n":
out.write("\\n");
break;
case '\r":
out.write("\\r");
break;
case '\f":
out.write("\\f");
break;
case '\u2028":
case '\u2029":
out.write(String.format("\\u%04x", (int) c));
break;
default:
if (c <= 0x1F) {
out.write(String.format("\\u%04x", (int) c));
} else {
out.write(c);
}
break;
}
}
out.write("\"");
|
public android.util.JsonWriter | value(java.lang.String value)Encodes {@code value}.
if (value == null) {
return nullValue();
}
beforeValue(false);
string(value);
return this;
|
public android.util.JsonWriter | value(boolean value)Encodes {@code value}.
beforeValue(false);
out.write(value ? "true" : "false");
return this;
|
public android.util.JsonWriter | value(double value)Encodes {@code value}.
if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue(false);
out.append(Double.toString(value));
return this;
|
public android.util.JsonWriter | value(long value)Encodes {@code value}.
beforeValue(false);
out.write(Long.toString(value));
return this;
|
public android.util.JsonWriter | value(java.lang.Number value)Encodes {@code value}.
if (value == null) {
return nullValue();
}
String string = value.toString();
if (!lenient &&
(string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue(false);
out.append(string);
return this;
|