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

net.pwall.json.JSONDeserializer.kt Maven / Gradle / Ivy

/*
 * @(#) JSONDeserializer.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019 Peter Wall
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.collections.ArrayList
import kotlin.jvm.internal.markers.KMappedMarker
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible

import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZonedDateTime
import java.util.Calendar
import java.util.Date
import java.util.LinkedList
import java.util.UUID

import net.pwall.json.annotation.JSONName
import net.pwall.util.ISO8601Date

/**
 * JSON Auto deserialize for Kotlin.
 *
 * @author  Peter Wall
 */
object JSONDeserializer {

    /**
     * Deserialize a parsed [JSONValue] to a specified [KType].
     *
     * @param   resultType  the target type
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig]
     * @return              the converted object
     */
    fun deserialize(resultType: KType, json: JSONValue?, config: JSONConfig? = null): Any? {
        config?.getFromJSONMMapping(resultType)?.let { return it(json) }
        if (json == null) {
            if (!resultType.isMarkedNullable)
                throw JSONException("Can't deserialize null as $resultType")
            return null
        }
        val classifier = resultType.classifier as? KClass<*> ?: throw JSONException("Can't deserialize $resultType")
        return deserialize(classifier, resultType.arguments, json, config)
    }

    /**
     * Deserialize a parsed [JSONValue] to a specified [KClass].
     *
     * @param   resultClass the target class
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @return              the converted object
     */
    fun  deserialize(resultClass: KClass, json: JSONValue?, config: JSONConfig? = null): T? {
        if (json == null)
            return null
        return deserialize(resultClass, emptyList(), json, config)
    }

    /**
     * Deserialize a parsed [JSONValue] to a specified [KClass], where the result may not be `null`.
     *
     * @param   resultClass the target class
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @return              the converted object
     */
    fun  deserializeNonNull(resultClass: KClass, json: JSONValue?, config: JSONConfig? = null): T {
        if (json == null)
            throw JSONException("Can't deserialize null as ${resultClass.simpleName}")
        return deserialize(resultClass, emptyList(), json, config)
    }

    /**
     * Deserialize a parsed [JSONValue] to a parameterized [KClass], with the specified [KTypeProjection]s.
     *
     * @param   resultClass the target class
     * @param   types       the [KTypeProjection]s
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig]
     * @return              the converted object
     */
    @Suppress("UNCHECKED_CAST")
    fun  deserialize(resultClass: KClass, types: List, json: JSONValue,
            config: JSONConfig? = null): T {

        // check for JSONValue

        if (resultClass.isSubclassOf(JSONValue::class) && resultClass.isSuperclassOf(json::class))
            return json as T

        // does the target class companion object have a "fromJSON()" method?

        try {
            findFromJSON(resultClass)?.let {
                return it.call(resultClass.companionObjectInstance, json) as T
            }
        }
        catch (e: Exception) {
            throw JSONException("Error in custom fromJSON - ${resultClass.simpleName}", e)
        }

        when (json) {

            is JSONBoolean -> {
                if (resultClass == Boolean::class)
                    return json.booleanValue() as T
            }

            is JSONString -> return deserializeString(resultClass, json.toString())

            is Number -> {

                when (resultClass) {

                    Int::class -> if (json is JSONInteger || json is JSONZero)
                        return json.toInt() as T

                    Long::class -> if (json is JSONLong || json is JSONInteger || json is JSONZero)
                        return json.toLong() as T

                    Double::class -> return json.toDouble() as T

                    Float::class -> return json.toFloat() as T

                    Short::class -> if (json is JSONInteger || json is JSONZero)
                        return json.toShort() as T

                    Byte::class -> if (json is JSONInteger || json is JSONZero)
                        return json.toByte() as T

                }

                throw JSONException("Can't deserialize $json as $resultClass")
            }

            is JSONArray -> return deserializeArray(resultClass, types, json, config)

            is JSONObject -> return deserializeObject(resultClass, types, json, config)

        }

        throw JSONException("Can't deserialize $resultClass")
    }

