All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squareup.moshi.JsonWriter Maven / Gradle / Ivy

There is a newer version: 4.1.5
Show newest version
/*
 * Copyright (C) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.moshi;

import com.jn.langx.annotation.Nullable;
import com.jn.langx.util.io.IOs;
import okio.BufferedSink;
import okio.BufferedSource;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.util.Arrays;

import static com.squareup.moshi.JsonScope.*;

/**
 * Writes a JSON (RFC 7159)
 * 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 stream JSON in Java?",
 *     "geo": null,
 *     "user": {
 *       "name": "json_newb",
 *       "followers_count": 41
 *      }
 *   },
 *   {
 *     "id": 912345678902,
 *     "text": "@json_newb just use JsonWriter!",
 *     "geo": [50.454722, -104.606667],
 *     "user": {
 *       "name": "jesse",
 *       "followers_count": 2
 *     }
 *   }
 * ]}
* This code encodes the above structure:
   {@code
 *   public void writeJsonStream(BufferedSink sink, List messages) throws IOException {
 *     JsonWriter writer = JsonWriter.of(sink);
 *     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}. */ public abstract class JsonWriter implements Closeable, Flushable { // The nesting stack. Using a manual array rather than an ArrayList saves 20%. This stack will // grow itself up to 256 levels of nesting including the top-level document. Deeper nesting is // prone to trigger StackOverflowErrors. int stackSize = 0; int[] scopes = new int[32]; String[] pathNames = new String[32]; int[] pathIndices = new int[32]; /** * A string containing a full set of spaces for a single level of indentation, or null for no * pretty printing. */ String indent; boolean lenient; boolean serializeNulls; boolean promoteValueToName; /** * Controls the deepest stack size that has begin/end pairs flattened: * *

    *
  • If -1, no begin/end pairs are being suppressed. *
  • If positive, this is the deepest stack size whose begin/end pairs are eligible to be * flattened. *
  • If negative, it is the bitwise inverse (~) of the deepest stack size whose begin/end * pairs have been flattened. *
* *

We differentiate between what layer would be flattened (positive) from what layer is being * flattened (negative) so that we don't double-flatten. * *

To accommodate nested flattening we require callers to track the previous state when they * provide a new state. The previous state is returned from {@link #beginFlatten} and restored * with {@link #endFlatten}. */ int flattenStackSize = -1; /** * Returns a new instance that writes UTF-8 encoded JSON to {@code sink}. */ public static JsonWriter of(BufferedSink sink) { return new JsonUtf8Writer(sink); } JsonWriter() { // Package-private to control subclasses. } /** * Returns the scope on the top of the stack. */ final int peekScope() { if (stackSize == 0) { throw new IllegalStateException("JsonWriter is closed."); } return scopes[stackSize - 1]; } /** * Before pushing a value on the stack this confirms that the stack has capacity. */ final boolean checkStack() { if (stackSize != scopes.length) return false; if (stackSize == 256) { throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?"); } scopes = Arrays.copyOf(scopes, scopes.length * 2); pathNames = Arrays.copyOf(pathNames, pathNames.length * 2); pathIndices = Arrays.copyOf(pathIndices, pathIndices.length * 2); if (this instanceof JsonValueWriter) { ((JsonValueWriter) this).stack = Arrays.copyOf(((JsonValueWriter) this).stack, ((JsonValueWriter) this).stack.length * 2); } return true; } final void pushScope(int newTop) { scopes[stackSize++] = newTop; } /** * Replace the value on the top of the stack with the given value. */ final void replaceTop(int topOfStack) { scopes[stackSize - 1] = topOfStack; } /** * 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. * * @param indent a string containing only whitespace. */ public void setIndent(String indent) { this.indent = !indent.isEmpty() ? indent : null; } /** * Returns a string containing only whitespace, used for each level of * indentation. If empty, the encoded document will be compact. */ public final String getIndent() { return indent != null ? indent : ""; } /** * Configure this writer to relax its syntax rules. By default, this writer * only emits well-formed JSON as specified by RFC 7159. 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 {@linkplain Double#isNaN() NaNs} or {@linkplain * Double#isInfinite() infinities}. *
*/ public final void setLenient(boolean lenient) { this.lenient = lenient; } /** * Returns true if this writer has relaxed syntax rules. */ public final boolean isLenient() { return lenient; } /** * Sets whether object members are serialized when their value is null. * This has no impact on array elements. The default is false. */ public final void setSerializeNulls(boolean serializeNulls) { this.serializeNulls = serializeNulls; } /** * Returns true if object members are serialized when their value is null. * This has no impact on array elements. The default is false. */ public final boolean getSerializeNulls() { return serializeNulls; } /** * Begins encoding a new array. Each call to this method must be paired with * a call to {@link #endArray}. * * @return this writer. */ public abstract JsonWriter beginArray() throws IOException; /** * Ends encoding the current array. * * @return this writer. */ public abstract JsonWriter endArray() throws IOException; /** * Begins encoding a new object. Each call to this method must be paired * with a call to {@link #endObject}. * * @return this writer. */ public abstract JsonWriter beginObject() throws IOException; /** * Ends encoding the current object. * * @return this writer. */ public abstract JsonWriter endObject() throws IOException; /** * Encodes the property name. * * @param name the name of the forthcoming value. Must not be null. * @return this writer. */ public abstract JsonWriter name(String name) throws IOException; /** * Encodes {@code value}. * * @param value the literal string value, or null to encode a null literal. * @return this writer. */ public abstract JsonWriter value(@Nullable String value) throws IOException; /** * Encodes {@code null}. * * @return this writer. */ public abstract JsonWriter nullValue() throws IOException; /** * Encodes {@code value}. * * @return this writer. */ public abstract JsonWriter value(boolean value) throws IOException; /** * Encodes {@code value}. * * @return this writer. */ public abstract JsonWriter value(@Nullable Boolean value) throws IOException; /** * Encodes {@code value}. * * @param value a finite value. May not be {@linkplain Double#isNaN() NaNs} or * {@linkplain Double#isInfinite() infinities}. * @return this writer. */ public abstract JsonWriter value(double value) throws IOException; /** * Encodes {@code value}. * * @return this writer. */ public abstract JsonWriter value(long value) throws IOException; /** * Encodes {@code value}. * * @param value a finite value. May not be {@linkplain Double#isNaN() NaNs} or * {@linkplain Double#isInfinite() infinities}. * @return this writer. */ public abstract JsonWriter value(@Nullable Number value) throws IOException; /** * Writes {@code source} directly without encoding its contents. Equivalent to * {@code try (BufferedSink sink = writer.valueSink()) { source.readAll(sink): }} * * @see #valueSink() */ public final JsonWriter value(BufferedSource source) throws IOException { if (promoteValueToName) { throw new IllegalStateException( "BufferedSource cannot be used as a map key in JSON at path " + getPath()); } BufferedSink sink = null; try{ sink = valueSink(); source.readAll(sink); }finally { IOs.close(sink); } return this; } /** * Returns a {@link BufferedSink} into which arbitrary data can be written without any additional * encoding. You must call {@link BufferedSink#close()} before interacting with this * {@code JsonWriter} instance again. *

* Since no validation is performed, options like {@link #setSerializeNulls} and other writer * configurations are not respected. */ public abstract BufferedSink valueSink() throws IOException; /** * Changes the writer to treat the next value as a string name. This is useful for map adapters so * that arbitrary type adapters can use {@link #value} to write a name value. */ final void promoteValueToName() throws IOException { int context = peekScope(); if (context != NONEMPTY_OBJECT && context != EMPTY_OBJECT) { throw new IllegalStateException("Nesting problem."); } promoteValueToName = true; } /** * Cancels immediately-nested calls to {@link #beginArray()} or {@link #beginObject()} and their * matching calls to {@link #endArray} or {@link #endObject()}. Use this to compose JSON adapters * without nesting. * *

For example, the following creates JSON with nested arrays: {@code [1,[2,3,4],5]}. * *

{@code
     *
     *   JsonAdapter> integersAdapter = ...
     *
     *   public void writeNumbers(JsonWriter writer) {
     *     writer.beginArray();
     *     writer.value(1);
     *     integersAdapter.toJson(writer, Arrays.asList(2, 3, 4));
     *     writer.value(5);
     *     writer.endArray();
     *   }
     * }
* *

With flattening we can create JSON with a single array {@code [1,2,3,4,5]}: * *

{@code
     *
     *   JsonAdapter> integersAdapter = ...
     *
     *   public void writeNumbers(JsonWriter writer) {
     *     writer.beginArray();
     *     int token = writer.beginFlatten();
     *     writer.value(1);
     *     integersAdapter.toJson(writer, Arrays.asList(2, 3, 4));
     *     writer.value(5);
     *     writer.endFlatten(token);
     *     writer.endArray();
     *   }
     * }
* *

This method flattens arrays within arrays: * *

{@code
     *
     *   Emit:       [1, [2, 3, 4], 5]
     *   To produce: [1, 2, 3, 4, 5]
     * }
*

* It also flattens objects within objects. Do not call {@link #name} before writing a flattened * object. * *

{@code
     *
     *   Emit:       {"a": 1, {"b": 2}, "c": 3}
     *   To Produce: {"a": 1, "b": 2, "c": 3}
     * }
*

* Other combinations are permitted but do not perform flattening. For example, objects inside of * arrays are not flattened: * *

{@code
     *
     *   Emit:       [1, {"b": 2}, 3, [4, 5], 6]
     *   To Produce: [1, {"b": 2}, 3, 4, 5, 6]
     * }
* *

This method returns an opaque token. Callers must match all calls to this method with a call * to {@link #endFlatten} with the matching token. */ public final int beginFlatten() { int context = peekScope(); if (context != NONEMPTY_OBJECT && context != EMPTY_OBJECT && context != NONEMPTY_ARRAY && context != EMPTY_ARRAY) { throw new IllegalStateException("Nesting problem."); } int token = flattenStackSize; flattenStackSize = stackSize; return token; } /** * Ends nested call flattening created by {@link #beginFlatten}. */ public final void endFlatten(int token) { flattenStackSize = token; } /** * Returns a JsonPath to * the current location in the JSON value. */ public final String getPath() { return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy