Skip to content

Inconsistent serializing/deserializing of Option[AnyVal] compared to AnyVal #209

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

Open
hythloday opened this issue May 29, 2015 · 12 comments

Comments

@hythloday
Copy link

scala> case class A(s: String) extends AnyVal
defined class A

scala> case class B(a: A)
defined class B

scala> objectMapper.writeValueAsString(B(A("foo")))
res1: String = {"a":"foo"}

scala> objectMapper.readValue(res1, classOf[B])
res2: B = B(A(foo))

scala> case class C(a: Option[A])
defined class C

--- Above all as expected ---

scala> objectMapper.writeValueAsString(C(Some(A("foo"))))
res3: String = {"a":{"s":"foo"}}

scala> objectMapper.readValue(res3, classOf[C])
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: {"a":{"s":"foo"}}; line: 1, column: 2]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:749)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:59)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:12)
at com.fasterxml.jackson.module.scala.deser.OptionDeserializer.deserialize(OptionDeserializerModule.scala:23)
at com.fasterxml.jackson.module.scala.deser.OptionDeserializer.deserialize(OptionDeserializerModule.scala:13)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:538)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:348)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1058)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:268)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:124)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3051)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2146)

scala> objectMapper.readValue(res1, classOf[C])
res16: C = C(Some(foo))

Is this the expected behaviour? I found it very surprising.

@hythloday hythloday changed the title Inconsistent handling of Option[AnyVal Inconsistent handling of Option[AnyVal] May 29, 2015
@hythloday hythloday changed the title Inconsistent handling of Option[AnyVal] Inconsistent serializing/deserializing of Option[AnyVal] compared to AnyVal May 29, 2015
@christophercurrie
Copy link
Member

Which version of the module are you using? I believe there's been a bugfix in this space recently, though that fix might not have yet made it out into a release.

@hythloday
Copy link
Author

Thanks for the reply. I was using 2.5.1 - I tested 2.5.2, 2.5.3-SNAPSHOT, and 2.6.0-SNAPSHOT and they all exhibit the same behaviour.

@jarreds
Copy link

jarreds commented Oct 24, 2015

I'm encountering an issue with Option[AnyVal] as well. I've confirmed that it seems to be serializing and deserializing correctly with the fixes from f6cf609, but the underlying deserialized type is incorrect.

Here is a full repro from a repl on the latest release:

import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.annotation.meta.getter

case class FooAnyVal(@(JsonValue @getter) underlying: String) extends AnyVal
case class FooAnyValHolder(value: Option[FooAnyVal])

val mapper = new ObjectMapper { registerModule(DefaultScalaModule) }

val value = FooAnyValHolder(Some(FooAnyVal("bar")))

val serialized = mapper.writeValueAsString(value)
val deserialized = mapper.readValue(serialized, classOf[FooAnyValHolder])

deserialized.value.foreach { v => println(v) }

throws

java.lang.ClassCastException: java.lang.String cannot be cast to FooAnyVal
  at $anonfun$1.apply(<console>:32)
  at scala.Option.foreach(Option.scala:257)
  ... 65 elided

@pshirshov
Copy link

Eh-eh. Very annoying problem...

@aafa
Copy link

aafa commented Mar 20, 2016

+1

@devshorts
Copy link

Is this fixed for scala 2.10? This is a big nuisance

@devshorts
Copy link

Are there any workarounds to this?

@devshorts
Copy link

@lure I'm not sure what you mean? So you can't use an anyval in an option?

@twistedpair
Copy link

+1, very annoying. We like to use named Strings, by extending AnyVal, so UserId is a type, not just a free form string. However, with this wrinkle, any place we use Option[UserId] will properly deserialize, but not serialize.

@cowtowncoder
Copy link
Member

I don't work on Scala module so I don't know now or why, but it would be useful if this could be verified against latest released version of scala module, 2.8.7.
This because 2.8 has many fixes to handling of "ReferenceType"s; general JavaType abstraction that Jackson uses for Java 8 Optional, Guava option and JDK AtomicReference, as well as Scala's Option. It is possible that scala module hasn't been fully updated to benefit from rework.

Also while it seems unlikely that scala version matters here, perhaps verifying that it also occurs with Scala 2.12 (and scala module 2.8.7) would make sense.

@twistedpair
Copy link

@cowtowncoder using jackson-module-scala 2.8.7 and scala 2.11.8, I still get the casting exception for a wrapped string class that extends AnyVal.

Example of the AnyVal impl:

@JsonSerialize(using = classOf[TypedStringJacksonSerializer])
trait TypedString extends Any {
  /** Wrapped string */
  def asString: String
}

/** custom serializer */
private[ids] class TypedStringJacksonSerializer
  extends StdSerializer[TypedString](classOf[TypedString]) {

  override def serialize(
      value: TypedString,
      gen: JsonGenerator,
      provider: SerializerProvider): Unit = gen.writeString(value.asString)
}

@cowtowncoder
Copy link
Member

@twistedpair As I said I know very little about this module (or Scala for that matter); but thank you for providing the snippet. It should help whomever can help.

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

No branches or pull requests

8 participants