Home > Blockchain >  How to use @Serializer on deeper JSON datastructures
How to use @Serializer on deeper JSON datastructures

Time:10-10

I'm new in Kotlin as a PHP dev. I have a data model, something like this:

@Serializable
data class Site (
    @SerialName("id")
    val id: Int,
    @SerialName("name")
    val name: String,
    @SerialName("accountId")
    val accountId: Int,
}

I have JSON output something like the following, which comes from a external API and which I am unable to control:

{
  "sites": {
    "count": 1,
    "site": [
      {
        "id": 12345,
        "name": "Foobar",
        "accountId": 123456 
      }
    ]
  }
}

When trying to get this from the API with ktor HTTPClient, I'd like to instruct the serializer to use sites.site as the root for my Site datamodel. Currently, I get the error: Uncaught Kotlin exception: io.ktor.serialization.JsonConvertException: Illegal input and Caused by: kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the array '[', but had 'EOF' instead at path: $

I'm using the following to fetch the endpoint:

package com.example.myapplication.myapp

import com.example.myapplication.myapp.models.Site
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

class Api {

    private val client = HttpClient {
        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true
                isLenient = true
                ignoreUnknownKeys = true
            })
        }
    }

    private val apiKey = "REDACTED"
    private val installationId = "REDACTED"
    private val apiHost = "REDACTED"

    suspend fun getSitesList(): List<Site> {
        return get("sites/list").body()
    }

    suspend fun get(endpoint: String): HttpResponse {
        val response = client.get(buildEndpointUrl(endpoint))
        return response
    }

    private fun buildEndpointUrl(endpoint: String): HttpRequestBuilder {

        val builder = HttpRequestBuilder()
        val parametersBuilder = ParametersBuilder()

        parametersBuilder.append("api_key", apiKey)
        builder.url {
            protocol = URLProtocol.HTTPS
            host = apiHost
            encodedPath = endpoint
            encodedParameters = parametersBuilder
        }
        builder.header("Accept", "application/json")

        return builder
    }
}

CodePudding user response:

you can try make your data model like this,

data class Site(
@SerializedName("sites")
var sites: Sites) {
data class Sites(
    @SerializedName("count")
    var count: Int,
    @SerializedName("site")
    var site: List<Site>
) {
    data class Site(
        @SerializedName("accountId")
        var accountId: Int,
        @SerializedName("id")
        var id: Int,
        @SerializedName("name")
        var name: String
    )
}}

CodePudding user response:

You have to model the whole response object and cannot just provide a model for some of its parts.

@Serializable
data class SitesResponse(
    val sites: SitesContainer,
)

@Serializable
data class SitesContainer(
    val count: Int,
    val site: List<Site>,
)

@Serializable
data class Site(    
    val accountId: Int,
    val id: Int,
    val name: String,
)
  • Related