All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.adamratzman.spotify.models.serialization.SerializationUtils.kt Maven / Gradle / Ivy

/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2022; Original author: Adam Ratzman */
package com.adamratzman.spotify.models.serialization

import com.adamratzman.spotify.GenericSpotifyApi
import com.adamratzman.spotify.SpotifyException
import com.adamratzman.spotify.models.CursorBasedPagingObject
import com.adamratzman.spotify.models.NeedsApi
import com.adamratzman.spotify.models.NullablePagingObject
import com.adamratzman.spotify.models.PagingObject
import com.adamratzman.spotify.models.PagingObjectBase
import com.adamratzman.spotify.models.instantiateLateinitsIfPagingObjects
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlin.reflect.KClass

internal val nonstrictJson =
    Json {
        isLenient = true
        ignoreUnknownKeys = true
        allowSpecialFloatingPointValues = true
        useArrayPolymorphism = true
        coerceInputValues = true
    }

// Parse function that catches on parse exception
internal fun  String.parseJson(producer: String.() -> T): T =
    try {
        producer(this)
    } catch (e: Exception) {
        throw SpotifyException.ParseException(
            "Unable to parse $this (${e.message})",
            e
        )
    }

// Generic object deserialization
internal fun  String.toObject(serializer: KSerializer, api: GenericSpotifyApi?, json: Json): T {
    return parseJson {
        val obj = json.decodeFromString(serializer, this)
        obj.instantiateLateinitsIfSpotifyObject(api)
        obj
    }
}

internal fun  String.toList(serializer: KSerializer>, api: GenericSpotifyApi?, json: Json): List {
    return parseJson {
        json.decodeFromString(serializer, this).onEach { obj -> obj?.instantiateLateinitsIfSpotifyObject(api) }
    }
}

internal fun Any.instantiateLateinitsIfSpotifyObject(api: GenericSpotifyApi?) {
    if (api == null) return
    if (this is NeedsApi) listOf(this).instantiateAllNeedsApiObjects(api)
    this.instantiateLateinitsIfPagingObjects(api)
}

// Inner object deserialization
internal inline fun  String.toInnerObject(serializer: KSerializer, innerName: String, json: Json): T {
    val map = this.parseJson {
        val t = (String.serializer() to serializer)
        json.decodeFromString(MapSerializer(t.first, t.second), this)
    }
    return (map[innerName] ?: error("Inner object with name $innerName doesn't exist in $map"))
}

internal inline fun  String.toInnerArray(
    serializer: KSerializer>,
    innerName: String,
    json: Json
): List {
    val map = this.parseJson {
        val t = (String.serializer() to serializer)
        json.decodeFromString(MapSerializer(t.first, t.second), this)
    }
    return (map[innerName] ?: error("Inner object with name $innerName doesn't exist in $map")).toList()
}

// Paging Object deserialization
internal fun  String.toCursorBasedPagingObject(
    tClazz: KClass,
    tSerializer: KSerializer,
    innerObjectName: String? = null,
    api: GenericSpotifyApi,
    json: Json,
    arbitraryInnerNameAllowed: Boolean = false,
    skipInnerNameFirstIfPossible: Boolean = true
): CursorBasedPagingObject {
    if (innerObjectName != null || (arbitraryInnerNameAllowed && !skipInnerNameFirstIfPossible)) {
        val jsonObjectRoot = (json.parseToJsonElement(this) as JsonObject)
        val jsonElement =
            innerObjectName?.let { jsonObjectRoot[it] } ?: jsonObjectRoot.keys.firstOrNull()?.let { jsonObjectRoot[it] }
                ?: throw SpotifyException.ParseException("Json element was null for class $tClazz (json $this)")

        val objectString = jsonElement.toString()
        val pagingObject = objectString.parseJson {
            json.decodeFromString(CursorBasedPagingObject.serializer(tSerializer), this)
        }
        pagingObject.instantiateLateinitsForPagingObject(tClazz, api)

        return pagingObject
    }
    try {
        val pagingObject = parseJson { json.decodeFromString(CursorBasedPagingObject.serializer(tSerializer), this) }
        pagingObject.instantiateLateinitsForPagingObject(tClazz, api)

        return pagingObject
    } catch (jde: SpotifyException.ParseException) {
        if (!arbitraryInnerNameAllowed && jde.message?.contains("unable to parse", true) == true) {
            return toCursorBasedPagingObject(
                tClazz,
                tSerializer,
                innerObjectName,
                api,
                json,
                arbitraryInnerNameAllowed = true,
                skipInnerNameFirstIfPossible = false
            )
        } else {
            throw jde
        }
    }
}