    @Suppress("UNCHECKED_CAST")
    fun  deserializeString(resultClass: KClass, str: String): T {

        try {

            when (resultClass) {

                String::class -> return str as T

                Char::class -> {
                    if (str.length != 1)
                        throw JSONException("Character must be string of length 1")
                    return str[0] as T
                }

                CharArray::class -> return str.toCharArray() as T

                Array::class -> return Array(str.length) { i -> str[i] } as T

                java.sql.Date::class -> return java.sql.Date.valueOf(str) as T

                java.sql.Time::class -> return java.sql.Time.valueOf(str) as T

                java.sql.Timestamp::class -> return java.sql.Timestamp.valueOf(str) as T

                Calendar::class -> return ISO8601Date.decode(str) as T

                Date::class -> return ISO8601Date.decode(str).time as T

                Instant::class -> return Instant.parse(str) as T

                LocalDate::class -> return LocalDate.parse(str) as T

                LocalDateTime::class -> return LocalDateTime.parse(str) as T

                OffsetTime::class -> return OffsetTime.parse(str) as T

                OffsetDateTime::class -> return OffsetDateTime.parse(str) as T

                ZonedDateTime::class -> return ZonedDateTime.parse(str) as T

                Year::class -> return Year.parse(str) as T

                YearMonth::class -> return YearMonth.parse(str) as T

                Duration::class -> return Duration.parse(str) as T

                Period::class -> return Period.parse(str) as T

                UUID::class -> return UUID.fromString(str) as T

            }

        }
        catch (e: JSONException) {
            throw e
        }
        catch (e: Exception) {
            throw JSONException("Can't deserialize \"$str\" as $resultClass", e)
        }

        // is the target class an enum?
        // this is ugly code but it works
        // it should be converted to use a Kotlin enum method if one is available
        if (resultClass.isSubclassOf(Enum::class))
            return resultClass.java.getMethod("valueOf", Class::class.java, String::class.java).
                    invoke(null, resultClass.java, str) as T

        // does the target class have a public constructor that takes String?
        // (e.g. StringBuilder, Integer, ... )

        resultClass.constructors.find { it.parameters.size == 1 && it.parameters[0].type.classifier == String::class }?.
                apply { return call(str) }

        throw JSONException("Can't deserialize \"$str\" as $resultClass")
    }

    @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
    fun  deserializeArray(resultClass: KClass, types: List, json: JSONArray,
            config: JSONConfig?): T {
        try {

            return when (resultClass) {

                BooleanArray::class -> BooleanArray(json.size) { i -> deserializeNonNull(Boolean::class, json[i]) }

                ByteArray::class -> ByteArray(json.size) { i -> deserializeNonNull(Byte::class, json[i]) }

                CharArray::class -> CharArray(json.size) { i -> deserializeNonNull(Char::class, json[i]) }

                DoubleArray::class -> DoubleArray(json.size) { i -> deserializeNonNull(Double::class, json[i]) }

                FloatArray::class -> FloatArray(json.size) { i -> deserializeNonNull(Float::class, json[i]) }

                IntArray::class -> IntArray(json.size) { i -> deserializeNonNull(Int::class, json[i]) }

                LongArray::class -> LongArray(json.size) { i -> deserializeNonNull(Long::class, json[i]) }

                ShortArray::class -> ShortArray(json.size) { i -> deserializeNonNull(Short::class, json[i]) }

                ArrayList::class -> {
                    val type = types.firstOrNull()?.type ?: throw JSONException("Type not specified")
                    ArrayList(json.size).apply {
                        json.forEach { add(deserialize(type, it, config)) }
                    }
                }

                LinkedList::class -> {
                    val type = types.firstOrNull()?.type ?: throw JSONException("Type not specified")
                    LinkedList().apply {
                        json.forEach { add(deserialize(type, it, config)) }
                    }
                }

                List::class -> {
                    val type = types.firstOrNull()?.type ?: throw JSONException("Type not specified")
                    val result =  json.map { deserialize(type, it, config) }
                    if (isMutable(resultClass)) result.toMutableList() else result
                }

                HashSet::class -> {
                    val type = types.firstOrNull()?.type ?: throw JSONException("Type not specified")
                    HashSet(json.size).apply {
                        json.forEach { add(deserialize(type, it, config)) }
                    }
                }

                LinkedHashSet::class -> {
                    val type = types.firstOrNull()?.type ?: throw JSONException("Type not specified")
                    LinkedHashSet(json.size).apply {
                        json.forEach { add(deserialize(type, it, config)) }
                    }
                }

                Set::class -> {
                    val result = HashSet(json.size)
                    val type = types.firstOrNull()?.type ?: throw JSONException("Type not specified")
                    json.forEach { result.add(deserialize(type, it, config)) }
                    if (isMutable(resultClass)) result.toMutableSet() else result.toSet()
                }

                Pair::class -> {
                    if (json.size != 2)
                        throw JSONException("Pair must have two members")
                    val type0 = types.firstOrNull()?.type ?: throw JSONException("First type not specified")
                    val type1 = types.getOrNull(1)?.type ?: throw JSONException("Second type not specified")
                    val result0 = deserialize(type0, json[0], config)
                    val result1 = deserialize(type1, json[1], config)
                    result0 to result1
                }

                Triple::class -> {
                    if (json.size != 3)
                        throw JSONException("Triple must have three members")
                    val type0 = types.firstOrNull()?.type ?: throw JSONException("First type not specified")
                    val type1 = types.getOrNull(1)?.type ?: throw JSONException("Second type not specified")
                    val type2 = types.getOrNull(2)?.type ?: throw JSONException("Third type not specified")
                    val result0 = deserialize(type0, json[0], config)
                    val result1 = deserialize(type1, json[1], config)
                    val result2 = deserialize(type2, json[2], config)
                    Triple(result0, result1, result2)
                }

                else -> throw JSONException("Can't deserialize array as $resultClass")

            } as T

        }
        catch (e: JSONException) {
            throw e
        }
        catch (e: Exception) {
            throw JSONException("Can't deserialize array as $resultClass", e)
        }

    }

