For a JSON like this consider the properties other than attributes
are dynamic:
"records": [
{
"attributes": {
"type": "...",
"url": "..."
},
"Id": "...",
"Name": "...",
"...": "..."
}
]
How can I Desrialize or Unmarshall into a Dataclass like this such that all the dynamic keys go into a recordBody: Map<String, Any>
@JsonClass(generateAdapter = true)
data class Body(
val records: List<Record>,
)
@JsonClass(generateAdapter = true)
data class Record(
val attributes: Attributes,
val recordBody: Map<String, Any>
)
@JsonClass(generateAdapter = true)
data class Attributes(
val type: String,
val url: String
)
I cannot find an annotation similar to @JsonAnySetter
The current answer assumes that the "attributes" property will always be the first property and that the recordBody map values are always strings (that doesn't seem to be the case in the original question?), and it also could use the selectName
and other Moshi JsonReader features.
Here's my take on a more resilient adapter.
@JsonClass(generateAdapter = true)
data class Body(
val records: List<Record>
)
@JsonClass(generateAdapter = true)
data class Record(
val attributes: Attributes,
val recordBody: Map<String, Any>
)
@JsonClass(generateAdapter = true)
data class Attributes(
val type: String,
val url: String
)
object RecordAdapter {
val options = JsonReader.Options.of("attributes")
@FromJson
fun fromJson(reader: JsonReader, attributesJsonAdapter: JsonAdapter<Attributes>): Record {
reader.beginObject()
var attributes: Attributes? = null
val recordBody = mutableMapOf<String, Any>()
while (reader.hasNext()) {
when (reader.selectName(options)) {
0 -> {
if (attributes != null) {
throw JsonDataException("Duplicate attributes.")
}
attributes = attributesJsonAdapter.fromJson(reader)
}
-1 -> {
recordBody[reader.nextName()] = reader.readJsonValue()!!
}
else -> {
throw AssertionError()
}
}
}
reader.endObject()
return Record(attributes!!, recordBody)
}
@ToJson
fun toJson(
writer: JsonWriter,
value: Record,
attributesJsonAdapter: JsonAdapter<Attributes>,
dynamicJsonAdapter: JsonAdapter<Any>
) {
writer.beginObject()
writer.name("attributes")
attributesJsonAdapter.toJson(writer, value.attributes)
for (entry in value.recordBody.entries) {
writer.name(entry.key)
dynamicJsonAdapter.toJson(writer, entry.value)
}
writer.endObject()
}
}
fun main() {
val moshi = Moshi.Builder().add(RecordAdapter).build()
val idResponseJsonAdapter = moshi.adapter(Body::class.java)
val encoded = """
{
"records": [
{
"attributes": {
"type": "...",
"url": "..."
},
"Id": "...",
"Name": "...",
"...": "..."
}
]
}""".trimIndent()
val decoded = Body(
listOf(
Record(
Attributes(
type = "...",
url = "..."
),
mapOf(
"Id" to "...",
"Name" to "...",
"..." to "..."
)
)
)
)
println(idResponseJsonAdapter.fromJson(encoded))
println(idResponseJsonAdapter.toJson(decoded))
}
Prints:
Body(records=[Record(attributes=Attributes(type=..., url=...), recordBody={Id=..., Name=..., ...=...})])
{"records":[{"attributes":{"type":"...","url":"..."},"Id":"...","Name":"...","...":"..."}]}