Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using deserialized instance's delegation throws NullPointerException #241

Closed
jeiea opened this issue Oct 17, 2018 · 7 comments
Closed

Using deserialized instance's delegation throws NullPointerException #241

jeiea opened this issue Oct 17, 2018 · 7 comments

Comments

@jeiea
Copy link

jeiea commented Oct 17, 2018

The following code throws NullPointerException. Is it expected behavior?

package sample

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JSON

interface IntId {
  var id: Int
}

interface IData {
  var msg: String?
}

@Serializable
data class Data(
  override var msg: String? = null
) : IData


@Serializable
data class IdData(
  override var id: Int,
  var data: Data
) : IntId, IData by data

inline fun <reified T : Any> deserialTest(root: T): T {
  val json = JSON.stringify(root)
  println(json)
  val obj = JSON.parse<T>(json)
  println(obj)
  return obj
}

fun main(args: Array<String>) {
  val src = IdData(3, Data("asdf"))
  val restored = deserialTest(src)
  println(restored.msg)
}
> Task :run FAILED
{"id":3,"data":{"msg":"asdf"}}
IdData(id=3, data=Data(msg=asdf))
Exception in thread "main" java.lang.NullPointerException
	at sample.IdData.getMsg(XSerialization.kt)
	at sample.XSerializationKt.main(XSerialization.kt:38)
@jeiea jeiea changed the title Using deserialized class' delegation throws NullPointerException Using deserialized instance's delegation throws NullPointerException Oct 17, 2018
@sandwwraith
Copy link
Member

It is a bug (and current limitation) of compiler plugin: it does not initialize synthetic fields which store delegates, so access to delegated methods leads to access to uninitialized fields and NPE. So right now it's impossible to use interface delegation in serializable classes.

@IARI
Copy link

IARI commented Aug 18, 2019

Delegation does seem to work fine for me, if the Object is instantiated by default in the constructor:

@Serializable
class MyImpl(val base: BaseClass = BaseClass()) : BaseClass by base {
    ...
}

@pdvrieze
Copy link
Contributor

@IARI The issue is that for delegation Kotlin actually will use a synthetic field that is initialised to the value of the base constructor parameter. It uses a different field for the base property (so in this case there are two fields holding the same value). If you pass a different version of BaseClass rather than the default the delegated property will not be serialized, only the visible copy. The correct thing would be for the serialization plugin to just reject serialization on delegates. I don't believe that it can be done correctly (as deserialization of two copies will lead to separate copies after deserialization where originally the two copies were references to the same object).

@IARI
Copy link

IARI commented Aug 19, 2019

If you pass a different version of BaseClass rather than the default the delegated property will not be serialized

I do not know what you mean exactly, but I haven't tested all possible use cases here.

I am aware of that this is not a clean solution, but just a workaround.
I have tested creating an Object, modifying the instance stored in base, and the values are serialized and deserialized correctly - or am I overlooking something here?
That's all I needed for my use case. I do not actually want to be able to pass a value via constructor, it's just there to duplicate the field, such that it will be serialized.

I do not advocate the outright rejection of serialization of delegations, since this is right now the only way to serialize "inherited fields" since that is broken (of course they are not inherited, but the behavior is achieved with composition by delegation - see #378)

@Whathecode
Copy link
Contributor

I intuitively expected a similar behavior as suggested by @pdvrieze:

The correct thing would be for the serialization plugin to just reject serialization on delegates.

In fact, I ended up using class delegation exactly because I didn't want the delegated implementation to be serialized; I wanted it to be constructed as part of object initialization.

The use case is constructing command objects for application services. Take a fictional StringCollectionService which is called by a client. Client and server both share the same application service interface. The command object is defined as follows:

@Serializable
data class Add( val toAdd: String ) : 
    ServiceInvoker<StringCollectionService, Unit> by createServiceInvoker( StringCollectionService::add, toAdd )

This strongly ties the command object implementation to the application service definition (StringCollectionService::add). ServiceInvoker can invoke the command object on a passed service (fun invokeOn( service: TService ): TReturn).

Therefore, I also ran into the issue that this invokeOn call results in a NullPointerException. However, note that I am simply interested in the default initialization of the object, based on the values which have been serialized. Interestingly, that means that I can properly initialize the object by calling copy() on it, since it is a data class. Of course, this very much feels like a workaround.

I simply wanted to point out this example use case so it could be considered prior to ruling out generating serializers for classes which use class delegation entirely. Perhaps we should be able to apply @Transient to the delegated interface?

@Whathecode
Copy link
Contributor

Starting from Kotlin 1.3.60, this now seems to throw a TypeError instead.

@NamekMaster
Copy link

Delegation does seem to work fine for me, if the Object is instantiated by default in the constructor:

@Serializable
class MyImpl(val base: BaseClass = BaseClass()) : BaseClass by base {
    ...
}

This is an accessible workaround for me, Thanks

770grappenmaker added a commit to 770grappenmaker/kotlin that referenced this issue Jan 28, 2022
…ialized in a function

This fixes the following issues: (not from YouTrack because they are kotlinx.serialization related)
Kotlin/kotlinx.serialization#1039
Kotlin/kotlinx.serialization#241
maxim092001 pushed a commit to maxim092001/kotlin that referenced this issue Apr 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants