使用Gson和Kotlin 1.0 beta 4反序列化具有延迟属性的类

cle*_*p6r 23 gson kotlin

使用Gson,我想反序列化包含惰性属性的Kotlin类.

使用Kotlin 1.0 beta 4,在对象反序列化期间出现以下错误:

Caused by: java.lang.InstantiationException: can't instantiate class kotlin.Lazy
Run Code Online (Sandbox Code Playgroud)

使用Kotlin 1.0 beta 2,我曾经用@Transient annotaiton标记该属性,告诉Gson跳过它.使用beta 4,这是不可能的,因为注释会导致编译错误.

This annotation is not applicable to target 'member property without backing field'
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚如何解决这个问题.有任何想法吗?

编辑:lazy属性被序列化为JSON("my_lazy_prop$delegate":{}),但这不是我想要的,因为它是从其他属性计算的.我想如果我找到一种方法来阻止该属性被序列化,那么将会修复反序列化崩溃.

Fab*_*ndl 35

由于Kotlin 1.0只是标记这样的字段在de/serialization期间忽略它:

@delegate:Transient 
val field by lazy { ... }
Run Code Online (Sandbox Code Playgroud)

  • 我(仍然)得到这个:`java.lang.NullPointerException:尝试在空对象引用`上调用接口方法'java.lang.Object kotlin.Lazy.getValue()'.使用非惰性(计算)变量有效. (19认同)

Ser*_*kov 9

原因是该delegate领域实际上不是支持领域,因此被禁止.其中一个解决方法是实现ExclusionStrategy:https://stackoverflow.com/a/27986860/1460833

像这样的东西:

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
annotation class GsonTransient

object TransientExclusionStrategy : ExclusionStrategy {
    override fun shouldSkipClass(type: Class<*>): Boolean = false
    override fun shouldSkipField(f: FieldAttributes): Boolean = 
            f.getAnnotation(GsonTransient::class.java) != null
                || f.name.endsWith("\$delegate")
}

fun gson() = GsonBuilder()
        .setExclusionStrategies(TransientExclusionStrategy)
        .create()
Run Code Online (Sandbox Code Playgroud)

请参阅相关票据https://youtrack.jetbrains.com/issue/KT-10502

另一种解决方法是序列化延迟值:

object SDForLazy : JsonSerializer<Lazy<*>>, JsonDeserializer<Lazy<*>> {
    override fun serialize(src: Lazy<*>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement =
            context.serialize(src.value)
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Lazy<*> = 
            lazyOf<Any?>(context.deserialize(json, (typeOfT as ParameterizedType).actualTypeArguments[0]))
}

class KotlinNamingPolicy(val delegate: FieldNamingStrategy = FieldNamingPolicy.IDENTITY) : FieldNamingStrategy {
    override fun translateName(f: Field): String = 
            delegate.translateName(f).removeSuffix("\$delegate")
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

data class C(val o: Int) {
    val f by lazy { 1 }
}

fun main(args: Array<String>) {
    val gson = GsonBuilder()
            .registerTypeAdapter(Lazy::class.java, SDForLazy)
            .setFieldNamingStrategy(KotlinNamingPolicy())
            .create()

    val s = gson.toJson(C(0))
    println(s)
    val c = gson.fromJson(s, C::class.java)
    println(c)
    println(c.f)
}
Run Code Online (Sandbox Code Playgroud)

这将产生以下输出:

{"f":1,"o":0}
C(o=0)
1
Run Code Online (Sandbox Code Playgroud)


jsa*_*rry 7

正如其他答案所解释的,委托字段不应被序列化。

您可以transient通过@Fabian Zeindl 的建议在委托字段中实现此目的:

@delegate:Transient 
val field by lazy { ... }
Run Code Online (Sandbox Code Playgroud)

GsonBuilder或按照 @Sergey Mashkov 的建议跳过 中的所有委托字段:

GsonBuilder().setExclusionStrategies(object : ExclusionStrategy {
    override fun shouldSkipClass(type: Class<*>): Boolean = false
    override fun shouldSkipField(f: FieldAttributes): Boolean = f.name.endsWith("\$delegate")
}
Run Code Online (Sandbox Code Playgroud)

但是,NullPointerException如果您的类没有无参构造函数,您可能会遇到问题。

发生这种情况是因为当 Gson 找不到无参构造函数时,它将使用ObjectConstructor带有UnsafeAllocatorusing Reflection 的 a 来构造您的对象。(参见/sf/answers/1305175931/)。这将清除 Kotlin 创建的委托字段。

要修复此问题,请在类中创建无参构造函数,或使用 GsonInstanceCreator为 Gson 提供默认对象。

GsonBuilder().registerTypeAdapter(YourClass::class, object : InstanceCreator<YourClass> {
    override fun createInstance(type: Type?) = YourClass("defaultValue")
})
Run Code Online (Sandbox Code Playgroud)