With all the posts out there about Moshi and people's confusion (including my own) about how to parse a list, I can't believe that this is this difficult. I have an object that has two nested lists. I can't get either of them to parse.
{
"respondent_id": "1",
"completion_status": "completed",
"date_modified": "2023-12-05T17:41:49Z",
"date_start": "2023-12-05T17:41:41Z",
"responses": [
{
"page_index": 0,
"page_id": "4771",
"question_id": "1645",
"question_index": 0,
"question_value": "Do you like me?",
"answers": [
{
"row_index": 0,
"row_id": "1",
"row_value": "",
"column_index": 10,
"column_id": "1",
"column_value": "Absolutely!"
}
]
},
{
"page_index": 0,
"page_id": "4771",
"question_id": "1614",
"question_index": 1,
"question_value": "What's the primary reason for your rating?",
"answers": [
{
"text_response": "Because you are cool"
}
]
},
{
"page_index": 0,
"page_id": "4771",
"question_id": "1614",
"question_index": 2,
"question_value": "How much do you like me?",
"answers": [
{
"row_index": 0,
"row_id": "1182",
"row_value": "More than anyone else"
}
]
}
]
}
So I created these data classes to parse this:
@JsonClass(generateAdapter = true)
data class SurveyResponse(
@SerializedName("respondent_id") val respondentId: String,
@SerializedName("completion_status") val completionStatus: String,
@SerializedName("date_modified") val dateModified: String,
@SerializedName("date_start") val dateStart: String,
@Json(name = "responses") val responses: List<SurveyQuestion>
)
@JsonClass(generateAdapter = true)
data class SurveyQuestion(
@SerializedName("page_index") val pageIndex: Int,
@SerializedName("page_id") val pageId: String,
@SerializedName("question_id") val questionId: String,
@SerializedName("question_index") val questionIndex: Int,
@SerializedName("question_value") val questionValue: String,
@Json(name = "answers") val answers: List<Answer>
)
@JsonClass(generateAdapter = true)
data class Answer(
@SerializedName("row_index") val rowIndex: Int,
@SerializedName("row_id") val rowId: String,
@SerializedName("row_value") val rowValue: String = "",
@SerializedName("column_index") val columnIndex: Int,
@SerializedName("column_id") val columnId: String,
@SerializedName("column_value") val columnValue: String
)
I even tried to create a coupld of custom adapters, although I have no reason to believe that they are necessary:
class ResponsesAdapter {
@FromJson
fun arrayListFromJson(list: List<SurveyQuestion>): ArrayList<SurveyQuestion> = ArrayList(list)
}
class AnswersAdapter {
@FromJson
fun arrayListFromJson(list: List<Answer>): ArrayList<Answer> = ArrayList(list)
}
And then wrote this code to parse this:
val moshi: Moshi = Moshi
.Builder()
.add(ResponsesAdapter())
.add(AnswersAdapter())
.build()
val jsonAdapter: JsonAdapter<SurveyResponse> = moshi.adapter<SurveyResponse>()
val surveyResponse = jsonAdapter.fromJson(jsonString)
I've tried many other ways to create a custom adapter, but I always get this error, which I have seen in many other posts:
java.lang.IllegalArgumentException: No JsonAdapter for java.util.ArrayList<java.lang.String>, you should probably use List instead of ArrayList (Moshi only supports the collection interfaces by default) or else register a custom JsonAdapter.
This message is particularly confusing because there is no ArrayList involved and there is no string (other than the raw string that i am trying to parse).
I've reached the point where I am just throwing stuff against the wall to see what sticks. This cannot be that difficult to resolve, but I don't see the solution.
EDIT
Following up on Faruk's answer below. I had tried to use KotlinJsonAdapterFactory
but had written that line incorrectly. I wrote .adapter<SurveyResponse::class.java>()
. Once I fixed that, the app no longer crashes.
The other part of his answer that fixed this was to change the annotations in the data classes from:
@SerializedName("whatever")
to:
@Json(name = "whatever")
Without that, all the values parsed as null.
First of all, you should handle that the values can be null.
@JsonClass(generateAdapter = true)
data class Answer(
@Json(name = "row_index") val rowIndex: Int?,
@Json(name = "row_id") val rowId: String?,
@Json(name = "row_value") val rowValue: String?,
@Json(name = "column_index") val columnIndex: Int?,
@Json(name = "column_id") val columnId: String?,
@Json(name = "column_value") val columnValue: String?,
@Json(name = "text_response") val textResponse: String?
)
@JsonClass(generateAdapter = true)
data class Response(
@Json(name = "page_index") val pageIndex: Int?,
@Json(name = "page_id") val pageId: String?,
@Json(name = "question_id") val questionId: String?,
@Json(name = "question_index") val questionIndex: Int?,
@Json(name = "question_value") val questionValue: String?,
@Json(name = "answers") val answers: List<Answer>?
)
@JsonClass(generateAdapter = true)
data class SurveyResponse(
@Json(name = "respondent_id") val respondentId: String?,
@Json(name = "completion_status") val completionStatus: String?,
@Json(name = "date_modified") val dateModified: String?,
@Json(name = "date_start") val dateStart: String?,
@Json(name = "responses") val responses: List<Response>?
)
You don't need adapters. Just use KotlinJsonAdapterFactory()
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
val adapter = moshi.adapter(SurveyResponse::class.java)
val surveyResponse = adapter.fromJson(jsonString)