Example: Handling Raw JSON Values

Sometimes you need to treat a part of a JSON document as a raw, unparsed string. This is useful when a field can contain arbitrary JSON that you want to store or forward without needing to model it with specific types.

Moshi supports this use case with JsonReader.nextSource() and a custom JsonAdapter.

The Goal

We have a data class where one field, rawJson, should hold the raw JSON string of the corresponding object in the input document.

@JsonClass(generateAdapter = true)
data class ExampleClass(
  val type: Int,
  @JsonString val rawJson: String, // This will hold raw JSON
)

Input JSON:

{"type":1,"rawJson":{"a":2,"b":3,"c":[1,2,3]}}

Desired rawJson property value:

{"a":2,"b":3,"c":[1,2,3]}

The Solution: A Qualifier and a Factory

We'll create a @JsonString qualifier to mark the property and a JsonAdapter.Factory to implement the logic.

1. Define the Qualifier

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

2. Create the JsonStringJsonAdapterFactory

This factory creates a special adapter for String properties annotated with @JsonString.

  • fromJson: It uses reader.nextSource() to get a BufferedSource for the next value and reads its entire content as a UTF-8 string. This captures the value exactly as it appears in the source, including braces, brackets, and quotes.
  • toJson: It uses writer.valueSink() to get a BufferedSink and writes the string value directly, without adding extra quotes or escaping.
class JsonStringJsonAdapterFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
    if (type != String::class.java) return null
    Types.nextAnnotations(annotations, JsonString::class.java) ?: return null
    return JsonStringJsonAdapter().nullSafe()
  }

  private class JsonStringJsonAdapter : JsonAdapter<String>() {
    override fun fromJson(reader: JsonReader): String =
      reader.nextSource().use(BufferedSource::readUtf8)

    override fun toJson(writer: JsonWriter, value: String?) {
      writer.valueSink().use { sink -> sink.writeUtf8(checkNotNull(value)) }
    }
  }
}

3. Usage

Register the factory with Moshi and use the @JsonString annotation on your property.

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

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

val json = """{"type":1,"rawJson":{"a":2,"b":3,"c":[1,2,3]}}"""
val example = adapter.fromJson(json)!!

println(example.type)      // Output: 1
println(example.rawJson) // Output: {"a":2,"b":3,"c":[1,2,3]}