search
LoginSignup
0
Organization

【Jackson】アノテーションをコードから追加する

TL;DR

  • ClassIntrospectorBasicBeanDescriptionを独自実装することで、コードからアノテーションを追加できる
    • 例えば、後からJsonSerializeアノテーションを追加することが可能
  • 見通しが悪くなるため、事情が無ければやらない方がよい
  • (これによってjackson-module-kotlinvalue classサポートが1歩進んだ)

状況

Jackson内で利用したいアノテーションがSyntheticなコンストラクタにしか付与されておらず、Jackson内で取り扱われるコンストラクタには付与されていないという状況がありました。
そこで、どこかでJacksonの処理に介入してSyntheticなコンストラクタのアノテーションをJackson内で取り扱われるコンストラクタ側に注入できないか試しました。

補足

以下はこの記事の元になったPRで修正されたfailing testです。
コンストラクタのパラメータに付与される形になるアノテーションが、変更前は機能しておらず、変更後は機能している様子が分かります。

やったこと

クラスの解析処理へ介入する

BeanDescriptionを独自実装し、同じく独自実装したClassIntrospectorから利用することで、Jacksonのクラス解析処理へ介入することが出来ます。
実装のベースとしては、それぞれBasicBeanDescriptionBasicClassIntrospectorを利用します。

実装したもの

BeanDescription

import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription
import com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector

// アノテーションの注入処理
private fun AnnotatedConstructor.injectSyntheticAnnotations() {
    val constructor = annotated

    @Suppress("UNCHECKED_CAST")
    val syntheticParams = constructor.parameterTypes.copyOf(constructor.parameterCount + 1)
        .apply { this[constructor.parameterCount] = defaultConstructorMarker } as Array<Class<*>>
    // 対応するSyntheticなコンストラクタの取得を試し、取得できなければ何もしない
    val syntheticConstructor = runCatching { declaringClass.getDeclaredConstructor(*syntheticParams) }
        .getOrNull()
        ?: return

    // 取得できた場合、注入処理を行う
    (0 until constructor.parameterCount).forEach { i ->
        val map = getParameterAnnotations(i)
        syntheticConstructor.parameterAnnotations[i].forEach { map.add(it) }
    }
}

internal class KotlinBeanDescription(coll: POJOPropertiesCollector) : BasicBeanDescription(coll) {
    init {
        // クラス内のコンストラクタ全てについてアノテーションの注入処理を行う
        this.constructors?.forEach { it.injectSyntheticAnnotations() }
    }
}

ClassIntrospector

import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.SerializationConfig
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription
import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector
import com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector

internal object KotlinClassIntrospector : BasicClassIntrospector() {
    override fun forSerialization(
        config: SerializationConfig,
        type: JavaType,
        r: MixInResolver
    ): BasicBeanDescription {
        // この処理はKotlinBeanDescription関係以外全てsuperからコピーしてKotlin化だけしたもの
        return _findStdTypeDesc(config, type)
            ?: _findStdJdkCollectionDesc(config, type)
            ?: run {
                val coll = collectProperties(config, type, r, true)

                if (type.rawClass.annotations.any { it is Metadata }) {
                    KotlinBeanDescription(coll)
                } else {
                    BasicBeanDescription.forDeserialization(coll)
                }
            }
    }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0