Home > Mobile >  Kotlin parse json list with non zero index using Moshi
Kotlin parse json list with non zero index using Moshi

Time:01-09

I am trying to parse a list with non zero index integer keys and values using Moshi with Retrofit 2. The array object from the server has the below structure:

array:1 [▼
    2021 => 6,
    2022 => 8
  ]

The parcelable Kotlin object has the following structure:

@Parcelize
data class UserHistory(
    @Json(name ="log_count") val logCount: List<Int>?,
    @Json(name ="days") val days: Int,
): Parcelable

The Retrofit 2 request returns the following error: com.squareup.moshi.JsonDataException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $.log_count.

How do I parse an array with Moshi that do not have zero indexed keys?

CodePudding user response:

So I solved the answer by changing the server request packet object design:

// -> Server array
array:1 [▼
      "history" => [
         [▼
            "year" => 2021,
            "days" => 8
         ],
         [▼
            "year" => 2022,
            "days" => 6
         ]
      ],
      "days" => 41
  ];

// -> Kotlin
@Parcelize
data class UserFiller(
    @json("years") val year: int,
    @json("days") val day: int
): Parcelable

@Parcelize
data class UserLogHistory(
    @Json(name ="history") val history: List<UserFiller>?,
    @Json(name ="days") val days: Int,
): Parcelable

The reason for changing the design is because Java which sired Kotlin only allows zero indexed arrays and sees not-zero based arrays as an object. Or well that is only answer I could come up with.

CodePudding user response:

You're thinking in PHP where you must be thinking in JSON/JavaScript.

{ "2021":"6","2022":"8"} is a JSON object, not array. JSON doesn't know indexed arrays at all.

If you know the field names beforehand, you can map it to a Java/Kotlin object. It doesn't work very well here, but for the sake of completeness:

data class UserLogHistoryYears(
    @Json(name ="2020") val y2020: String?,
    @Json(name ="2021") val y2021: String?,
    @Json(name ="2022") val y2022: String?,
    @Json(name ="2023") val y2023: String?,
    //etc...
    )

data class UserLogHistory(
    val history: UserLogHistoryYears?,
    val days: Int,
)

Note that field name being all-numeric is a mere technicality. Fields in Kotlin must start with a letter, hence I've added "y" prefix and then @Json annotation renames it back for the parser.

If the fields are dynamic (as here), it's often better to map them to a Map<String, String?>

data class UserLogHistory(
    val history: Map<String, String?>?,
    val days: Int,
)

Note that whatever format it was in PHP is 100% irrelevant to you. PHP is mapped to JSON somehow, and it's that JSON that you're working with. You're mapping PHP->JSON and JSON->Kotlin, there's no PHP->Kotlin relationship. So the PHP visualizations are useless for this purpose, you must look into the JSON generated. As many conversions are lossy, here you're losing the information that your data is an array - it's converted into object, because this is the limitation of JSON. Another limitation is that maps and objects are same thing in JSON, which is something you can exploit here.

Side note: Parcelable is useful only if you're sending the output through Android mechanisms, like Intent. For parsing, they do nothing.

Side note 2: @Json annotations are required only when field name in JSON is different than it is in Kotlin.

Side note 3: it might be worthwile to make the fields appear as JSON "Number", not String (without ""), so you can use Int in Kotlin.

  • Related