I need to consume a jsonapi (<https://jsonapi.org>...
# serialization
s
I need to consume a jsonapi (https://jsonapi.org) based endpoint and looking for some pointers on how to get started deserializing the data. I have a basic response with serializer attributes setup but it isn’t quite right. >>>
👀 1
I currently have:
Copy code
@Serializable
class Response (
    var data: List<Resource>?,
    var included: List<Resource>? =  null,
    var links: HashMap<String, String>? = null,
    var meta: HashMap<String, @ContextualSerialization Any>? = null
)

@Serializable
class ResponseSingle (
    var data: Resource?,
    var included: List<Resource>? = null,
    var links: HashMap<String, String>? = null,
    var meta: HashMap<String, @ContextualSerialization Any>? = null
)

@Serializable
class Resource(
    val id: String,
    val type: String,
    val attributes: HashMap<String, @ContextualSerialization Any>? = null,
    val relationships: Map<String, Relationship>? = null
)

@Serializable
class Relationship(@SerialName("data") val links: List<ResourceLink>)

@Serializable
class ResourceLink(val id: String, val type: String)
I can serialize this:
Copy code
val rsp = Response(
            listOf(
                Resource(
                    "1", "test", hashMapOf("a" to 1, "b" to 2, "c" to "c")
                )
            ), null, null, hashMapOf("pages" to 1, "header" to "Header")
        )
but not this:
Copy code
val rsp = Response(
            listOf(
                Resource(
                    "1", "test", hashMapOf("a" to 1, "b" to 2, "c" to "c", "obj" to hashMapOf<String, Any>("attr1" to 1, "attr2" to "a string"))
                )
            ), null, null, hashMapOf("pages" to 1, "header" to "Header")
        )
I get
Can’t locate argument-less serializer for class java.util.HashMap
Is there a way to do this? Can it be improved? Ideally I would be able to make Resource a generic class that puts all of the attributes into actual object properties but I would be happy enough for them to just be nested maps.
b
Try just Map in the definition
d
`Map<String, Any`> could be replaced with
JsonObject
.
👍 1
s
Map<String, JsonObject>
is working out best for me. Thank you, that will allow me to move forward.
🎉 1
c
Hey, sorry for bothering you @Sam, I am trying to parse a jsonapi response and I found your answer. Are you still using this code?
s
With some modifications yes.
Copy code
@Serializable
class Response(
    var data: List<Resource>? = null,
    var errors: List<ResponseError>? = null,
    var included: List<Resource>? = null,
    var links: Map<String, String>? = null,
    var meta: JsonObject? = null
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as Response

        if (data != other.data) return false
        if (errors != other.errors) return false
        if (included != other.included) return false
        if (links != other.links) return false
        if (meta != other.meta) return false

        return true
    }

    override fun hashCode(): Int {
        var result = data?.hashCode() ?: 0
        result = 31 * result + (errors?.hashCode() ?: 0)
        result = 31 * result + (included?.hashCode() ?: 0)
        result = 31 * result + (links?.hashCode() ?: 0)
        result = 31 * result + (meta?.hashCode() ?: 0)
        return result
    }

    override fun toString(): String {
        return "Response(data=$data, included=$included, links=$links, meta=$meta)"
    }

    fun getIncludedOrNull(link: ResourceLink): Resource? {
        return included?.firstOrNull { it.id == link.id && it.type == link.type }
    }

    fun getIncluded(link: ResourceLink): Resource {
        return getIncludedOrNull(link) ?: throw NoSuchElementException("Response contains no included element matching the link.")
    }
}
@Serializable
class Resource(
    val id: String,
    val type: String,
    val attributes: JsonObject? = null,
    val relationships: Map<String, Relationship>? = null,
    val links: Map<String, String>? = null
    ) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as Resource

        if (id != other.id) return false
        if (type != other.type) return false
        if (attributes != other.attributes) return false
        if (relationships != other.relationships) return false
        if (links != other.links) return false

        return true
    }

    override fun hashCode(): Int {
        var result = id.hashCode()
        result = 31 * result + type.hashCode()
        result = 31 * result + (attributes?.hashCode() ?: 0)
        result = 31 * result + (relationships?.hashCode() ?: 0)
        result = 31 * result + (links?.hashCode() ?: 0)
        return result
    }

    override fun toString(): String {
        return "Resource(id='$id', type='$type', attributes=$attributes, relationships=$relationships)"
    }
}

@Serializable
class Relationship(@SerialName("data") val links: List<ResourceLink>) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as Relationship

        if (links != other.links) return false

        return true
    }

    override fun hashCode(): Int {
        return links.hashCode()
    }
}

@Serializable
class ResourceLink(val id: String, val type: String) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as ResourceLink

        if (id != other.id) return false
        if (type != other.type) return false

        return true
    }

    override fun hashCode(): Int {
        var result = id.hashCode()
        result = 31 * result + type.hashCode()
        return result
    }
}

@Serializable
class ResponseError(
    val id: String? = null,
    val links: List<ResourceLink>,
    val status: String? = null,
    val code: String? = null,
    val title: String? = null,
    val detail: String? = null,
    val source: Source? = null,
    val meta: JsonObject? = null
)

@Serializable
class Source(val pointer: String, val parameter: String)
❤️ 1
c
Super helpful, many thanks!
👍 1
May I ask you an example of how you consume a response? Are you manually mapping the response to an object or you're using something for avoid it?
nvm, got something working :)
l
@Sam This’s quite an old thread, but I can get your code usage with Ktor to make it work? I might have the same question to Dario:
May I ask you an example of how you consume a response? Are you manually mapping the response to an object or you’re using something for avoid it?
s
Sorry. I don’t have that code anymore. I have changed jobs since then.