Qualifiers: Different Encodings for the Same Type

Sometimes you need to encode the same data type in different ways within the same JSON object. For example, an int might represent a simple number in one field but a hex color string (e.g., "#ff0000") in another.

Moshi uses qualifier annotations to solve this problem. A qualifier is a custom annotation that you create, which is itself annotated with @JsonQualifier.

Example: A Hex Color Adapter

Let's build an adapter that serializes an int as a hex color string.

The Problem

Consider this class representing a rectangle:

data class Rectangle(
  val width: Int,
  val height: Int,
  val color: Int
)

By default, Moshi will serialize this to:

{
  "width": 1024,
  "height": 768,
  "color": 16711680
}

We want the color field to be a hex string like "#ff0000".

Step 1: Define a Qualifier Annotation

First, create your own annotation and annotate it with @JsonQualifier. The annotation must have RUNTIME retention.

import com.squareup.moshi.JsonQualifier
import kotlin.annotation.Retention

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class HexColor

Step 2: Apply the Qualifier to the Property

Next, apply your new @HexColor annotation to the color property.

data class Rectangle(
  val width: Int,
  val height: Int,
  @HexColor val color: Int
)

This tells Moshi that the color field requires a special adapter—one that is associated with the @HexColor qualifier.

Step 3: Create a Qualified Adapter

Now, create a custom adapter whose @ToJson and @FromJson methods are also annotated with @HexColor. This links the adapter to the property.

class ColorAdapter {
  @ToJson
  fun toJson(@HexColor rgb: Int): String {
    return "#%06x".format(rgb)
  }

  @FromJson
  @HexColor
  fun fromJson(rgb: String): Int {
    return rgb.substring(1).toInt(16)
  }
}
  • The @ToJson method takes an int annotated with @HexColor and returns a String.
  • The @FromJson method takes a String and returns an int, and the method itself is annotated with @HexColor to signify what it produces.

Step 4: Register the Adapter

Finally, add an instance of your ColorAdapter to your Moshi.Builder.

val moshi = Moshi.Builder()
    .add(ColorAdapter())
    .addLast(KotlinJsonAdapterFactory())
    .build()

val adapter = moshi.adapter(Rectangle::class.java)

Now, when Moshi serializes or deserializes a Rectangle, it will use the default Int adapter for width and height, but it will use your special ColorAdapter for the @HexColor-annotated color property.

Resulting JSON:

{
  "width": 1024,
  "height": 768,
  "color": "#ff0000"
}