我有一个配置元素的层次结构,它们都实现了一个接口(简化):
interface Configuration {
val elements: Long
val data: Long?
var parent: Configuration?
}
实现将由jackson-kotlin构建,读取这样的Yaml文件:
checks:
elements: 10
data: 1234
ports:
elements: 5
代码也可以这样做:
CheckConfig(elements = 10, data = 1234, portConfig = PortConfig(elements = 5))
我的问题是 - 当我构建PortConfig
时,我还不知道它的父母。杰克逊可能,但我不知道如何利用这一点。
如果用户没有为此特定配置部分提供任何PortConfig
属性,则data
实现需要其父级回退。
我目前所做的事情完全是hacky:
inline fun <reified T: Configuration> T.updateChildLinks() {
(this::class as KClass<T>).memberProperties.forEach {
if (it.returnType.isSubtypeOf(Configuration::class.createType(nullable = true))) {
it.isAccessible = true
val config: Configuration = it.get(this) as Configuration
config.parent = this
}
}
}
并在每个父级init
区块中调用此方法。我可以做得更好(特别是,我不喜欢我必须记得在初始阶段调用辅助函数)?
已编辑添加:之前,我有一个仅代码解决方案,可以将data
传递到链中:
CheckConfig(elements = 10, data = 1234,
portConfig = PortConfig(elements = 5, parentData = 1234))
然后data
在parentData
旁观,但我无法与杰克逊合作。
答案 0 :(得分:0)
因此,为了将来的参考,这是我最终的结果:
诀窍是承认我的data class
无法链接到它的父母,因为孩子将在他们的父母之前建造。
然而,我可以利用Jackson通过实现一个自定义反序列化来攻击原始的回退问题,该反序列化器跟踪父属性值并应用它(如果缺少的话)。
我的下面的实现使用Deque,即使对Kotlin感到有些尴尬。
唯一的缺点是反序列化程序在反序列化后显然只知道你的属性值(显然),因此不能将它传递给可能需要它的子值。因此,这会错误地为您123
提供345
:
top:
data: 123
sub:
subsub:
important: false
data: 345
但是如果你可以控制它,你可以使用下面的内容:
class PropInheritance : JsonDeserializer<Long>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Long {
val currentContextName = p.parsingContext
.pathAsPointer().toString()
// cut the last "prop" variable name
.replaceAfterLast("/", "")
val prop = p.longValue
val propStack = initStackIfNeeded(ctxt)
propStack.push(currentContextName to prop)
return prop
}
override fun getNullValue(ctxt: DeserializationContext): Long {
val propStack = initStackIfNeeded(ctxt)
// here, the prop name is missing the context is just the parent
val currentContextName = ctxt.parser.parsingContext
.pathAsPointer().toString()
// find a prop on stack that has the same path
while (propStack.isNotEmpty()
&& !currentContextName.startsWith(propStack.peek().first)) {
propStack.pop()
}
val prop = if (propStack.isNotEmpty())
propStack.peek().second
else
ctxt.getAttribute("default") as Long
// record current prop on the stack so potential children can find it
propStack.push(currentContextName to prop)
return prop
}
private fun initStackIfNeeded(ctxt: DeserializationContext)
: Deque<Pair<String, Long>> {
if (ctxt.getAttribute("stack") == null) {
ctxt.setAttribute("stack", ArrayDeque<Pair<String, Long>>())
}
return ctxt.getAttribute("stack") as Deque<Pair<String, Long>>
}
}
就我而言,我使用mix-in将@JsonDeserialize(using = PropInheritance::class)
应用于我的界面方法的getter:
interface HasProp {
val prop: Long
}
interface HasPropMixin {
@JsonDeserialize(using = PropInheritance::class)
fun getProp(): Long
}
mapper = ObjectMapper(YAMLFactory()).registerModule(KotlinModule())
.addMixin(HasProp::class.java, HasPropMixin::class.java)