Search code examples
androidkotlinkotlinx.serialization

JSON Serialization / Deserialization Kotlin Android Jetpack Compose Table


I'm in the process of creating a table section in Jetpack Compose that deserializes the data from a JSON.

At this point, I'm simply trying to display the data (the table formatting and clean up will come later, so you can ignore that part). Things have been going okay until I got to a part where the table row values are an array of arrays. I'm getting this error:

Error:

Expected class kotlinx.serialization.json.JsonObject as the serialized body of ...TableRow, but had class kotlinx.serialization.json.JsonArray

Here is the part of the test JSON that is being parsed for this table:

{
 "title": "TRANSACTION HISTORY",
 "type": "Table",
 "names": [
      "Date",
      "Amount",
      "Lender",
      "Borrower"
   ],
    "values": [
        [
           "07\/01\/2014",
           "$315,000",
           "Steve's Lending Co.",
           "Glenn Danzig"
         ],
         [
           "10\/13\/2011",
           "$236,000",
           "Feet Company",
           "Marcus Toeswater"
         ]
      ]
   },
   {
     "title": "ASSESSMENT & TAX",
     "type": "Table",
     "names": [
          "Year",
          "Property Taxes",
          "Tax Assessment"
     ],
     "values": [
          [
            "2017",
            "$6,068",
            "$395,000"
          ],
          [
            "2016",
            "$5,864",
            "$372,000"
          ],
          [
            "2015",
            "$5,609",
            "$341,500"
          ]
      ]
   },

Here's the code I have right now. I'm simply trying to post two rows of data: 1 with the column header names, and 1 with all of the row data (doesn't have to look nice at this point - I'm just trying to get the mapping done)

@Serializable
@SerialName("Table")
@Parcelize
data class TableSection(
    override val title: String? = null,
    @SerialName("names")
    val columnHeader: ArrayList<String?> = arrayListOf(),
    @SerialName("values")
    val tableData: ArrayList<TableRow> = arrayListOf(),
) : Section() {
    @Composable
    override fun Content(modifier: Modifier) {
        return Column {
            Row {
                columnHeader.map {
                    Text(it ?: "")
                }
            }
            Row {
                tableData.map { row ->
                    row.tableRowValues.map { value ->
                        Text(value ?: "")
                    }
                }
            }
        }
    }
}

@Serializable
@Parcelize
data class TableRow(
    @Serializable
    val tableRowValues: ArrayList<String?> = arrayListOf(),
) : Parcelable

Note: The Title and the Table Column Headers work just fine. It's the table data that is throwing the error.


Solution

  • I ended up creating a custom serializer to get the nested values:

    @Serializable(with = TableRowSerializer::class)
    @Parcelize
    data class TableRow(
        val tableRowValues: List<String?> = arrayListOf(),
    ) : Parcelable
    
    object TableRowSerializer : KSerializer<TableRow> {
        private val serializer = ListSerializer(String.serializer())
    
        override val descriptor: SerialDescriptor = serializer.descriptor
    
        override fun serialize(encoder: Encoder, value: TableRow) {
            val rowValues = value.tableRowValues
                rowValues.let{rowValues as? List<String>}
                    ?.let { encoder.encodeSerializableValue(serializer, it) }
        }
    
        override fun deserialize(decoder: Decoder): TableRow {
            return TableRow(decoder.decodeSerializableValue(serializer))
        }
    }