internal fun  String.toNonNullablePagingObject(
    tClazz: KClass,
    tSerializer: KSerializer,
    innerObjectName: String? = null,
    api: GenericSpotifyApi,
    json: Json,
    arbitraryInnerNameAllowed: Boolean = false,
    skipInnerNameFirstIfPossible: Boolean = true
): NullablePagingObject {
    if (innerObjectName != null || (arbitraryInnerNameAllowed && !skipInnerNameFirstIfPossible)) {
        val jsonObjectRoot = (json.parseToJsonElement(this) as JsonObject)
        val jsonElement =
            innerObjectName?.let { jsonObjectRoot[it] } ?: jsonObjectRoot.keys.firstOrNull()?.let { jsonObjectRoot[it] }
                ?: throw SpotifyException.ParseException("Json element was null for class $tClazz (json $this)")

        val objectString = jsonElement.toString()
        val pagingObject = objectString.parseJson {
            json.decodeFromString(NullablePagingObject.serializer(tSerializer), this)
        }
        pagingObject.instantiateLateinitsForPagingObject(tClazz, api)

        return pagingObject
    }

    try {
        val pagingObject = this.parseJson { json.decodeFromString(NullablePagingObject.serializer(tSerializer), this) }
        pagingObject.instantiateLateinitsForPagingObject(tClazz, api)
        return pagingObject
    } catch (jde: SpotifyException.ParseException) {
        if (arbitraryInnerNameAllowed && jde.message?.contains("unable to parse", true) == true) {
            return toNonNullablePagingObject(
                tClazz,
                tSerializer,
                innerObjectName,
                api,
                json,
                arbitraryInnerNameAllowed = true,
                skipInnerNameFirstIfPossible = false
            )
        } else {
            throw jde
        }
    }
}

internal inline fun  String.toNonNullablePagingObject(
    tSerializer: KSerializer,
    innerObjectName: String? = null,
    api: GenericSpotifyApi,
    json: Json,
    arbitraryInnerNameAllowed: Boolean = false,
    skipInnerNameFirstIfPossible: Boolean = true
): PagingObject = toNullablePagingObject(
    tSerializer,
    innerObjectName,
    api,
    json,
    arbitraryInnerNameAllowed,
    skipInnerNameFirstIfPossible
).toPagingObject()

internal inline fun  String.toNullablePagingObject(
    tSerializer: KSerializer,
    innerObjectName: String? = null,
    api: GenericSpotifyApi,
    json: Json,
    arbitraryInnerNameAllowed: Boolean = false,
    skipInnerNameFirstIfPossible: Boolean = true
): NullablePagingObject = toNonNullablePagingObject(
    T::class,
    tSerializer,
    innerObjectName,
    api,
    json,
    arbitraryInnerNameAllowed,
    skipInnerNameFirstIfPossible
)

// Kotlin object -> JSON string transformations
internal fun Map.mapToJsonString() = JsonObject(this).toString()

internal fun List.instantiateAllNeedsApiObjects(api: GenericSpotifyApi) {
    this.instantiateLateinitsIfPagingObjects(api)
    asSequence().filterNotNull().map { member -> member.getMembersThatNeedApiInstantiation() }.flatten()
        .distinct()
        .filterNotNull().toList()
        .forEach { member ->
            member.api = api
            member.instantiateLateinitsIfPagingObjects(api)
        }
}

internal fun > PagingObjectBase.instantiateLateinitsForPagingObject(
    tClazz: KClass?,
    api: GenericSpotifyApi
) {
    getMembersThatNeedApiInstantiation().instantiateAllNeedsApiObjects(api)
    this.api = api
    this.itemClass = tClazz
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy