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

Deserialization of case object creates a new instance of case object #211

Closed
wsowa opened this issue Jun 25, 2015 · 5 comments
Closed

Deserialization of case object creates a new instance of case object #211

wsowa opened this issue Jun 25, 2015 · 5 comments
Assignees
Labels

Comments

@wsowa
Copy link

wsowa commented Jun 25, 2015

This test will fail at assertion

case object TestObject

val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)

val original = TestObject
val deserialized = mapper.readValue[TestObject.type](mapper.writeValueAsString(original))

assert(deserialized == original)

After quick investigation i found out that original and deserialized are two instances of the case object. In my opinion it is a bug since I wound never expect to have two instances of a case object in one JVM.

@christophercurrie
Copy link
Member

You are correct that the API you are using does not handle this correctly. That said, the underlying Jackson API being used expects to create a new object; it might be possible to work around this internally, but I think it would be more correct for the API to throw an exception in this case.

What you want to do in this case is to use an updating reader:

val deserialized =
  mapper.readerForUpdating(TestObject).readValue(mapper.writeValueAsString(original))

This should allow your test to pass.

@ittaiz
Copy link

ittaiz commented Apr 9, 2017

I have a similar issue but I'm trying to deserialize a complex object which has a case object in depth 3. AFAIU the API the readerForUpdating won't help me there and I didn't find something else in the API to help me. Any ideas?

@jqno
Copy link

jqno commented Apr 18, 2019

We ran into the same issue. It turns out that Constructor.newInstance actually replaces the existing reference to the case object as well, which is quite insidious. See https://stackoverflow.com/q/55702041/127863 for some background.

We worked around this with this hack:

def deserialize[T](content: String, valueType: Class[T]): T
  getScalaSingletonInstance(valueType)
    .getOrElse(mapper.readValue(content, valueType))

private def getScalaSingletonInstance(c: Class[_ <: T]): Option[T] =
  c.getDeclaredFields.find(_.getName == "MODULE$").map(_.get(null).asInstanceOf[T])

(There is a cleaner way to check if something is a (case) object: https://stackoverflow.com/a/53456695/127863, but this solution was good enough for our purposes.)

It would be nice if Jackson supported this sort of thing out of the box though.

@pjfanning pjfanning self-assigned this Sep 3, 2021
@pjfanning pjfanning added the 2.13 label Sep 3, 2021
@pjfanning
Copy link
Member

pjfanning commented Sep 3, 2021

Thanks @jqno - I've added a ScalaObjectDeserializerModule for jackson-module-scala 2.13.0 (this change does not appear in 2.13.0-rc2) - this is based on your code snippet.

ScalaObjectDeserializerModule is not part of DefaultScalaModule. If no issues are reported in 2.13.x, I will add it to DefaultScalaModule for jackson-module-scala 2.14.0.

2e7596a has the change and test cases that demonstrate how to use ScalaObjectDeserializerModule

@pjfanning
Copy link
Member

pjfanning commented Aug 22, 2023

I'm going to make ScalaObjectDeserializerModule part of DefaultScalaModule in v2.16.0 release - see #647

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants