mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Finish up JsonWriter Kotlin conversion (#1501)
Co-authored-by: Zac Sweers <pandanomic@gmail.com> Co-authored-by: Goooler <wangzongler@gmail.com>
This commit is contained in:
@@ -139,12 +139,12 @@ public abstract class JsonAdapter<T> {
|
|||||||
override fun fromJson(reader: JsonReader) = delegate.fromJson(reader)
|
override fun fromJson(reader: JsonReader) = delegate.fromJson(reader)
|
||||||
|
|
||||||
override fun toJson(writer: JsonWriter, value: T?) {
|
override fun toJson(writer: JsonWriter, value: T?) {
|
||||||
val serializeNulls = writer.getSerializeNulls()
|
val serializeNulls = writer.serializeNulls
|
||||||
writer.setSerializeNulls(true)
|
writer.serializeNulls = true
|
||||||
try {
|
try {
|
||||||
delegate.toJson(writer, value)
|
delegate.toJson(writer, value)
|
||||||
} finally {
|
} finally {
|
||||||
writer.setSerializeNulls(serializeNulls)
|
writer.serializeNulls = serializeNulls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,12 +262,12 @@ public abstract class JsonAdapter<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toJson(writer: JsonWriter, value: T?) {
|
override fun toJson(writer: JsonWriter, value: T?) {
|
||||||
val originalIndent = writer.getIndent()
|
val originalIndent = writer.indent
|
||||||
writer.setIndent(indent)
|
writer.indent = indent
|
||||||
try {
|
try {
|
||||||
delegate.toJson(writer, value)
|
delegate.toJson(writer, value)
|
||||||
} finally {
|
} finally {
|
||||||
writer.setIndent(originalIndent)
|
writer.indent = originalIndent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,13 +44,15 @@ internal class JsonUtf8Writer(
|
|||||||
private var separator = ":"
|
private var separator = ":"
|
||||||
private var deferredName: String? = null
|
private var deferredName: String? = null
|
||||||
|
|
||||||
init {
|
override var indent: String
|
||||||
pushScope(JsonScope.EMPTY_DOCUMENT)
|
get() = super.indent
|
||||||
|
set(value) {
|
||||||
|
super.indent = value
|
||||||
|
separator = if (value.isNotEmpty()) ": " else ":"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setIndent(indent: String) {
|
init {
|
||||||
super.setIndent(indent)
|
pushScope(JsonScope.EMPTY_DOCUMENT)
|
||||||
separator = if (indent.isNotEmpty()) ": " else ":"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beginArray(): JsonWriter {
|
override fun beginArray(): JsonWriter {
|
||||||
@@ -172,7 +174,7 @@ internal class JsonUtf8Writer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun value(value: Double): JsonWriter = apply {
|
override fun value(value: Double): JsonWriter = apply {
|
||||||
require(lenient || !value.isNaN() && !value.isInfinite()) {
|
require(isLenient || !value.isNaN() && !value.isInfinite()) {
|
||||||
"Numeric values must be finite, but was $value"
|
"Numeric values must be finite, but was $value"
|
||||||
}
|
}
|
||||||
if (promoteValueToName) {
|
if (promoteValueToName) {
|
||||||
@@ -201,7 +203,7 @@ internal class JsonUtf8Writer(
|
|||||||
return nullValue()
|
return nullValue()
|
||||||
}
|
}
|
||||||
val string = value.toString()
|
val string = value.toString()
|
||||||
val isFinite = lenient || string != "-Infinity" && string != "Infinity" && string != "NaN"
|
val isFinite = isLenient || string != "-Infinity" && string != "Infinity" && string != "NaN"
|
||||||
require(isFinite) { "Numeric values must be finite, but was $value" }
|
require(isFinite) { "Numeric values must be finite, but was $value" }
|
||||||
if (promoteValueToName) {
|
if (promoteValueToName) {
|
||||||
promoteValueToName = false
|
promoteValueToName = false
|
||||||
@@ -258,7 +260,7 @@ internal class JsonUtf8Writer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun newline() {
|
private fun newline() {
|
||||||
if (indent == null) {
|
if (_indent == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sink.writeByte('\n'.code)
|
sink.writeByte('\n'.code)
|
||||||
@@ -295,7 +297,7 @@ internal class JsonUtf8Writer(
|
|||||||
val nextTop: Int
|
val nextTop: Int
|
||||||
when (peekScope()) {
|
when (peekScope()) {
|
||||||
JsonScope.NONEMPTY_DOCUMENT -> {
|
JsonScope.NONEMPTY_DOCUMENT -> {
|
||||||
if (!lenient) {
|
if (!isLenient) {
|
||||||
throw IllegalStateException("JSON must have only one top-level value.")
|
throw IllegalStateException("JSON must have only one top-level value.")
|
||||||
}
|
}
|
||||||
nextTop = JsonScope.NONEMPTY_DOCUMENT
|
nextTop = JsonScope.NONEMPTY_DOCUMENT
|
||||||
|
@@ -144,7 +144,7 @@ internal class JsonValueWriter : JsonWriter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun value(value: Double): JsonWriter {
|
override fun value(value: Double): JsonWriter {
|
||||||
require(lenient || !value.isNaN() && value != Double.NEGATIVE_INFINITY && value != Double.POSITIVE_INFINITY) {
|
require(isLenient || !value.isNaN() && value != Double.NEGATIVE_INFINITY && value != Double.POSITIVE_INFINITY) {
|
||||||
"Numeric values must be finite, but was $value"
|
"Numeric values must be finite, but was $value"
|
||||||
}
|
}
|
||||||
if (promoteValueToName) {
|
if (promoteValueToName) {
|
||||||
|
@@ -1,588 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 static com.squareup.moshi.JsonScope.EMPTY_ARRAY;
|
|
||||||
import static com.squareup.moshi.JsonScope.EMPTY_OBJECT;
|
|
||||||
import static com.squareup.moshi.JsonScope.NONEMPTY_ARRAY;
|
|
||||||
import static com.squareup.moshi.JsonScope.NONEMPTY_OBJECT;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.Flushable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.annotation.CheckReturnValue;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import okio.BufferedSink;
|
|
||||||
import okio.BufferedSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>) 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.
|
|
||||||
*
|
|
||||||
* <h2>Encoding JSON</h2>
|
|
||||||
*
|
|
||||||
* 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:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>To write <strong>arrays</strong>, 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()}.
|
|
||||||
* <li>To write <strong>objects</strong>, 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()}.
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <h2>Example</h2>
|
|
||||||
*
|
|
||||||
* Suppose we'd like to encode a stream of messages such as the following:
|
|
||||||
*
|
|
||||||
* <pre>{@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
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* This code encodes the above structure:
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* public void writeJsonStream(BufferedSink sink, List<Message> messages) throws IOException {
|
|
||||||
* JsonWriter writer = JsonWriter.of(sink);
|
|
||||||
* writer.setIndent(" ");
|
|
||||||
* writeMessagesArray(writer, messages);
|
|
||||||
* writer.close();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public void writeMessagesArray(JsonWriter writer, List<Message> 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<Double> doubles) throws IOException {
|
|
||||||
* writer.beginArray();
|
|
||||||
* for (Double value : doubles) {
|
|
||||||
* writer.value(value);
|
|
||||||
* }
|
|
||||||
* writer.endArray();
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>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:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>If -1, no begin/end pairs are being suppressed.
|
|
||||||
* <li>If positive, this is the deepest stack size whose begin/end pairs are eligible to be
|
|
||||||
* flattened.
|
|
||||||
* <li>If negative, it is the bitwise inverse (~) of the deepest stack size whose begin/end
|
|
||||||
* pairs have been flattened.
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>We differentiate between what layer would be flattened (positive) from what layer is being
|
|
||||||
* flattened (negative) so that we don't double-flatten.
|
|
||||||
*
|
|
||||||
* <p>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;
|
|
||||||
|
|
||||||
private Map<Class<?>, Object> tags;
|
|
||||||
|
|
||||||
/** Returns a new instance that writes UTF-8 encoded JSON to {@code sink}. */
|
|
||||||
@CheckReturnValue
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
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 <a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the
|
|
||||||
* writer to lenient permits the following:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>Top-level values of any type. With strict writing, the top-level value must be an object
|
|
||||||
* or an array.
|
|
||||||
* <li>Numbers may be {@linkplain Double#isNaN() NaNs} or {@linkplain Double#isInfinite()
|
|
||||||
* infinities}.
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final void setLenient(boolean lenient) {
|
|
||||||
this.lenient = lenient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if this writer has relaxed syntax rules. */
|
|
||||||
@CheckReturnValue
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
try (BufferedSink sink = valueSink()) {
|
|
||||||
source.readAll(sink);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link BufferedSink} into which arbitrary data can be written without any additional
|
|
||||||
* encoding. You <b>must</b> call {@link BufferedSink#close()} before interacting with this {@code
|
|
||||||
* JsonWriter} instance again.
|
|
||||||
*
|
|
||||||
* <p>Since no validation is performed, options like {@link #setSerializeNulls} and other writer
|
|
||||||
* configurations are not respected.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
public abstract BufferedSink valueSink() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the value which may be a string, number, boolean, null, map, or list.
|
|
||||||
*
|
|
||||||
* @return this writer.
|
|
||||||
* @see JsonReader#readJsonValue()
|
|
||||||
*/
|
|
||||||
public final JsonWriter jsonValue(@Nullable Object value) throws IOException {
|
|
||||||
if (value instanceof Map<?, ?>) {
|
|
||||||
beginObject();
|
|
||||||
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
|
|
||||||
Object key = entry.getKey();
|
|
||||||
if (!(key instanceof String)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
key == null
|
|
||||||
? "Map keys must be non-null"
|
|
||||||
: "Map keys must be of type String: " + key.getClass().getName());
|
|
||||||
}
|
|
||||||
name(((String) key));
|
|
||||||
jsonValue(entry.getValue());
|
|
||||||
}
|
|
||||||
endObject();
|
|
||||||
|
|
||||||
} else if (value instanceof List<?>) {
|
|
||||||
beginArray();
|
|
||||||
for (Object element : ((List<?>) value)) {
|
|
||||||
jsonValue(element);
|
|
||||||
}
|
|
||||||
endArray();
|
|
||||||
|
|
||||||
} else if (value instanceof String) {
|
|
||||||
value(((String) value));
|
|
||||||
|
|
||||||
} else if (value instanceof Boolean) {
|
|
||||||
value(((Boolean) value).booleanValue());
|
|
||||||
|
|
||||||
} else if (value instanceof Double) {
|
|
||||||
value(((Double) value).doubleValue());
|
|
||||||
|
|
||||||
} else if (value instanceof Long) {
|
|
||||||
value(((Long) value).longValue());
|
|
||||||
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
value(((Number) value));
|
|
||||||
|
|
||||||
} else if (value == null) {
|
|
||||||
nullValue();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unsupported type: " + value.getClass().getName());
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* <p>In this example, calling this method allows two sequential calls to {@link #value(String)}
|
|
||||||
* to produce the object, {@code {"a": "b"}}.
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* JsonWriter writer = JsonWriter.of(...);
|
|
||||||
* writer.beginObject();
|
|
||||||
* writer.promoteValueToName();
|
|
||||||
* writer.value("a");
|
|
||||||
* writer.value("b");
|
|
||||||
* writer.endObject();
|
|
||||||
* }</pre>
|
|
||||||
*/
|
|
||||||
public 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.
|
|
||||||
*
|
|
||||||
* <p>For example, the following creates JSON with nested arrays: {@code [1,[2,3,4],5]}.
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* JsonAdapter<List<Integer>> integersAdapter = ...
|
|
||||||
*
|
|
||||||
* public void writeNumbers(JsonWriter writer) {
|
|
||||||
* writer.beginArray();
|
|
||||||
* writer.value(1);
|
|
||||||
* integersAdapter.toJson(writer, Arrays.asList(2, 3, 4));
|
|
||||||
* writer.value(5);
|
|
||||||
* writer.endArray();
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>With flattening we can create JSON with a single array {@code [1,2,3,4,5]}:
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* JsonAdapter<List<Integer>> 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();
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>This method flattens arrays within arrays:
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* Emit: [1, [2, 3, 4], 5]
|
|
||||||
* To produce: [1, 2, 3, 4, 5]
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* It also flattens objects within objects. Do not call {@link #name} before writing a flattened
|
|
||||||
* object.
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* Emit: {"a": 1, {"b": 2}, "c": 3}
|
|
||||||
* To Produce: {"a": 1, "b": 2, "c": 3}
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* Other combinations are permitted but do not perform flattening. For example, objects inside of
|
|
||||||
* arrays are not flattened:
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* Emit: [1, {"b": 2}, 3, [4, 5], 6]
|
|
||||||
* To Produce: [1, {"b": 2}, 3, 4, 5, 6]
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>This method returns an opaque token. Callers must match all calls to this method with a call
|
|
||||||
* to {@link #endFlatten} with the matching token.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
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 <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to the current location
|
|
||||||
* in the JSON value.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
public final String getPath() {
|
|
||||||
return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the tag value for the given class key. */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@CheckReturnValue
|
|
||||||
public final @Nullable <T> T tag(Class<T> clazz) {
|
|
||||||
if (tags == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (T) tags.get(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Assigns the tag value using the given class key and value. */
|
|
||||||
public final <T> void setTag(Class<T> clazz, T value) {
|
|
||||||
if (!clazz.isAssignableFrom(value.getClass())) {
|
|
||||||
throw new IllegalArgumentException("Tag value must be of type " + clazz.getName());
|
|
||||||
}
|
|
||||||
if (tags == null) {
|
|
||||||
tags = new LinkedHashMap<>();
|
|
||||||
}
|
|
||||||
tags.put(clazz, value);
|
|
||||||
}
|
|
||||||
}
|
|
538
moshi/src/main/java/com/squareup/moshi/JsonWriter.kt
Normal file
538
moshi/src/main/java/com/squareup/moshi/JsonWriter.kt
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
/*
|
||||||
|
* 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.squareup.moshi.JsonScope.EMPTY_ARRAY
|
||||||
|
import com.squareup.moshi.JsonScope.EMPTY_OBJECT
|
||||||
|
import com.squareup.moshi.JsonScope.NONEMPTY_ARRAY
|
||||||
|
import com.squareup.moshi.JsonScope.NONEMPTY_OBJECT
|
||||||
|
import okio.BufferedSink
|
||||||
|
import okio.BufferedSource
|
||||||
|
import okio.Closeable
|
||||||
|
import okio.IOException
|
||||||
|
import java.io.Flushable
|
||||||
|
import javax.annotation.CheckReturnValue
|
||||||
|
import kotlin.Throws
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a JSON ([RFC 7159](http://www.ietf.org/rfc/rfc7159.txt)) 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 `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 [beginArray]. Write each of the
|
||||||
|
* array's elements with the appropriate [value] methods or by nesting other arrays and
|
||||||
|
* objects. Finally close the array using [endArray].
|
||||||
|
* * To write **objects**, first call [beginObject]. Write each of the
|
||||||
|
* object's properties by alternating calls to [name] with the property's value. Write
|
||||||
|
* property values with the appropriate [value] method or by nesting other objects or
|
||||||
|
* arrays. Finally close the object using [endObject].
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* Suppose we'd like to encode a stream of messages such as the following:
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* "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:
|
||||||
|
*
|
||||||
|
* ```java
|
||||||
|
* public void writeJsonStream(BufferedSink sink, List<Message> messages) throws IOException {
|
||||||
|
* JsonWriter writer = JsonWriter.of(sink);
|
||||||
|
* writer.setIndent(" ");
|
||||||
|
* writeMessagesArray(writer, messages);
|
||||||
|
* writer.close();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void writeMessagesArray(JsonWriter writer, List<Message> 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<Double> doubles) throws IOException {
|
||||||
|
* writer.beginArray();
|
||||||
|
* for (Double value : doubles) {
|
||||||
|
* writer.value(value);
|
||||||
|
* }
|
||||||
|
* writer.endArray();
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Each `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 [IllegalStateException].
|
||||||
|
*/
|
||||||
|
public sealed class JsonWriter : 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.
|
||||||
|
*/
|
||||||
|
@JvmField
|
||||||
|
protected var stackSize: Int = 0
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected var scopes: IntArray = IntArray(32)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected var pathNames: Array<String?> = arrayOfNulls(32)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected var pathIndices: IntArray = IntArray(32)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string containing a full set of spaces for a single level of indentation, or null for no
|
||||||
|
* pretty printing.
|
||||||
|
*/
|
||||||
|
@JvmField
|
||||||
|
protected var _indent: String? = null
|
||||||
|
public open var indent: String
|
||||||
|
/**
|
||||||
|
* Returns a string containing only whitespace, used for each level of indentation. If empty,
|
||||||
|
* the encoded document will be compact.
|
||||||
|
*/
|
||||||
|
get() = _indent.orEmpty()
|
||||||
|
/**
|
||||||
|
* Sets the indentation string to be repeated for each level of indentation in the encoded
|
||||||
|
* document. If `indent.isEmpty()` the encoded document will be compact. Otherwise, the
|
||||||
|
* encoded document will be more human-readable.
|
||||||
|
*
|
||||||
|
* @param value a string containing only whitespace.
|
||||||
|
*/
|
||||||
|
set(value) {
|
||||||
|
_indent = value.ifEmpty { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure this writer to relax its syntax rules. By default, this writer only emits well-formed
|
||||||
|
* JSON as specified by [RFC 7159](http://www.ietf.org/rfc/rfc7159.txt). 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 [NaNs][Double.isNaN] or [infinities][Double.isInfinite].
|
||||||
|
*
|
||||||
|
* Returns true if this writer has relaxed syntax rules.
|
||||||
|
*/
|
||||||
|
@get:CheckReturnValue
|
||||||
|
public var isLenient: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether object members are serialized when their value is null. This has no impact on
|
||||||
|
* array elements. The default is false.
|
||||||
|
*
|
||||||
|
* Returns true if object members are serialized when their value is null. This has no impact on
|
||||||
|
* array elements. The default is false.
|
||||||
|
*/
|
||||||
|
@get:CheckReturnValue
|
||||||
|
public var serializeNulls: Boolean = false
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected var promoteValueToName: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 [beginFlatten] and restored
|
||||||
|
* with [endFlatten].
|
||||||
|
*/
|
||||||
|
@JvmField
|
||||||
|
protected var flattenStackSize: Int = -1
|
||||||
|
|
||||||
|
private var tags: MutableMap<Class<*>, Any>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [JsonPath](http://goessner.net/articles/JsonPath/) to the current location
|
||||||
|
* in the JSON value.
|
||||||
|
*/
|
||||||
|
@get:CheckReturnValue
|
||||||
|
public val path: String
|
||||||
|
get() = JsonScope.getPath(stackSize, scopes, pathNames, pathIndices)
|
||||||
|
|
||||||
|
/** Returns the scope on the top of the stack. */
|
||||||
|
protected fun peekScope(): Int {
|
||||||
|
check(stackSize != 0) { "JsonWriter is closed." }
|
||||||
|
return scopes[stackSize - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Before pushing a value on the stack this confirms that the stack has capacity. */
|
||||||
|
protected fun checkStack(): Boolean {
|
||||||
|
if (stackSize != scopes.size) return false
|
||||||
|
if (stackSize == 256) {
|
||||||
|
throw JsonDataException("Nesting too deep at $path: circular reference?")
|
||||||
|
}
|
||||||
|
scopes = scopes.copyOf(scopes.size * 2)
|
||||||
|
pathNames = pathNames.copyOf(pathNames.size * 2)
|
||||||
|
pathIndices = pathIndices.copyOf(pathIndices.size * 2)
|
||||||
|
if (this is JsonValueWriter) {
|
||||||
|
stack = stack.copyOf(stack.size * 2)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun pushScope(newTop: Int) {
|
||||||
|
scopes[stackSize++] = newTop
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Replace the value on the top of the stack with the given value. */
|
||||||
|
protected fun replaceTop(topOfStack: Int) {
|
||||||
|
scopes[stackSize - 1] = topOfStack
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins encoding a new array. Each call to this method must be paired with a call to [endArray].
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun beginArray(): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends encoding the current array.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun endArray(): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins encoding a new object. Each call to this method must be paired with a call to [endObject].
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun beginObject(): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends encoding the current object.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun endObject(): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the property name.
|
||||||
|
*
|
||||||
|
* @param name the name of the forthcoming value. Must not be null.
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun name(name: String): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `value`.
|
||||||
|
*
|
||||||
|
* @param value the literal string value, or null to encode a null literal.
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun value(value: String?): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `null`.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun nullValue(): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `value`.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun value(value: Boolean): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `value`.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun value(value: Boolean?): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `value`.
|
||||||
|
*
|
||||||
|
* @param value a finite value. May not be [NaNs][Double.isNaN] or [infinities][Double.isInfinite].
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun value(value: Double): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `value`.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun value(value: Long): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes `value`.
|
||||||
|
*
|
||||||
|
* @param value a finite value. May not be [NaNs][Double.isNaN] or [infinities][Double.isInfinite].
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun value(value: Number?): JsonWriter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes `source` directly without encoding its contents. Equivalent to
|
||||||
|
* ```java
|
||||||
|
* try (BufferedSink sink = writer.valueSink()) {
|
||||||
|
* source.readAll(sink):
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see valueSink
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public fun value(source: BufferedSource): JsonWriter {
|
||||||
|
check(!promoteValueToName) { "BufferedSource cannot be used as a map key in JSON at path $path" }
|
||||||
|
valueSink().use(source::readAll)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [BufferedSink] into which arbitrary data can be written without any additional
|
||||||
|
* encoding. You **must** call [BufferedSink.close] before interacting with this `JsonWriter` instance again.
|
||||||
|
*
|
||||||
|
* Since no validation is performed, options like [serializeNulls] and other writer
|
||||||
|
* configurations are not respected.
|
||||||
|
*/
|
||||||
|
@CheckReturnValue
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public abstract fun valueSink(): BufferedSink
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the value which may be a string, number, boolean, null, map, or list.
|
||||||
|
*
|
||||||
|
* @return this writer.
|
||||||
|
* @see JsonReader.readJsonValue
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public fun jsonValue(value: Any?): JsonWriter {
|
||||||
|
when (value) {
|
||||||
|
is Map<*, *> -> {
|
||||||
|
beginObject()
|
||||||
|
for ((k, v) in value) {
|
||||||
|
requireNotNull(k) { "Map keys must be non-null" }
|
||||||
|
require(k is String) { "Map keys must be of type String: ${k.javaClass.name}" }
|
||||||
|
name(k)
|
||||||
|
jsonValue(v)
|
||||||
|
}
|
||||||
|
endObject()
|
||||||
|
}
|
||||||
|
is List<*> -> {
|
||||||
|
beginArray()
|
||||||
|
for (element in value) {
|
||||||
|
jsonValue(element)
|
||||||
|
}
|
||||||
|
endArray()
|
||||||
|
}
|
||||||
|
is String -> value(value as String?)
|
||||||
|
is Boolean -> value(value)
|
||||||
|
is Double -> value(value)
|
||||||
|
is Long -> value(value)
|
||||||
|
is Number -> value(value)
|
||||||
|
null -> nullValue()
|
||||||
|
else -> throw IllegalArgumentException("Unsupported type: ${value.javaClass.name}")
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 [value] to write a name value.
|
||||||
|
*
|
||||||
|
* In this example, calling this method allows two sequential calls to [value]
|
||||||
|
* to produce the object, `{"a": "b"}`.
|
||||||
|
*
|
||||||
|
* ```java
|
||||||
|
* JsonWriter writer = JsonWriter.of(...);
|
||||||
|
* writer.beginObject();
|
||||||
|
* writer.promoteValueToName();
|
||||||
|
* writer.value("a");
|
||||||
|
* writer.value("b");
|
||||||
|
* writer.endObject();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
public fun promoteValueToName() {
|
||||||
|
val context = peekScope()
|
||||||
|
check(context == NONEMPTY_OBJECT || context == EMPTY_OBJECT) {
|
||||||
|
"Nesting problem."
|
||||||
|
}
|
||||||
|
promoteValueToName = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels immediately-nested calls to [beginArray] or [beginObject] and their
|
||||||
|
* matching calls to [endArray] or [endObject]. Use this to compose JSON adapters
|
||||||
|
* without nesting.
|
||||||
|
*
|
||||||
|
* For example, the following creates JSON with nested arrays: `[1,[2,3,4],5]`.
|
||||||
|
*
|
||||||
|
* ```java
|
||||||
|
* JsonAdapter<List<Integer>> 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 `[1,2,3,4,5]`:
|
||||||
|
*
|
||||||
|
* ```java
|
||||||
|
* JsonAdapter<List<Integer>> 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:
|
||||||
|
*
|
||||||
|
* Emit: `[1, [2, 3, 4], 5]`
|
||||||
|
* To produce: `[1, 2, 3, 4, 5]`
|
||||||
|
*
|
||||||
|
* It also flattens objects within objects. Do not call [name] before writing a flattened
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* 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:
|
||||||
|
*
|
||||||
|
* 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 [endFlatten] with the matching token.
|
||||||
|
*/
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun beginFlatten(): Int {
|
||||||
|
val context = peekScope()
|
||||||
|
check(context == NONEMPTY_OBJECT || context == EMPTY_OBJECT || context == NONEMPTY_ARRAY || context == EMPTY_ARRAY) {
|
||||||
|
"Nesting problem."
|
||||||
|
}
|
||||||
|
val token = flattenStackSize
|
||||||
|
flattenStackSize = stackSize
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ends nested call flattening created by [beginFlatten]. */
|
||||||
|
public fun endFlatten(token: Int) {
|
||||||
|
flattenStackSize = token
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the tag value for the given class key. */
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> tag(clazz: Class<T>): T? {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return tags?.get(clazz) as T?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assigns the tag value using the given class key and value. */
|
||||||
|
public fun <T : Any> setTag(clazz: Class<T>, value: T) {
|
||||||
|
require(clazz.isAssignableFrom(value::class.java)) {
|
||||||
|
"Tag value must be of type ${clazz.name}"
|
||||||
|
}
|
||||||
|
val localTags = tags ?: LinkedHashMap<Class<*>, Any>().also { tags = it }
|
||||||
|
localTags[clazz] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/** Returns a new instance that writes UTF-8 encoded JSON to `sink`. */
|
||||||
|
@JvmStatic
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun of(sink: BufferedSink): JsonWriter = JsonUtf8Writer(sink)
|
||||||
|
}
|
||||||
|
}
|
@@ -27,16 +27,16 @@ internal class MapJsonAdapter<K, V>(moshi: Moshi, keyType: Type, valueType: Type
|
|||||||
private val keyAdapter: JsonAdapter<K> = moshi.adapter(keyType)
|
private val keyAdapter: JsonAdapter<K> = moshi.adapter(keyType)
|
||||||
private val valueAdapter: JsonAdapter<V> = moshi.adapter(valueType)
|
private val valueAdapter: JsonAdapter<V> = moshi.adapter(valueType)
|
||||||
|
|
||||||
override fun toJson(writer: JsonWriter, map: Map<K, V?>?) {
|
override fun toJson(writer: JsonWriter, value: Map<K, V?>?) {
|
||||||
writer.beginObject()
|
writer.beginObject()
|
||||||
// Never null because we wrap in nullSafe()
|
// Never null because we wrap in nullSafe()
|
||||||
for ((key, value) in knownNotNull(map)) {
|
for ((k, v) in knownNotNull(value)) {
|
||||||
if (key == null) {
|
if (k == null) {
|
||||||
throw JsonDataException("Map key is null at " + writer.path)
|
throw JsonDataException("Map key is null at ${writer.path}")
|
||||||
}
|
}
|
||||||
writer.promoteValueToName()
|
writer.promoteValueToName()
|
||||||
keyAdapter.toJson(writer, key)
|
keyAdapter.toJson(writer, k)
|
||||||
valueAdapter.toJson(writer, value)
|
valueAdapter.toJson(writer, v)
|
||||||
}
|
}
|
||||||
writer.endObject()
|
writer.endObject()
|
||||||
}
|
}
|
||||||
|
@@ -63,7 +63,7 @@ public class Moshi internal constructor(builder: Builder) {
|
|||||||
}
|
}
|
||||||
val annotations = buildSet(annotationTypes.size) {
|
val annotations = buildSet(annotationTypes.size) {
|
||||||
for (annotationType in annotationTypes) {
|
for (annotationType in annotationTypes) {
|
||||||
add(createJsonQualifierImplementation(annotationType)!!)
|
add(createJsonQualifierImplementation(annotationType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return adapter(type, annotations)
|
return adapter(type, annotations)
|
||||||
|
Reference in New Issue
Block a user