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 JsonAdapter
s 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
: TheMoshi
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.