Composing Adapters with Factories

While @ToJson and @FromJson methods are great for simple adapters, JsonAdapter.Factory is Moshi's most powerful tool for customization. A factory is an object that creates JsonAdapters for types it supports.

Moshi's core functionality is built on a chain of factories. When you request an adapter with moshi.adapter(type), Moshi iterates through its registered factories, asking each one if it can create an adapter for the requested type. The first factory that returns a non-null adapter wins.

The JsonAdapter.Factory Interface

A factory has a single method to implement:

interface Factory {
  fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>?
}
  • type: The type for which an adapter is being requested (e.g., List<String>).
  • annotations: Any qualifier annotations on the field or parameter.
  • moshi: The Moshi instance, which you can use to get adapters for other types.

If your factory doesn't support the given type and annotations, it should return null so Moshi can continue to the next factory.

Composing Adapters: moshi.nextAdapter()

The real power of factories comes from composition. A factory can request the next adapter in the chain for the same type. This allows you to delegate the core serialization logic and add behavior before or after it.

You do this by calling moshi.nextAdapter(this, type, annotations).

Example: A Factory to Serialize Nulls for Annotated Types

Let's create a factory that enables serializeNulls() for any type annotated with a custom @AlwaysSerializeNulls annotation.

1. Define the Annotation

@Retention(AnnotationRetention.RUNTIME)
annotation class AlwaysSerializeNulls

2. Create the Factory

class AlwaysSerializeNullsFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
    // Check if our annotation is present.
    val rawType: Class<*> = type.rawType
    if (!rawType.isAnnotationPresent(AlwaysSerializeNulls::class.java)) {
      return null // We don't support this type. Next factory in the chain, please.
    }

    // Get the next adapter in the chain for this type.
    // We pass 'this' to skip our own factory and avoid an infinite loop.
    val delegate: JsonAdapter<Any> = moshi.nextAdapter(this, type, annotations)

    // Wrap the delegate with our custom behavior.
    return delegate.serializeNulls()
  }
}

3. Register the Factory

val moshi = Moshi.Builder()
    .add(AlwaysSerializeNullsFactory())
    .build()

4. Use it

Now, any class annotated with @AlwaysSerializeNulls will have its null properties included in the JSON output.

@AlwaysSerializeNulls
data class Car(val make: String?, val model: String?, val color: String?)

val car = Car("Ford", "Mach-E", null)
val adapter = moshi.adapter(Car::class.java)

// Output: {"make":"Ford","model":"Mach-E","color":null}
adapter.toJson(car)

This pattern of checking for a type/annotation, delegating with nextAdapter(), and then wrapping the delegate is the foundation for creating powerful, reusable, and composable adapters.