Search code examples
jsonkotlinokhttparrayobject

how to make list of objects from a fragment of json-file?


i want to use json file from the api-request to google sheets. from request i am getting such file:

{ "range": "'Пн'!A1:AB986", "majorDimension": "ROWS", "values": [ [ "Точка", "Отдать", "Забрать" ], [ "Заводы" ], [ "Пластик", "", "карт" ], [ "Балашов", "тов" ], [ "ПЭК", "", "док" ], [ "балашов" ], [ "таможня" ], [], [], [ "", "", "", "", " " ], [], [], [], [], [ "Центр" ], [ "ведомости", "тов1", "рем" ], [ "балашов" ], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [ "Московский" ], [ "таможня", "рем доки" ], [], [], [], [], [], [], [], [], [], [], [], [], [ "Прочее" ] ] }

(maybe it is more visible in this picture).

but for work i need only "values". for every array in "values" i want it to convert into Array of UserDataModel.

here is my code: UserDataModel.kt

data class UserDataModel(var point:String?, var get: String?, var take: String?, var isSelected: Boolean = false)

ActivityChoosing.kt (a fragment with function where i am making request)

private fun getDataFromAPI(){
        var url: String = "https://sheets.googleapis.com/v4/spreadsheets/10-yIy1ZuPSjqgmXd7kO9sHobuVlpZHQnz5dMrou0cVs/values/Пн?alt=json&key=${API}"
        val request = okhttp3.Request.Builder().url(url).build()
        val client = OkHttpClient()
            .newBuilder()
            .addInterceptor { chain ->
                val originalRequest = chain.request()
                val builder = originalRequest
                    .newBuilder()
                val newRequest = builder.build()
                chain.proceed(newRequest)
            }.build()
        client.newCall(request).enqueue(object : Callback{
            override fun onFailure(call: Call, e: IOException) {
                Log.d("Mylog", "failed")
                e.printStackTrace()
            }
            override fun onResponse(call: Call, response: okhttp3.Response) {
                DATA_FROM_SHEETS = response.body?.string()!!
                var json = JSONObject(DATA_FROM_SHEETS)
                val gson = Gson()
                val model_list = gson.fromJson(json["values"].toString() , Array<UserDataModel>::class.java)
               Log.d("Mylog", model_list.toString() )
            }
        })

        }

but when initializing model_list (string 4 in onResponse()) i am gettin "Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 3 path $[0]". how can i do it correctly?


Solution

  • The serialization just doesn't work because you have an array, e.g. [ "Пластик", "", "карт" ] that doesn't serialize to the object UserDataModel because this is not array!

    I'd approach this with an intermediate object to read easily from the JSON stream, and I wouldn't mix using JSONObject() and Gson.

    import com.google.gson.Gson
    
    data class UserDataModel(
        val point: String?,
        val get: String?,
        val take: String?,
        var isSelected: Boolean = false
    )
    
    data class ReaderModel(val values: List<List<String>>)
    
    fun main(args: Array<String>) {
        val source = """
                { "range": "'Пн'!A1:AB986", "majorDimension": "ROWS", "values": [ [ "Точка", "Отдать", "Забрать" ], [ "Заводы" ], [ "Пластик", "", "карт" ], [ "Балашов", "тов" ], [ "ПЭК", "", "док" ], [ "балашов" ], [ "таможня" ], [], [], [ "", "", "", "", " " ], [], [], [], [], [ "Центр" ], [ "ведомости", "тов1", "рем" ], [ "балашов" ], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [ "Московский" ], [ "таможня", "рем доки" ], [], [], [], [], [], [], [], [], [], [], [], [], [ "Прочее" ] ] }
            """.trimIndent()
    
        // first read the JSON into this object which has a List of List of Strings
        // if you don't need the range and majorDimension fields, Gson will by default ignore them
        val parsed = Gson().fromJson(source, ReaderModel::class.java)
        println(parsed)
    
        // now using Kotlin transform that into the desired object
        val models : List<UserDataModel> = parsed.values
            .drop(1) // if the first row contains titles, this is how to ignore that first row
            .map {
                UserDataModel(
                    point = if (it.size >= 1) it[0] else null,
                    get = if (it.size >= 2) it[1] else null,
                    take = if (it.size >= 3) it[2] else null,
                )
            }
            .filter { !it.point.isNullOrBlank() } // if you want it ignore the emtpy/blank nodes
        models.forEach { println(it) }
    }