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 usesreader.nextSource()
to get aBufferedSource
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 useswriter.valueSink()
to get aBufferedSink
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]}