Example: Handling Multiple JSON Formats for a Single Type
Sometimes an API may provide the same type of data in different JSON structures. For example, a Card
could be represented as a compact string ("5D"
) or as a structured object ({"rank":"5","suit":"DIAMONDS"}
).
This example demonstrates how to create a JsonAdapter
that can decode both formats but always encodes to the compact string format.
The Challenge
We want to parse both of these JSON snippets into the same Card
object:
Format 1: Compact String
"5D"
Format 2: Object
{"rank":"5","suit":"DIAMONDS"}
When we serialize a Card
object, we always want to produce the compact string format.
The Solution: A Delegating Adapter
We can solve this by creating a main adapter that checks the type of the incoming JSON token. If it's a string, it delegates to a string-based adapter. If it's an object, it delegates to Moshi's default object adapter.
We'll use a @JsonQualifier
to distinguish between our string-only adapter and the default adapter.
1. Define the Qualifier
@Retention(RetentionPolicy.RUNTIME)
@JsonQualifier
@interface CardString {}
2. Create the String-Only Adapter
This adapter is qualified with @CardString
and only knows how to handle the compact string format.
public final class CardStringAdapter {
@ToJson
String toJson(@CardString Card card) {
return card.rank + card.suit.name().substring(0, 1);
}
@FromJson @CardString
Card fromJson(String card) {
// ... logic to parse "5D" into a Card object ...
}
}
3. Create the Main Delegating Adapter
This adapter is the primary adapter for the Card
type. Its fromJson
method uses reader.peek()
to decide which delegate adapter to use.
public final class MultipleFormatsCardAdapter {
@ToJson
void toJson(JsonWriter writer, Card value, @CardString JsonAdapter<Card> stringAdapter)
throws IOException {
// Always encode to the string format
stringAdapter.toJson(writer, value);
}
@FromJson
Card fromJson(
JsonReader reader,
@CardString JsonAdapter<Card> stringAdapter, // Our special string adapter
JsonAdapter<Card> defaultAdapter // Moshi's default object adapter
) throws IOException {
if (reader.peek() == JsonReader.Token.STRING) {
return stringAdapter.fromJson(reader);
} else {
return defaultAdapter.fromJson(reader);
}
}
}
4. Register the Adapters
Register all the necessary adapters with Moshi.
Moshi moshi = new Moshi.Builder()
.add(new MultipleFormatsCardAdapter()) // Main adapter
.add(new CardStringAdapter()) // String-only adapter
.build();
JsonAdapter<Card> cardAdapter = moshi.adapter(Card.class);
Now cardAdapter
can seamlessly handle both JSON formats for decoding and will consistently produce the compact string format for encoding.