TL;DR
-
ClassIntrospector
とBasicBeanDescription
を独自実装することで、コードからアノテーションを追加できる- 例えば、後から
JsonSerialize
アノテーションを追加することが可能
- 例えば、後から
- 見通しが悪くなるため、事情が無ければやらない方がよい
- (これによって
jackson-module-kotlin
のvalue class
サポートが1歩進んだ)
状況
Jackson
内で利用したいアノテーションがSynthetic
なコンストラクタにしか付与されておらず、Jackson
内で取り扱われるコンストラクタには付与されていないという状況がありました。
そこで、どこかでJackson
の処理に介入してSynthetic
なコンストラクタのアノテーションをJackson
内で取り扱われるコンストラクタ側に注入できないか試しました。
補足
以下はこの記事の元になったPR
で修正されたfailing test
です。
コンストラクタのパラメータに付与される形になるアノテーションが、変更前は機能しておらず、変更後は機能している様子が分かります。
やったこと
クラスの解析処理へ介入する
BeanDescription
を独自実装し、同じく独自実装したClassIntrospector
から利用することで、Jackson
のクラス解析処理へ介入することが出来ます。
実装のベースとしては、それぞれBasicBeanDescription
とBasicClassIntrospector
を利用します。
実装したもの
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)
}
}
}
}