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

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

/*
 * @(#) JSONConfig.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.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSupertypeOf
import kotlin.reflect.full.starProjectedType

import net.pwall.json.annotation.JSONIgnore
import net.pwall.json.annotation.JSONName

/**
 * Configuration class for JSON Auto serialize / deserialize for Kotlin.
 *
 * @author  Peter Wall
 */
class JSONConfig {

    /** Read buffer size (for `json-ktor`) */
    var readBufferSize = defaultBufferSize

    private val fromJSONMap: MutableMap Any?> = HashMap()

    private val toJSONMap: MutableMap JSONValue?> = HashMap()

    private val nameAnnotations: MutableList, KProperty.Getter>> =
            arrayListOf(namePropertyPair(JSONName::class, "name"))

    private val ignoreAnnotations: MutableList> = arrayListOf(JSONIgnore::class)

    /**
     * Get a `fromJSON` mapping function for the specified [KType].
     *
     * @param   type    the target type
     * @return          the mapping function, or `null` if not found
     */
    fun getFromJSONMapping(type: KType): ((JSONValue?) -> Any?)? = fromJSONMap[type]

    /**
     * Get a `toJSON` mapping function for the specified [KType].
     *
     * @param   type    the source type
     * @return          the mapping function, or `null` if not found
     */
    fun getToJSONMapping(type: KType): ((Any?) -> JSONValue?)? = toJSONMap[type]

    /**
     * Find a `toJSON` mapping function that will accept the specified type, or any supertype of it.
     *
     * @param   type    the source type
     * @return          the mapping function, or `null` if not found
     */
    fun findToJSONMapping(type: KType): ((Any?) -> JSONValue?)? {
        val match = toJSONMap.keys.find { it.isSupertypeOf(type) } ?: return null
        return toJSONMap[match]
    }

    /**
     * Add custom mapping from JSON to the specified type.
     *
     * @param   type    the target type
     * @param   mapping the mapping function
     * @return          the `JSONConfig` object (for chaining)
     */
    fun fromJSON(type: KType, mapping: (JSONValue?) -> Any?): JSONConfig {
        fromJSONMap[type] = mapping
        return this
    }

    /**
     * Add custom mapping from a specified type to JSON.
     *
     * @param   type    the source type
     * @param   mapping the mapping function
     * @return          the `JSONConfig` object (for chaining)
     */
    fun toJSON(type: KType, mapping: (Any?) -> JSONValue?): JSONConfig {
        toJSONMap[type] = mapping
        return this
    }

    /**
     * Add custom mapping from JSON to the inferred type.
     *
     * @param   mapping the mapping function
     * @param   T       the type to be mapped
     * @return          the `JSONConfig` object (for chaining)
     */
    inline fun  fromJSON(noinline mapping: (JSONValue?) -> T?) =
            fromJSON(T::class.starProjectedType, mapping)

    /**
     * Add custom mapping from an inferred type to JSON.
     *
     * @param   mapping the mapping function
     * @param   T       the type to be mapped
     * @return          the `JSONConfig` object (for chaining)
     */
    inline fun  toJSON(noinline mapping: (T?) -> JSONValue?) =
            toJSON(T::class.starProjectedType) { mapping(it as T?) }

    /**
     * Add an annotation specification to the list of annotations that specify the name to be used when serializing or
     * deserializing a property.
     *
     * @param   nameAnnotationClass the annotation class to specify the name
     * @param   argumentName        the name of the argument to the annotation that holds the name
     * @param   T                   the annotation class
     * @return                      the `JSONConfig` object (for chaining)
     */
    fun  addNameAnnotation(nameAnnotationClass: KClass, argumentName: String): JSONConfig {
        nameAnnotations.add(namePropertyPair(nameAnnotationClass, argumentName))
        return this
    }

    private fun  namePropertyPair(nameAnnotationClass: KClass, argumentName: String):
            Pair, KProperty.Getter> {
        return nameAnnotationClass to findAnnotationStringProperty(nameAnnotationClass, argumentName).getter
    }

    @Suppress("UNCHECKED_CAST")
    private fun  findAnnotationStringProperty(annotationClass: KClass, argumentName: String):
            KProperty {
        annotationClass.members.forEach {
            if (it is KProperty<*> && it.name == argumentName && it.returnType == String::class.starProjectedType) {
                return it as KProperty
            }
        }
        throw IllegalArgumentException(
                "Annotation class ${annotationClass.simpleName} does not have a String property $argumentName")
    }

    /**
     * Find the name to be used when serializing or deserializing a property, from the annotations supplied.
     *
     * @param   annotations the [Annotation]s (from the parameter, or the property, or both)
     * @return              the name to be used, or `null` if no annotation found
     */
    fun findNameFromAnnotation(annotations: List?): String? {
        nameAnnotations.forEach { entry ->
            annotations?.forEach {
                if (it::class.isSubclassOf(entry.first))
                    return entry.second.call(it)
            }
        }
        return null
    }

    /**
     * Add an annotation specification to the list of annotations that specify that the property is to be ignored when
     * serializing or deserializing.
     *
     * @param   ignoreAnnotationClass   the annotation class
     * @param   T                       the annotation class
     * @return                          the `JSONConfig` object (for chaining)
     */
    fun  addIgnoreAnnotation(ignoreAnnotationClass: KClass): JSONConfig {
        ignoreAnnotations.add(ignoreAnnotationClass)
        return this
    }

    /**
     * Test whether a property has an annotation to indicate that it is to be ignored when serializing or deserializing.
     *
     * @param   annotations the [Annotation]s (from the parameter, or the property, or both)
     * @return              `true` if an "ignore" annotation appears in the supplied list
     */
    fun hasIgnoreAnnotation(annotations: List?): Boolean {
        ignoreAnnotations.forEach { entry ->
            annotations?.forEach {
                if (it::class.isSubclassOf(entry))
                    return true
            }
        }
        return false
    }

    /**
     * Combine another `JSONConfig` into this one.
     *
     * @param   config  the other `JSONConfig`
     * @return          the `JSONConfig` object (for chaining)
     */
    fun combineAll(config: JSONConfig): JSONConfig {
        readBufferSize = config.readBufferSize
        fromJSONMap.putAll(config.fromJSONMap)
        toJSONMap.putAll(config.toJSONMap)
        nameAnnotations.addAll(config.nameAnnotations)
        ignoreAnnotations.addAll(config.ignoreAnnotations)
        return this
    }

    /**
     * Combine custom mappings from another `JSONConfig` into this one.
     *
     * @param   config  the other `JSONConfig`
     * @return          the `JSONConfig` object (for chaining)
     */
    fun combineMappings(config: JSONConfig): JSONConfig {
        fromJSONMap.putAll(config.fromJSONMap)
        toJSONMap.putAll(config.toJSONMap)
        return this
    }

    companion object {

        const val defaultBufferSize = DEFAULT_BUFFER_SIZE

        val defaultConfig = JSONConfig()

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy