Abstract problem: Create a trait that can be mixed into the companion object of a class, to give that object a method that returns an object of that class.
Concrete problem: I'm trying to create a bunch of classes for use with RESTful service calls, that know how to serialize and de-serialize themselves, like so:
case class Foo
(
var bar : String,
var blip : String
)
extends SerializeToJson
object Foo extends DeserializeFromJson
The intended usage is like so:
var f = Foo( "abc","123" )
var json = f.json
var newF = Foo.fromJson( json )
I'm using Genson
to do the serialization/deserialization, which I access through a global object:
object JSON {
val parser = new ScalaGenson( new GensonBuilder() <...> )
}
Then I define the traits like so:
trait SerializeToJson {
def json : String = JSON.parser.toJson(this)
}
trait DeserializeFromJson[T <: DeserializeFromJson[T]] {
def fromJson( json : String ) : T = JSON.parser.fromJson( json )
}
This compiles. But this does not:
object Foo extends DeserializeFromJson[Foo]
I get the following error message:
type arguments [Foo] do not conform to trait DeserializeFromJson's
type parameter bounds [T <: DeserializeFromJson[T]]
I've tried creating a single trait, like so:
trait JsonSerialization[T <: JsonSerialization[T]] {
def json(implicit m: Manifest[JsonSerialization[T]]) : String =
JSON.parser.toJson(this)(m)
def fromJson( json : String ) : T =
JSON.parser.fromJson(json)
}
Now, if I just declare case class Foo (...) extends JsonSerialization[Foo]
then I can't call Foo.fromJson
because only an instance of class Foo
has that method, not the companion object.
If I declare object Foo extend JsonSerialization[Foo]
then I can compile and Foo
has a .fromJson
method. But at run time, the call to fromJson
thinks that T
is a JsonSerialization
, and not a Foo
, or so the following run-time error suggests:
java.lang.ClassCastException: scala.collection.immutable.HashMap$HashTrieMap cannot be cast to ...JsonSerialization
at ...JsonSerialization$class.fromJson(DataModel.scala:14)
at ...Foo.fromJson(Foo.scala:6)
And I can't declare object Foo extends Foo
because I get
module extending its companion class cannot use default constructor arguments
So I can try adding constructor parameters, and that compiles and runs, but again the run-time type when it tries to deserialize is wrong, giving me the above error.
The only thing I've been able to do that works is to define fromJson
in every companion object. But there MUST be a way to define it in a trait, and just mix in that trait. Right?
The solution is to simplify the type parameter for the trait.
trait DeserializeFromJson[T] {
def fromJson( json : String )(implicit m : Manifest[T]) : T =
JSON.parser.fromJson[T](json)(m)
}
Now, the companion object can extend DeserializeFromJson[Foo]
and when I call Foo.fromJson( json )
it is able to tell Genson the correct type information so that an object of the appropriate type is created.