Example: Unwrapping Enveloped JSON

APIs often wrap their responses in a common "envelope" structure, such as a data object. For example:

{
  "data": {
    "rank": "4",
    "suit": "CLUBS"
  }
}

While your networking layer might need to know about the envelope, your application models often only care about the content inside data. Manually unwrapping this everywhere can be tedious. This example shows how to create a generic JsonAdapter.Factory to handle this automatically.

The Goal

We want to be able to request an adapter for Card.class and have it automatically handle the data envelope, so we can parse the above JSON directly into a Card object.

The Solution: A Custom Factory and Qualifier

We'll create a @JsonQualifier annotation to mark when we want to unwrap a response and a JsonAdapter.Factory to implement the logic.

1. Define the Qualifier

This annotation will signal our factory to apply the unwrapping logic.

@Retention(RUNTIME)
@JsonQualifier
public @interface Enveloped {}

2. Define the Envelope Structure

Create a generic class that represents the envelope structure.

private static final class Envelope<T> {
    final T data;
}

3. Create the EnvelopeJsonAdapter.Factory

The factory's create method will check for our @Enveloped annotation. If it's present, it will:

  1. Get an adapter for the Envelope<T> type itself.
  2. Create a new JsonAdapter that uses the envelope adapter to decode the full structure but only returns the inner data field.
public static final class EnvelopeJsonAdapter extends JsonAdapter<Object> {
    public static final JsonAdapter.Factory FACTORY = new Factory() {
        @Override
        public @Nullable JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
            Set<? extends Annotation> delegateAnnotations = Types.nextAnnotations(annotations, Enveloped.class);
            if (delegateAnnotations == null) {
                return null; // Not our annotation, move on
            }

            // Get an adapter for the full Envelope<T> type
            Type envelopeType = Types.newParameterizedTypeWithOwner(
                EnvelopeJsonAdapter.class, Envelope.class, type);
            JsonAdapter<Envelope<?>> delegate = moshi.nextAdapter(this, envelopeType, delegateAnnotations);

            return new EnvelopeJsonAdapter(delegate);
        }
    };

    private final JsonAdapter<Envelope<?>> delegate;

    EnvelopeJsonAdapter(JsonAdapter<Envelope<?>> delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object fromJson(JsonReader reader) throws IOException {
        // Decode the envelope, but only return the 'data' part
        return delegate.fromJson(reader).data;
    }

    @Override
    public void toJson(JsonWriter writer, Object value) throws IOException {
        // Wrap the value in an envelope before serializing
        delegate.toJson(writer, new Envelope<>(value));
    }
}

4. Usage

First, build a Moshi instance with the new factory.

Moshi moshi = new Moshi.Builder()
    .add(EnvelopeJsonAdapter.FACTORY)
    .build();

Now, when you need to parse an enveloped response, request the adapter with the @Enveloped qualifier.

JsonAdapter<Card> adapter = moshi.adapter(Card.class, Enveloped.class);
Card card = adapter.fromJson("{\"data\":{\"rank\":\"4\",\"suit\":\"CLUBS\"}}");

System.out.println(card); // Output: 4CLUBS

This powerful pattern allows you to cleanly separate API structural concerns (like envelopes) from your core application models.