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

com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.kt Maven / Gradle / Ivy

Go to download

Add-on module for Jackson (https://github.com/FasterXML/jackson/) to support Kotlin language, specifically introspection of method/constructor parameter names, without having to add explicit property name annotation.

There is a newer version: 2.18.2
Show newest version
package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.databind.deser.ValueInstantiators
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import kotlin.reflect.KParameter
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.kotlinFunction

class KotlinValueInstantiator(src: StdValueInstantiator) : StdValueInstantiator(src) {
    @Suppress("UNCHECKED_CAST")
    override fun createFromObjectWith(ctxt: DeserializationContext, props: Array, buffer: PropertyValueBuffer): Any? {
        val callable = when (_withArgsCreator) {
            is AnnotatedConstructor -> (_withArgsCreator.annotated as Constructor).kotlinFunction
            is AnnotatedMethod -> (_withArgsCreator.annotated as Method).kotlinFunction
            else -> throw IllegalStateException("Expected a construtor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
        } ?: return super.createFromObjectWith(ctxt, props, buffer) // we cannot reflect this method so do the default Java-ish behavior

        val jsonParmValueList = buffer.getParameters(props) // properties in order, null for missing or actual nulled parameters

        // quick short circuit for special handling for no null checks needed and no optional parameters
        if (jsonParmValueList.none { it == null } && callable.parameters.none { it.isOptional }) {
           return super.createFromObjectWith(ctxt, jsonParmValueList)
        }

        val callableParametersByName = hashMapOf()

        callable.parameters.forEachIndexed { idx, paramDef ->
            if (paramDef.kind == KParameter.Kind.INSTANCE || paramDef.kind == KParameter.Kind.EXTENSION_RECEIVER) {
                // we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior
                return super.createFromObjectWith(ctxt, jsonParmValueList)
            } else {
                val jsonProp = props.get(idx)
                val isMissing = !buffer.hasParameter(jsonProp)
                val paramVal = jsonParmValueList.get(idx)

                if (isMissing) {
                    if (paramDef.isOptional) {
                        // this is ok, optional parameter not resolved will have default parameter value of method
                    } else if (paramVal == null) {
                        if (paramDef.type.isMarkedNullable) {
                            // null value for nullable type, is ok
                            callableParametersByName.put(paramDef, null)
                        } else {
                            // missing value coming in as null for non-nullable type
                            throw MissingKotlinParameterException(
                                    parameter = paramDef,
                                    processor = ctxt.parser,
                                    msg = "Instantiation of ${this.valueTypeDesc} value failed for JSON property ${jsonProp.name} due to missing (therefore NULL) value for creator parameter ${paramDef.name} which is a non-nullable type"
                            ).wrapWithPath(this.valueClass, jsonProp.name)
                        }
                    } else {
                        // default value for datatype for non nullable type, is ok
                        callableParametersByName.put(paramDef, paramVal)
                    }
                } else {
                    if (paramVal == null && !paramDef.type.isMarkedNullable) {
                        // value coming in as null for non-nullable type
                        throw MissingKotlinParameterException(
                                parameter = paramDef,
                                processor = ctxt.parser,
                                msg = "Instantiation of ${this.valueTypeDesc} value failed for JSON property ${jsonProp.name} due to NULL value for creator parameter ${paramDef.name} which is a non-nullable type"
                        ).wrapWithPath(this.valueClass, jsonProp.name)
                    } else {
                        // value present, and can be set
                        callableParametersByName.put(paramDef, paramVal)
                    }
                }
            }
        }

        return if (callableParametersByName.size == jsonParmValueList.size) {
            // we didn't do anything special with default parameters, do a normal call
            super.createFromObjectWith(ctxt, jsonParmValueList)
        } else {
            val accessible = callable.isAccessible
            if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
                    (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))) {
                callable.isAccessible = true
            }

            callable.callBy(callableParametersByName)
        }

    }

    override fun createFromObjectWith(ctxt: DeserializationContext, args: Array): Any {
        return super.createFromObjectWith(ctxt, args)
    }

}

class KotlinInstantiators : ValueInstantiators {
    override fun findValueInstantiator(deserConfig: DeserializationConfig, beanDescriptor: BeanDescription, defaultInstantiator: ValueInstantiator): ValueInstantiator {
        return if (beanDescriptor.beanClass.isKotlinClass()) {
            if (defaultInstantiator is StdValueInstantiator) {
                KotlinValueInstantiator(defaultInstantiator)
            } else {
                // TODO: return defaultInstantiator and let default method parameters and nullability go unused?  or die with exception:
                throw IllegalStateException("KotlinValueInstantiator requires that the default ValueInstantiator is StdValueInstantiator")
            }
        } else {
            defaultInstantiator
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy