Streaming APIs: JsonReader and JsonWriter
While Moshi's data binding via JsonAdapter
is powerful and convenient for most use cases, sometimes you need lower-level access to the JSON stream. This can be for performance reasons, handling massive JSON documents, or implementing custom logic that doesn't map cleanly to objects.
Moshi provides two core classes for this: JsonReader
for parsing and JsonWriter
for generating JSON.
JsonReader
JsonReader
allows you to read a JSON-encoded value as a stream of tokens. The stream includes literal values (strings, numbers, booleans, nulls) and the begin/end delimiters of objects and arrays.
You can process a JSON document token by token:
val json = """{
"id": 912345678901,
"text": "How do I read a JSON stream?",
"user": {
"name": "json_newb",
"followers_count": 41
}
}"""
val reader = JsonReader.of(Buffer().writeUtf8(json))
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"id" -> println(reader.nextLong())
"text" -> println(reader.nextString())
"user" -> {
reader.beginObject()
while(reader.hasNext()) {
when (reader.nextName()) {
"name" -> println(reader.nextString())
"followers_count" -> println(reader.nextInt())
else -> reader.skipValue()
}
}
reader.endObject()
}
else -> reader.skipValue() // Important to skip unknown names
}
}
reader.endObject()
JsonReader
is a powerful tool for manual parsing and is the foundation upon which JsonAdapter
s are built.
Reading Raw JSON with nextSource()
A particularly useful method on JsonReader
is nextSource()
. It returns an Okio BufferedSource
that streams the raw UTF-8 bytes of the next JSON value. This is extremely efficient for:
- Extracting an embedded JSON document without parsing it.
- Passing a JSON value to another library or system.
- Processing a very large value (like a base64-encoded file) as a stream.
See the Raw JSON Values example for a practical use case.
JsonWriter
JsonWriter
allows you to write a JSON-encoded value token by token.
val buffer = Buffer()
val writer = JsonWriter.of(buffer)
writer.beginObject()
writer.name("id").value(912345678901)
writer.name("text").value("How do I write a JSON stream?")
writer.name("user")
writer.beginObject()
writer.name("name").value("json_pro")
writer.name("followers_count").value(99)
writer.endObject()
writer.endObject()
println(buffer.readUtf8())
// Output: {"id":912345678901,"text":"How do I write a JSON stream?","user":{"name":"json_pro","followers_count":99}}
Writing Raw JSON with value(BufferedSource)
Symmetrical to JsonReader.nextSource()
, JsonWriter.value(BufferedSource)
allows you to write raw, pre-encoded JSON bytes directly into the stream. This is useful for embedding a JSON document that you already have as a string or byte stream without parsing and re-serializing it.
val rawJson = "{\"nested\":true}"
writer.beginObject()
writer.name("data")
writer.value(Buffer().writeUtf8(rawJson))
writer.endObject()
// Output: {"data":{"nested":true}}