mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49: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 toJson(writer: JsonWriter, value: T?) {
|
||||
val serializeNulls = writer.getSerializeNulls()
|
||||
writer.setSerializeNulls(true)
|
||||
val serializeNulls = writer.serializeNulls
|
||||
writer.serializeNulls = true
|
||||
try {
|
||||
delegate.toJson(writer, value)
|
||||
} finally {
|
||||
writer.setSerializeNulls(serializeNulls)
|
||||
writer.serializeNulls = serializeNulls
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,12 +262,12 @@ public abstract class JsonAdapter<T> {
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: T?) {
|
||||
val originalIndent = writer.getIndent()
|
||||
writer.setIndent(indent)
|
||||
val originalIndent = writer.indent
|
||||
writer.indent = indent
|
||||
try {
|
||||
delegate.toJson(writer, value)
|
||||
} finally {
|
||||
writer.setIndent(originalIndent)
|
||||
writer.indent = originalIndent
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -44,15 +44,17 @@ internal class JsonUtf8Writer(
|
||||
private var separator = ":"
|
||||
private var deferredName: String? = null
|
||||
|
||||
override var indent: String
|
||||
get() = super.indent
|
||||
set(value) {
|
||||
super.indent = value
|
||||
separator = if (value.isNotEmpty()) ": " else ":"
|
||||
}
|
||||
|
||||
init {
|
||||
pushScope(JsonScope.EMPTY_DOCUMENT)
|
||||
}
|
||||
|
||||
override fun setIndent(indent: String) {
|
||||
super.setIndent(indent)
|
||||
separator = if (indent.isNotEmpty()) ": " else ":"
|
||||
}
|
||||
|
||||
override fun beginArray(): JsonWriter {
|
||||
check(!promoteValueToName) { "Array cannot be used as a map key in JSON at path $path" }
|
||||
writeDeferredName()
|
||||
@@ -172,7 +174,7 @@ internal class JsonUtf8Writer(
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
if (promoteValueToName) {
|
||||
@@ -201,7 +203,7 @@ internal class JsonUtf8Writer(
|
||||
return nullValue()
|
||||
}
|
||||
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" }
|
||||
if (promoteValueToName) {
|
||||
promoteValueToName = false
|
||||
@@ -258,7 +260,7 @@ internal class JsonUtf8Writer(
|
||||
}
|
||||
|
||||
private fun newline() {
|
||||
if (indent == null) {
|
||||
if (_indent == null) {
|
||||
return
|
||||
}
|
||||
sink.writeByte('\n'.code)
|
||||
@@ -295,7 +297,7 @@ internal class JsonUtf8Writer(
|
||||
val nextTop: Int
|
||||
when (peekScope()) {
|
||||
JsonScope.NONEMPTY_DOCUMENT -> {
|
||||
if (!lenient) {
|
||||
if (!isLenient) {
|
||||
throw IllegalStateException("JSON must have only one top-level value.")
|
||||
}
|
||||
nextTop = JsonScope.NONEMPTY_DOCUMENT
|
||||
|
@@ -144,7 +144,7 @@ internal class JsonValueWriter : 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"
|
||||
}
|
||||
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 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()
|
||||
// Never null because we wrap in nullSafe()
|
||||
for ((key, value) in knownNotNull(map)) {
|
||||
if (key == null) {
|
||||
throw JsonDataException("Map key is null at " + writer.path)
|
||||
for ((k, v) in knownNotNull(value)) {
|
||||
if (k == null) {
|
||||
throw JsonDataException("Map key is null at ${writer.path}")
|
||||
}
|
||||
writer.promoteValueToName()
|
||||
keyAdapter.toJson(writer, key)
|
||||
valueAdapter.toJson(writer, value)
|
||||
keyAdapter.toJson(writer, k)
|
||||
valueAdapter.toJson(writer, v)
|
||||
}
|
||||
writer.endObject()
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@ public class Moshi internal constructor(builder: Builder) {
|
||||
}
|
||||
val annotations = buildSet(annotationTypes.size) {
|
||||
for (annotationType in annotationTypes) {
|
||||
add(createJsonQualifierImplementation(annotationType)!!)
|
||||
add(createJsonQualifierImplementation(annotationType))
|
||||
}
|
||||
}
|
||||
return adapter(type, annotations)
|
||||
|
Reference in New Issue
Block a user