mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Moshi.Builder.addLast() (#1233)
This is mostly useful for KotlinJsonAdapterFactory.
This commit is contained in:
101
README.md
101
README.md
@@ -504,6 +504,99 @@ public final class BlackjackHand {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Composing Adapters
|
||||||
|
|
||||||
|
In some situations Moshi's default Java-to-JSON conversion isn't sufficient. You can compose
|
||||||
|
adapters to build upon the standard conversion.
|
||||||
|
|
||||||
|
In this example, we turn serialize nulls, then delegate to the built-in adapter:
|
||||||
|
|
||||||
|
```java
|
||||||
|
class TournamentWithNullsAdapter {
|
||||||
|
@ToJson void toJson(JsonWriter writer, Tournament tournament,
|
||||||
|
JsonAdapter<Tournament> delegate) throws IOException {
|
||||||
|
boolean wasSerializeNulls = writer.getSerializeNulls();
|
||||||
|
writer.setSerializeNulls(true);
|
||||||
|
try {
|
||||||
|
delegate.toJson(writer, tournament);
|
||||||
|
} finally {
|
||||||
|
writer.setLenient(wasSerializeNulls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When we use this to serialize a tournament, nulls are written! But nulls elsewhere in our JSON
|
||||||
|
document are skipped as usual.
|
||||||
|
|
||||||
|
Moshi has a powerful composition system in its `JsonAdapter.Factory` interface. We can hook in to
|
||||||
|
the encoding and decoding process for any type, even without knowing about the types beforehand. In
|
||||||
|
this example, we customize types annotated `@AlwaysSerializeNulls`, which an annotation we create,
|
||||||
|
not built-in to Moshi:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Target(TYPE)
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface AlwaysSerializeNulls {}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@AlwaysSerializeNulls
|
||||||
|
static class Car {
|
||||||
|
String make;
|
||||||
|
String model;
|
||||||
|
String color;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each `JsonAdapter.Factory` interface is invoked by `Moshi` when it needs to build an adapter for a
|
||||||
|
user's type. The factory either returns an adapter to use, or null if it doesn't apply to the
|
||||||
|
requested type. In our case we match all classes that have our annotation.
|
||||||
|
|
||||||
|
```java
|
||||||
|
static class AlwaysSerializeNullsFactory implements JsonAdapter.Factory {
|
||||||
|
@Override public JsonAdapter<?> create(
|
||||||
|
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||||
|
Class<?> rawType = Types.getRawType(type);
|
||||||
|
if (!rawType.isAnnotationPresent(AlwaysSerializeNulls.class)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonAdapter<Object> delegate = moshi.nextAdapter(this, type, annotations);
|
||||||
|
return delegate.serializeNulls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After determining that it applies, the factory looks up Moshi's built-in adapter by calling
|
||||||
|
`Moshi.nextAdapter()`. This is key to the composition mechanism: adapters delegate to each other!
|
||||||
|
The composition in this example is simple: it applies the `serializeNulls()` transform on the
|
||||||
|
delegate.
|
||||||
|
|
||||||
|
Composing adapters can be very sophisticated:
|
||||||
|
|
||||||
|
* An adapter could transform the input object before it is JSON-encoded. A string could be
|
||||||
|
trimmed or truncated; a value object could be simplified or normalized.
|
||||||
|
|
||||||
|
* An adapter could repair the output object after it is JSON-decoded. It could fill-in missing
|
||||||
|
data or discard unwanted data.
|
||||||
|
|
||||||
|
* The JSON could be given extra structure, such as wrapping values in objects or arrays.
|
||||||
|
|
||||||
|
Moshi is itself built on the pattern of repeatedly composing adapters. For example, Moshi's built-in
|
||||||
|
adapter for `List<T>` delegates to the adapter of `T`, and calls it repeatedly.
|
||||||
|
|
||||||
|
### Precedence
|
||||||
|
|
||||||
|
Moshi's composition mechanism tries to find the best adapter for each type. It starts with the first
|
||||||
|
adapter or factory registered with `Moshi.Builder.add()`, and proceeds until it finds an adapter for
|
||||||
|
the target type.
|
||||||
|
|
||||||
|
If a type can be matched multiple adapters, the earliest one wins.
|
||||||
|
|
||||||
|
To register an adapter at the end of the list, use `Moshi.Builder.addLast()` instead. This is most
|
||||||
|
useful when registering general-purpose adapters, such as the `KotlinJsonAdapterFactory` below.
|
||||||
|
|
||||||
Kotlin
|
Kotlin
|
||||||
------
|
------
|
||||||
|
|
||||||
@@ -517,14 +610,12 @@ JSON. Enable it by adding the `KotlinJsonAdapterFactory` to your `Moshi.Builder`
|
|||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val moshi = Moshi.Builder()
|
val moshi = Moshi.Builder()
|
||||||
// ... add your own JsonAdapters and factories ...
|
.addLast(KotlinJsonAdapterFactory())
|
||||||
.add(KotlinJsonAdapterFactory())
|
|
||||||
.build()
|
.build()
|
||||||
```
|
```
|
||||||
|
|
||||||
Moshi’s adapters are ordered by precedence, so you always want to add the Kotlin adapter after your
|
Moshi’s adapters are ordered by precedence, so you should use `addLast()` with
|
||||||
own custom adapters. Otherwise the `KotlinJsonAdapterFactory` will take precedence and your custom
|
`KotlinJsonAdapterFactory`, and `add()` with your custom adapters.
|
||||||
adapters will not be called.
|
|
||||||
|
|
||||||
The reflection adapter requires the following additional dependency:
|
The reflection adapter requires the following additional dependency:
|
||||||
|
|
||||||
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Square, 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.recipes;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.Types;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class IncludeNullsForAnnotatedTypes {
|
||||||
|
public void run() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder().add(new AlwaysSerializeNullsFactory()).build();
|
||||||
|
|
||||||
|
JsonAdapter<Driver> driverAdapter = moshi.adapter(Driver.class);
|
||||||
|
|
||||||
|
Car car = new Car();
|
||||||
|
car.make = "Ford";
|
||||||
|
car.model = "Mach-E";
|
||||||
|
car.color = null; // This null will show up in the JSON because Car has @AlwaysSerializeNulls.
|
||||||
|
|
||||||
|
Driver driver = new Driver();
|
||||||
|
driver.name = "Jesse";
|
||||||
|
driver.emailAddress = null; // This null will be omitted.
|
||||||
|
driver.favoriteCar = car;
|
||||||
|
|
||||||
|
System.out.println(driverAdapter.toJson(driver));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Target(TYPE)
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface AlwaysSerializeNulls {}
|
||||||
|
|
||||||
|
@AlwaysSerializeNulls
|
||||||
|
static class Car {
|
||||||
|
String make;
|
||||||
|
String model;
|
||||||
|
String color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Driver {
|
||||||
|
String name;
|
||||||
|
String emailAddress;
|
||||||
|
Car favoriteCar;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AlwaysSerializeNullsFactory implements JsonAdapter.Factory {
|
||||||
|
@Override
|
||||||
|
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||||
|
Class<?> rawType = Types.getRawType(type);
|
||||||
|
if (!rawType.isAnnotationPresent(AlwaysSerializeNulls.class)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonAdapter<Object> delegate = moshi.nextAdapter(this, type, annotations);
|
||||||
|
return delegate.serializeNulls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
new IncludeNullsForAnnotatedTypes().run();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Square, 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.recipes;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.JsonWriter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.ToJson;
|
||||||
|
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter;
|
||||||
|
import com.squareup.moshi.recipes.models.Tournament;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public final class IncludeNullsForOneType {
|
||||||
|
public void run() throws Exception {
|
||||||
|
Moshi moshi =
|
||||||
|
new Moshi.Builder()
|
||||||
|
.add(Date.class, new Rfc3339DateJsonAdapter())
|
||||||
|
.add(new TournamentWithNullsAdapter())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JsonAdapter<Tournament> tournamentAdapter = moshi.adapter(Tournament.class);
|
||||||
|
|
||||||
|
// Moshi normally skips nulls, but with our adapter registered they are emitted.
|
||||||
|
Tournament withNulls = new Tournament("Waterloo Classic", null, null);
|
||||||
|
System.out.println(tournamentAdapter.toJson(withNulls));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class TournamentWithNullsAdapter {
|
||||||
|
@ToJson
|
||||||
|
void toJson(JsonWriter writer, Tournament tournament, JsonAdapter<Tournament> delegate)
|
||||||
|
throws IOException {
|
||||||
|
boolean wasSerializeNulls = writer.getSerializeNulls();
|
||||||
|
writer.setSerializeNulls(true);
|
||||||
|
try {
|
||||||
|
// Once we've customized the JSON writer, we let the default JSON adapter do its job.
|
||||||
|
delegate.toJson(writer, tournament);
|
||||||
|
} finally {
|
||||||
|
writer.setLenient(wasSerializeNulls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
new IncludeNullsForOneType().run();
|
||||||
|
}
|
||||||
|
}
|
@@ -55,6 +55,7 @@ public final class Moshi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final List<JsonAdapter.Factory> factories;
|
private final List<JsonAdapter.Factory> factories;
|
||||||
|
private final int lastOffset;
|
||||||
private final ThreadLocal<LookupChain> lookupChainThreadLocal = new ThreadLocal<>();
|
private final ThreadLocal<LookupChain> lookupChainThreadLocal = new ThreadLocal<>();
|
||||||
private final Map<Object, JsonAdapter<?>> adapterCache = new LinkedHashMap<>();
|
private final Map<Object, JsonAdapter<?>> adapterCache = new LinkedHashMap<>();
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ public final class Moshi {
|
|||||||
factories.addAll(builder.factories);
|
factories.addAll(builder.factories);
|
||||||
factories.addAll(BUILT_IN_FACTORIES);
|
factories.addAll(BUILT_IN_FACTORIES);
|
||||||
this.factories = Collections.unmodifiableList(factories);
|
this.factories = Collections.unmodifiableList(factories);
|
||||||
|
this.lastOffset = builder.lastOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a JSON adapter for {@code type}, creating it if necessary. */
|
/** Returns a JSON adapter for {@code type}, creating it if necessary. */
|
||||||
@@ -181,10 +183,14 @@ public final class Moshi {
|
|||||||
/** Returns a new builder containing all custom factories used by the current instance. */
|
/** Returns a new builder containing all custom factories used by the current instance. */
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
public Moshi.Builder newBuilder() {
|
public Moshi.Builder newBuilder() {
|
||||||
int fullSize = factories.size();
|
Builder result = new Builder();
|
||||||
int tailSize = BUILT_IN_FACTORIES.size();
|
for (int i = 0, limit = lastOffset; i < limit; i++) {
|
||||||
List<JsonAdapter.Factory> customFactories = factories.subList(0, fullSize - tailSize);
|
result.add(factories.get(i));
|
||||||
return new Builder().addAll(customFactories);
|
}
|
||||||
|
for (int i = lastOffset, limit = factories.size() - BUILT_IN_FACTORIES.size(); i < limit; i++) {
|
||||||
|
result.addLast(factories.get(i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an opaque object that's equal if the type and annotations are equal. */
|
/** Returns an opaque object that's equal if the type and annotations are equal. */
|
||||||
@@ -195,55 +201,20 @@ public final class Moshi {
|
|||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
final List<JsonAdapter.Factory> factories = new ArrayList<>();
|
final List<JsonAdapter.Factory> factories = new ArrayList<>();
|
||||||
|
int lastOffset = 0;
|
||||||
|
|
||||||
public <T> Builder add(final Type type, final JsonAdapter<T> jsonAdapter) {
|
public <T> Builder add(Type type, JsonAdapter<T> jsonAdapter) {
|
||||||
if (type == null) throw new IllegalArgumentException("type == null");
|
return add(newAdapterFactory(type, jsonAdapter));
|
||||||
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
|
||||||
|
|
||||||
return add(
|
|
||||||
new JsonAdapter.Factory() {
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonAdapter<?> create(
|
|
||||||
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
|
||||||
return annotations.isEmpty() && Util.typesMatch(type, targetType)
|
|
||||||
? jsonAdapter
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> Builder add(
|
public <T> Builder add(
|
||||||
final Type type,
|
Type type, Class<? extends Annotation> annotation, JsonAdapter<T> jsonAdapter) {
|
||||||
final Class<? extends Annotation> annotation,
|
return add(newAdapterFactory(type, annotation, jsonAdapter));
|
||||||
final JsonAdapter<T> jsonAdapter) {
|
|
||||||
if (type == null) throw new IllegalArgumentException("type == null");
|
|
||||||
if (annotation == null) throw new IllegalArgumentException("annotation == null");
|
|
||||||
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
|
||||||
if (!annotation.isAnnotationPresent(JsonQualifier.class)) {
|
|
||||||
throw new IllegalArgumentException(annotation + " does not have @JsonQualifier");
|
|
||||||
}
|
|
||||||
if (annotation.getDeclaredMethods().length > 0) {
|
|
||||||
throw new IllegalArgumentException("Use JsonAdapter.Factory for annotations with elements");
|
|
||||||
}
|
|
||||||
|
|
||||||
return add(
|
|
||||||
new JsonAdapter.Factory() {
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonAdapter<?> create(
|
|
||||||
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
|
||||||
if (Util.typesMatch(type, targetType)
|
|
||||||
&& annotations.size() == 1
|
|
||||||
&& Util.isAnnotationPresent(annotations, annotation)) {
|
|
||||||
return jsonAdapter;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder add(JsonAdapter.Factory factory) {
|
public Builder add(JsonAdapter.Factory factory) {
|
||||||
if (factory == null) throw new IllegalArgumentException("factory == null");
|
if (factory == null) throw new IllegalArgumentException("factory == null");
|
||||||
factories.add(factory);
|
factories.add(lastOffset++, factory);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,17 +223,74 @@ public final class Moshi {
|
|||||||
return add(AdapterMethodsFactory.get(adapter));
|
return add(AdapterMethodsFactory.get(adapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
Builder addAll(List<JsonAdapter.Factory> factories) {
|
public <T> Builder addLast(Type type, JsonAdapter<T> jsonAdapter) {
|
||||||
this.factories.addAll(factories);
|
return addLast(newAdapterFactory(type, jsonAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Builder addLast(
|
||||||
|
Type type, Class<? extends Annotation> annotation, JsonAdapter<T> jsonAdapter) {
|
||||||
|
return addLast(newAdapterFactory(type, annotation, jsonAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addLast(JsonAdapter.Factory factory) {
|
||||||
|
if (factory == null) throw new IllegalArgumentException("factory == null");
|
||||||
|
factories.add(factory);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder addLast(Object adapter) {
|
||||||
|
if (adapter == null) throw new IllegalArgumentException("adapter == null");
|
||||||
|
return addLast(AdapterMethodsFactory.get(adapter));
|
||||||
|
}
|
||||||
|
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
public Moshi build() {
|
public Moshi build() {
|
||||||
return new Moshi(this);
|
return new Moshi(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static <T> JsonAdapter.Factory newAdapterFactory(
|
||||||
|
final Type type, final JsonAdapter<T> jsonAdapter) {
|
||||||
|
if (type == null) throw new IllegalArgumentException("type == null");
|
||||||
|
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
||||||
|
|
||||||
|
return new JsonAdapter.Factory() {
|
||||||
|
@Override
|
||||||
|
public @Nullable JsonAdapter<?> create(
|
||||||
|
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||||
|
return annotations.isEmpty() && Util.typesMatch(type, targetType) ? jsonAdapter : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> JsonAdapter.Factory newAdapterFactory(
|
||||||
|
final Type type,
|
||||||
|
final Class<? extends Annotation> annotation,
|
||||||
|
final JsonAdapter<T> jsonAdapter) {
|
||||||
|
if (type == null) throw new IllegalArgumentException("type == null");
|
||||||
|
if (annotation == null) throw new IllegalArgumentException("annotation == null");
|
||||||
|
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
||||||
|
if (!annotation.isAnnotationPresent(JsonQualifier.class)) {
|
||||||
|
throw new IllegalArgumentException(annotation + " does not have @JsonQualifier");
|
||||||
|
}
|
||||||
|
if (annotation.getDeclaredMethods().length > 0) {
|
||||||
|
throw new IllegalArgumentException("Use JsonAdapter.Factory for annotations with elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonAdapter.Factory() {
|
||||||
|
@Override
|
||||||
|
public @Nullable JsonAdapter<?> create(
|
||||||
|
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||||
|
if (Util.typesMatch(type, targetType)
|
||||||
|
&& annotations.size() == 1
|
||||||
|
&& Util.isAnnotationPresent(annotations, annotation)) {
|
||||||
|
return jsonAdapter;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A possibly-reentrant chain of lookups for JSON adapters.
|
* A possibly-reentrant chain of lookups for JSON adapters.
|
||||||
*
|
*
|
||||||
|
@@ -1269,6 +1269,68 @@ public final class MoshiTest {
|
|||||||
assertThat(adapter.fromJson(json)).isEqualTo(new Pizza(5, true));
|
assertThat(adapter.fromJson(json)).isEqualTo(new Pizza(5, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void precedence() throws Exception {
|
||||||
|
Moshi moshi =
|
||||||
|
new Moshi.Builder()
|
||||||
|
.add(new AppendingAdapterFactory(" a"))
|
||||||
|
.addLast(new AppendingAdapterFactory(" y"))
|
||||||
|
.add(new AppendingAdapterFactory(" b"))
|
||||||
|
.addLast(new AppendingAdapterFactory(" z"))
|
||||||
|
.build();
|
||||||
|
JsonAdapter<String> adapter = moshi.adapter(String.class).lenient();
|
||||||
|
assertThat(adapter.toJson("hello")).isEqualTo("\"hello a b y z\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void precedenceWithNewBuilder() throws Exception {
|
||||||
|
Moshi moshi1 =
|
||||||
|
new Moshi.Builder()
|
||||||
|
.add(new AppendingAdapterFactory(" a"))
|
||||||
|
.addLast(new AppendingAdapterFactory(" w"))
|
||||||
|
.add(new AppendingAdapterFactory(" b"))
|
||||||
|
.addLast(new AppendingAdapterFactory(" x"))
|
||||||
|
.build();
|
||||||
|
Moshi moshi2 =
|
||||||
|
moshi1
|
||||||
|
.newBuilder()
|
||||||
|
.add(new AppendingAdapterFactory(" c"))
|
||||||
|
.addLast(new AppendingAdapterFactory(" y"))
|
||||||
|
.add(new AppendingAdapterFactory(" d"))
|
||||||
|
.addLast(new AppendingAdapterFactory(" z"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JsonAdapter<String> adapter = moshi2.adapter(String.class).lenient();
|
||||||
|
assertThat(adapter.toJson("hello")).isEqualTo("\"hello a b c d w x y z\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a suffix to a string before emitting it. */
|
||||||
|
static final class AppendingAdapterFactory implements JsonAdapter.Factory {
|
||||||
|
private final String suffix;
|
||||||
|
|
||||||
|
AppendingAdapterFactory(String suffix) {
|
||||||
|
this.suffix = suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||||
|
if (type != String.class) return null;
|
||||||
|
|
||||||
|
final JsonAdapter<String> delegate = moshi.nextAdapter(this, type, annotations);
|
||||||
|
return new JsonAdapter<String>() {
|
||||||
|
@Override
|
||||||
|
public String fromJson(JsonReader reader) throws IOException {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toJson(JsonWriter writer, String value) throws IOException {
|
||||||
|
delegate.toJson(writer, value + suffix);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class Pizza {
|
static class Pizza {
|
||||||
final int diameter;
|
final int diameter;
|
||||||
final boolean extraCheese;
|
final boolean extraCheese;
|
||||||
|
Reference in New Issue
Block a user