Reputation: 3723
Using Gson, I want to deserialize a Kotlin class that contains a lazy property.
With Kotlin 1.0 beta 4 I get the following error during object deserialization:
Caused by: java.lang.InstantiationException: can't instantiate class kotlin.Lazy
With Kotlin 1.0 beta 2, I used to mark the property with the @Transient annotaiton to tell Gson to skip it. With beta 4 this is not possible anymore, as the annotation causes a compile error.
This annotation is not applicable to target 'member property without backing field'
I can’t figure out how to fix this. Any ideas?
Edit: the lazy property is serialized to JSON ("my_lazy_prop$delegate":{}
), but this is not what I want as it is computed from other properties. I suppose if I find a way to prevent the property from being serialized the deserialization crash would be fixed.
Upvotes: 33
Views: 8843
Reputation: 318
As explained by other answers, the delegate field should not be serialized.
You can achieve this with transient
in the delegate field, as proposed by @Fabian Zeindl:
@delegate:Transient
val field by lazy { ... }
or skipping all delegate fields in the GsonBuilder
, as proposed by @Sergey Mashkov:
GsonBuilder().setExclusionStrategies(object : ExclusionStrategy {
override fun shouldSkipClass(type: Class<*>): Boolean = false
override fun shouldSkipField(f: FieldAttributes): Boolean = f.name.endsWith("\$delegate")
}
However, you may face a NullPointerException
if your class doesn't have a no-argument constructor.
It happens because when Gson doesn't find the no-argument constructor, it will use a ObjectConstructor
with an UnsafeAllocator
using Reflection to construct your object. (see https://stackoverflow.com/a/18645370). This will erase the Kotlin creation of the delegate field.
To fix it, either create a no-argument constructor in your class, or use Gson InstanceCreator
to provide Gson with a default object.
GsonBuilder().registerTypeAdapter(YourClass::class, object : InstanceCreator<YourClass> {
override fun createInstance(type: Type?) = YourClass("defaultValue")
})
Upvotes: 7
Reputation: 5988
Since Kotlin 1.0 simply mark the field like this to ignore it during de/serialization:
@delegate:Transient
val field by lazy { ... }
Upvotes: 49
Reputation: 4760
The reason is that the delegate
field is not a backing field actually so it was forbidden. One of the workarounds is to implement ExclusionStrategy
: https://stackoverflow.com/a/27986860/1460833
Something like that:
@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()
See related ticket https://youtrack.jetbrains.com/issue/KT-10502
The other workaround is to serialize lazy values as well:
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")
}
Usage example:
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)
}
that will produce the following output:
{"f":1,"o":0}
C(o=0)
1
Upvotes: 13