    @Suppress("UNCHECKED_CAST")
    fun  deserializeObject(resultClass: KClass, types: List, json: JSONObject,
            config: JSONConfig?): T {

        try {
            if (resultClass.isSubclassOf(Map::class)) {
                if (types.size != 2)
                    throw JSONException("Incorrect type arguments for Map")
                val keyClass = types[0].type?.classifier as? KClass<*> ?:
                        throw JSONException("Key type not specified for Map")
                val valueType = types[1].type ?: throw JSONException("Value type not specified for Map")
                val result =  HashMap()
                json.forEach { key: String ->
                    result[deserializeString(keyClass, key)] = deserialize(valueType, json[key], config)
                }
                return (if (isMutable(resultClass)) result.toMutableMap() else result.toMap()) as T
            }

            resultClass.objectInstance?.let { return setRemainingFields(resultClass, it, json, config) }

            findBestConstructor(resultClass.constructors, json)?.let { constructor ->
                val argMap = HashMap()
                val jsonCopy = HashMap(json)
                constructor.parameters.forEach { parameter ->
                    val paramName = parameter.aName()
                    jsonCopy[paramName]?.let {
                        argMap[parameter] = deserialize(parameter.type, it, config)
                        jsonCopy.remove(paramName)
                    }
                }
                return setRemainingFields(resultClass, constructor.callBy(argMap), jsonCopy, config)
            }
        }
        catch (e: JSONException) {
            throw e
        }
        catch (e: Exception) {
            throw JSONException("Can't deserialize object as $resultClass", e)
        }

        throw JSONException("Can't deserialize object as $resultClass")

    }

    private fun   setRemainingFields(resultClass: KClass, instance: T, json: Map,
            config: JSONConfig?): T {
        json.forEach { entry -> // JSONObject fields not used in constructor
            val member = findField(resultClass.members, entry.key) ?:
                    throw JSONException("Can't find property ${entry.key} in ${resultClass.simpleName}")
            val value = deserialize(member.returnType, json[entry.key], config)
            if (member is KMutableProperty<*>) {
                val wasAccessible = member.isAccessible
                member.isAccessible = true
                try {
                    member.setter.call(instance, value)
                }
                catch (e: Exception) {
                    throw JSONException("Error setting property ${entry.key} in ${resultClass.simpleName}", e)
                }
                finally {
                    member.isAccessible = wasAccessible
                }
            }
            else {
                if (member.getter.call(instance) != value)
                    throw JSONException("Can't set property ${entry.key} in ${resultClass.simpleName}")
            }
        }
        return instance
    }

    private fun findField(members: Collection>, name: String): KProperty<*>? {
        for (member in members) {
            if (member is KProperty<*> && member.aName() == name)
                    return member
        }
        return null
    }

    private fun  findBestConstructor(constructors: Collection>, json: JSONObject): KFunction? {
        var result: KFunction? = null
        var best = -1
        for (constructor in constructors) {
            val parameters = constructor.parameters
            if (parameters.any { it.aName() == null || it.kind != KParameter.Kind.VALUE })
                continue
            val n = findMatchingParameters(parameters, json)
            if (n > best) {
                result = constructor
                best = n
            }
        }
        return result
    }

    private fun findMatchingParameters(parameters: List, json: JSONObject): Int {
        var n = 0
        for (parameter in parameters) {
            if (json.containsKey(parameter.aName()))
                n++
            else {
                if (!parameter.isOptional)
                    return -1
            }
        }
        return n
    }

    private fun KParameter.aName(): String? { // annotated name, or regular name if not overridden
        return findAnnotation()?.name ?: name
    }

    private fun KProperty<*>.aName(): String? { // annotated name, or regular name if not overridden
        return findAnnotation()?.name ?: name
    }

    private val fromJsonCache = HashMap, KFunction<*>>()

    private fun findFromJSON(resultClass: KClass<*>): KFunction<*>? {
        fromJsonCache[resultClass]?.let { return it }
        val newEntry = try {
            resultClass.companionObject?.functions?.find { function ->
                function.name == "fromJSON" &&
                        function.parameters.size == 2 &&
                        function.parameters[0].type.classifier == resultClass.companionObject &&
                        function.parameters[1].type.classifier == JSONValue::class &&
                        function.returnType.classifier == resultClass
            }
        }
        catch (e: Exception) {
            null
        }
        return newEntry?.apply { fromJsonCache[resultClass] = this }
    }

    private fun isMutable(resultClass: KClass<*>): Boolean = resultClass.isSubclassOf(KMappedMarker::class)
    // NOTE - KMappedMarker is a marker interface indicating mutability

    inline fun  deserialize(json: JSONValue): T? = deserialize(T::class, json)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy