jackson-module-scala icon indicating copy to clipboard operation
jackson-module-scala copied to clipboard

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

Open hythloday opened this issue 10 years ago • 12 comments

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 avatar May 29 '15 00:05 hythloday

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.

christophercurrie avatar May 29 '15 01:05 christophercurrie

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.

hythloday avatar Jun 01 '15 18:06 hythloday

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 https://github.com/FasterXML/jackson-module-scala/commit/f6cf609451d01f036e87ff1070abcb0b6e991b42, 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

jarreds avatar Oct 24 '15 00:10 jarreds

Eh-eh. Very annoying problem...

pshirshov avatar Feb 02 '16 19:02 pshirshov

+1

aafa avatar Mar 20 '16 09:03 aafa

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

devshorts avatar May 19 '16 00:05 devshorts

Are there any workarounds to this?

devshorts avatar Sep 12 '16 19:09 devshorts

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

devshorts avatar Sep 16 '16 18:09 devshorts

+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.

twistedpair avatar Feb 23 '17 16:02 twistedpair

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.

cowtowncoder avatar Feb 23 '17 17:02 cowtowncoder

@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)
}

twistedpair avatar Feb 23 '17 20:02 twistedpair

@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.

cowtowncoder avatar Feb 24 '17 18:02 cowtowncoder