将 Number 隐式转换为更复杂的类型

Implicitly convert a Number to a more complex type

提问人:vatbub 提问时间:11/17/2023 更新时间:11/17/2023 访问量:21

问:

在我的项目中,我曾经有过类似的:data class

data class SampleClassThatContainsALength(val length: Double)

使用 ,将序列化为 。jackson-databindSampleClassThatContainsALength(0.025){"length":0.025}

在整个项目中,长度被隐含地假定为米,但为了减少歧义,我实现了一个类,它负责单位:Length

data class Length(val m: Double) : Comparable<Length> {

    @get:JsonIgnore
    val km: Double get() = m / 1e3

    @get:JsonIgnore
    val cm: Double get() = m / 1e-2

    @get:JsonIgnore
    val mm: Double get() = m / 1e-3

    // More conversions to other units...

    operator fun plus(other: Length) = (this.m + other.m).m
    operator fun minus(other: Length) = (this.m - other.m).m
    operator fun unaryMinus() = (-this.m).m

    // More operator implementations...
}

val Number.km get() = Length(this.toDouble() * 1e3)
val Number.m get() = Length(this.toDouble())
val Number.cm get() = Length(this.toDouble() * 1e-2)
val Number.mm get() = Length(this.toDouble() * 1e-3)
// More extension functions for other units...

因此,数据类现在如下所示:

data class SampleClassThatContainsALength(val length: Length)

示例对象序列化为 .SampleClassThatContainsALength(0.025.m){"length":{"m":0.025}}

我现在有一个问题,我想使用新的序列化,因为它清楚地说明了单位,但我仍然有一些旧的JSON文档(类似于开头的序列化JSON),我想将其隐式转换为新的实现。

所以,总而言之,我想要两者,并反序列化为 .{"length":0.025}{"length":{"m":0.025}}SampleClassThatContainsALength(0.025.m)

现在,反序列化按预期工作,但反序列化失败{"length":{"m":0.025}}{"length":0.025}

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `de.uni_freiburg.inatech.streem.image_converter.common.math.units.Length` (although at least one Creator exists): no double/Double-argument constructor/factory method to deserialize from Number value (0.025)
 at [Source: (String)"{"length":0.025}"; line: 1, column: 11] (through reference chain: de.uni_freiburg.inatech.streem.image_converter.data.json.SampleClassThatContainsALength["length"])

这是我到目前为止尝试过的:

  1. 我在类中创建了一个,并添加了以下工厂方法:companion objectLength
companion object {
    @JsonCreator
    @JvmStatic
    fun createFromNumber(number: Number): Length = Length(number.toDouble())
}

这会导致 的反序列化起作用,但现在新格式的反序列化不再起作用:{"length":0.025}

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.Number` from Object value (token `JsonToken.FIELD_NAME`)
 at [Source: (byte[])[19 bytes]; byte offset: #7]
  1. 除了配套对象中的工厂方法外,我还尝试将 的主构造函数标记为如下所示:Length@JsonCreator
data class Length @JsonCreator constructor(val m: Double) : Comparable<Length> {

但结果与尝试 1 完全相同。

  1. 我没有使用工厂方法,而是添加了一个接受(而不是):NumberDouble
data class Length(val m: Double) : Comparable<Length> {
    constructor(m: Number) : this(m.toDouble())

虽然新格式现在正在工作,但旧格式仍然被打破。

json kotlin jackson-databind

评论


答:

0赞 vatbub 11/17/2023 #1

哇,这比我想象的要快,我有点惭愧,我在发布之前没有找到这个。尽管如此,对于遇到相同问题的人来说,这是解决方案:

  1. 将构造函数保留原样:
// No annotation and no secondary constructor necessary
data class Length(val m: Double) : Comparable<Length> {
// ...
  1. 为这两种情况创建两个工厂方法:
companion object {
    // Handles the case {"length":{"m":0.025}}
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    @JvmStatic
    fun createFromObject(m: Double): Length = Length(m)

    // Handles the case {"length":0.025}
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    @JvmStatic
    fun createFromNumber(m: String): Length = Length(m.toDouble())
}

请注意,现在需要 a 而不是 or ,这似乎很重要。createFromNumberStringNumberDouble

以下是激励我这样做的原因:

https://github.com/FasterXML/jackson-databind/issues/2353