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

k75qkfdt  于 2023-11-20  发布在  其他
关注(0)|答案(1)|浏览(74)

在我的项目中,我曾经有一个类似于这样的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.我在Length类中创建了一个companion object,并添加了以下工厂方法:

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.除了伴随对象中的工厂方法,我还尝试用@JsonCreator标记Length的主构造函数,如下所示:

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


但结果与尝试1完全相同。
1.代替工厂方法,我添加了一个接受Number(而不是Double)的二级构造函数:

data class Length(val m: Double) : Comparable<Length> {
    constructor(m: Number) : this(m.toDouble())


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

5anewei6

5anewei61#

哇,这比我想象的要快,我有点惭愧,我在发布之前没有找到这个。然而,这里有解决方案,任何人都有同样的问题:
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())
}


请注意,createFromNumber现在接受String,而不是NumberDouble,这似乎很重要。
以下是我的灵感来源:
https://github.com/FasterXML/jackson-databind/issues/2353

相关问题