6

The following test snippet

implicit val formats = DefaultFormats + FullTypeHints(Contacts.classList)

val serialized = Serialization.write(List(Mail(field = "random@mail.com", note = "Random note.")))
println(serialized)

Serialization.read[List[Contact[_]]](serialized).isInstanceOf[List[Mail]] should be (true)

fails with

Can't find constructor for Contact[Object]
org.json4s.package$MappingException: Can't find constructor for Contact[Object]
    at org.json4s.reflect.package$.fail(package.scala:95)
    at org.json4s.reflect.ScalaSigReader$$anonfun$5.apply(ScalaSigReader.scala:21)
    at org.json4s.reflect.ScalaSigReader$$anonfun$5.apply(ScalaSigReader.scala:21)
    at scala.Option.getOrElse(Option.scala:121)
    at org.json4s.reflect.ScalaSigReader$.readConstructor(ScalaSigReader.scala:21)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder.ctorParamType(Reflector.scala:93)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$createConstructorDescriptors$3$$anonfun$15.apply(Reflector.scala:156)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$createConstructorDescriptors$3$$anonfun$15.apply(Reflector.scala:142)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
    at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59)
    at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$createConstructorDescriptors$3.apply(Reflector.scala:142)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$createConstructorDescriptors$3.apply(Reflector.scala:136)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
    at scala.collection.mutable.ArraySeq.foreach(ArraySeq.scala:74)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder.createConstructorDescriptors(Reflector.scala:136)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder.constructorsAndCompanion(Reflector.scala:121)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder.result(Reflector.scala:183)
    at org.json4s.reflect.Reflector$.createDescriptor(Reflector.scala:53)
    at org.json4s.reflect.Reflector$$anonfun$describe$1.apply(Reflector.scala:48)
    at org.json4s.reflect.Reflector$$anonfun$describe$1.apply(Reflector.scala:48)
    at org.json4s.reflect.package$Memo.apply(package.scala:36)
    at org.json4s.reflect.Reflector$.describe(Reflector.scala:48)
    at org.json4s.Extraction$$anonfun$extract$6.apply(Extraction.scala:393)
    at org.json4s.Extraction$$anonfun$extract$6.apply(Extraction.scala:392)
    at org.json4s.Extraction$.customOrElse(Extraction.scala:606)
    at org.json4s.Extraction$.extract(Extraction.scala:392)
    at org.json4s.Extraction$CollectionBuilder$$anonfun$7.apply(Extraction.scala:410)
    at org.json4s.Extraction$CollectionBuilder$$anonfun$7.apply(Extraction.scala:410)
    at scala.collection.immutable.List.map(List.scala:284)
    at org.json4s.Extraction$CollectionBuilder.mkCollection(Extraction.scala:410)
    at org.json4s.Extraction$CollectionBuilder.result(Extraction.scala:430)
    at org.json4s.Extraction$$anonfun$extract$5.apply(Extraction.scala:382)
    at org.json4s.Extraction$$anonfun$extract$5.apply(Extraction.scala:382)
    at org.json4s.Extraction$.customOrElse(Extraction.scala:606)
    at org.json4s.Extraction$.extract(Extraction.scala:382)
    at org.json4s.Extraction$.extract(Extraction.scala:39)
    at org.json4s.ExtractableJsonAstNode.extract(ExtractableJsonAstNode.scala:21)
    at org.json4s.jackson.Serialization$.read(Serialization.scala:50)
    at org.json4s.Serialization$class.read(Serialization.scala:30)
    at org.json4s.jackson.Serialization$.read(Serialization.scala:17)

where Contact is

abstract class Contact[Field : Validable](
  val field: Field,
  val created: Long,
  val updated: Long,
  val note: String) { }

and Mail is

case class Mail(
  override val field: String,
  override val created: Long = System.currentTimeMillis(),
  override val updated: Long = System.currentTimeMillis(),
  override val note: String)
extends Contact[String](field, created, updated, note)(Mail)

case object Mail extends Validable[String] {
  override def valid(field: String): Boolean = {
    Validator.email(field)
  }
}

and test output is

[{"jsonClass":"whatever.core.entities.utility.contact.Mail","field":"random@mail.com","created":1508428385266,"updated":1508428385266,"note":"Random note."}]

Digging into the codebase of JSON4S shows that type hint is not used at all.

How may I force JSON4S to use type hints?

Cheers

E_net4
  • 27,810
  • 13
  • 101
  • 139
Dyin
  • 5,815
  • 8
  • 44
  • 69

2 Answers2

0

This is an example of how you could use FullTypeHints.

You cannot ask implicitly for a Validable[Field], neither adding multiple explicit arguments lists (and this happens also for implicitly parameters, see note below) because they are not supported by JSON4S.

Note: asking for a Validable[Field] implicitly with Contact[Field: Validable] syntax is equal to adding an additional arguments list with implicit validable: Validable[Field].

You could, instead, add an additional parameter validable: Validable[Field] to the Contact constructor or (as in the example below) a validable field that should be overridden by concrete classes (like Mail) extending the Contact abstract class.

trait Validable[T]{
  def valid(field: T): Boolean
}

abstract class Contact[Field](
  val field: Field, val created: Long, 
  val updated: Long, val note: String) {
  val validable: Validable[Field]
}

object Contacts{ val classList = List(classOf[Mail]) }

case class Mail(
  override val field: String,
  override val created: Long = System.currentTimeMillis(),
  override val updated: Long = System.currentTimeMillis(),
  override val note: String
) extends Contact[String](field, created, updated, note){
  override val validable: Validable[String] = Mail
}

case object Mail extends Validable[String] {
  override def valid(field: String): Boolean = true
}

implicit val formats = DefaultFormats + FullTypeHints(Contacts.classList)
val mail: List[Mail] = List(Mail(field = "random@mail.com", note = "Random note."))
val serialized = Serialization.write(mail)
val mailS = Serialization.read[List[Contact[_]]](serialized)

print(mail == mailS)

The serialized JSON representation is the following:

[
  {
    "jsonClass":"whatever.core.entities.utility.contact$Mail",
    "field":"random@mail.com",
    "created":1509098018776,
    "updated":1509098018776,
    "note":"Random note."
  }
]
Federico Pellegatta
  • 3,977
  • 1
  • 17
  • 29
  • Sorry, I missed the need for `Validable[Field]`, updated my answer to cope with that. Now it should fully answer your question. – Federico Pellegatta Oct 27 '17 at 10:13
  • Thanks, but that is not going to work, since `Contact[Field]` is an abstract class. `Contact[Field]` must be a trait. – Dyin Oct 28 '17 at 09:56
  • My code above compiles fine with `Contact[Field]` being abstract class (as per your question), if there are other requirements, that we are not aware of, please try to clarify those in your question. – Federico Pellegatta Oct 28 '17 at 12:00
  • It compiles, yes, but here is a runtime error. Have you ran your code? Which version of JSON4S are you using? – Dyin Oct 28 '17 at 15:37
  • Yes, my code works at runtime. I'm using json4s v3.5.3 @ scala 2.11.8. Is this exact code throwing a `MappingException` at runtime in your environment? – Federico Pellegatta Oct 28 '17 at 18:42
0

Implement Contact using traits and it will work.

Dyin
  • 5,815
  • 8
  • 44
  • 69