Implicit conversion of an abstract type parameter in Scala

I am new to Scala and am exploring the possibilities of implicit transformations and the famous pie template. I tried to create a model class whose id value is indicated as an abstract type to avoid leakage of implementation information. I also mixed it with a cake pattern template wrapper. Everything works fine, except for the implicit conversion from id to JSON (as part of Play). The Scala compiler simply cannot find the implicit conversion, no matter what I do.

Here is the code that reproduces the problem:

import anorm._ import play.api.libs.json._ trait Card { type Key val NoId: Key val id: Key val name: String } trait CardModelComponent { val cardModel: CardModel trait CardModel { def findById(id: Long): Option[Card] def findAll: Seq[Card] def delete(id: Long) def create(name: String): Option[Card] } } trait CardModelComponentImpl extends CardModelComponent { case class CardImpl(id: Pk[Long], name: String) extends Card { type Key = Pk[Long] object Key extends Writes[Key] { implicit def writes(key: Key): JsValue = { key match { case Id(idVal: Long) => JsNumber(idVal) case _ => JsNull } } } val NoId = NotAssigned } class CardModelImpl extends CardModel { def findById(id: Long): Option[Card] = { None } def findAll: Seq[Card] = { Seq(CardImpl(Id(1), "Some card"))} def delete(id: Long) {} def create(name: String): Option[Card] = { Some(CardImpl(Id(1), name)) } } } object ComponentsRegistry extends CardModelComponentImpl { val cardModel = new CardModelImpl } val card = ComponentsRegistry.cardModel.create("Test card").get Json.toJson(card.id) 

The error output that I get is as follows:

 > card: Card = CardImpl(1,Test card) > <console>:19: error: No Json deserializer found for type card.Key. Try to implem ent an implicit Writes or Format for this type. Json.toJson(card.id) ^ 

Is there any way to make it work? It seems that the wrapper on the cake template hides too much type information from the compiler, I assume, from the type name card.Key .

I also tried to create a Writer implementation for Pk with the same error as the result.

+4
source share
3 answers

As Jesper Nordenberg wrote, you must bring a copy of Writes [Key] into the scope. One way to do this is to require the implementation of the Map to publish an instance of Writer and then import it if you call toJson, for example:

...

 trait Card { type Key val NoId: Key val id: Key val name: String implicit val Key: Writes[Key] } 

...

 trait CardModelComponentImpl extends CardModelComponent { case class CardImpl(id: Pk[Long], name: String) extends Card { type Key = Pk[Long] implicit object Key extends Writes[Key] { def writes(key: Key): JsValue = { key match { case Id(idVal: Long) => JsNumber(idVal) case _ => JsNull } } } val NoId = NotAssigned } class CardModelImpl extends CardModel { def findById(id: Long): Option[Card] = { None } def findAll: Seq[Card] = { Seq(CardImpl(Id(1), "Some card"))} def delete(id: Long) {} def create(name: String): Option[Card] = { Some(CardImpl(Id(1), name)) } } } 

...

 val card = ComponentsRegistry.cardModel.create("Test card").get import card.Key Json.toJson(card.id) 

or pass it explicitly:

 val card = ComponentsRegistry.cardModel.create("Test card").get Json.toJson(card.id)(card.Key) 
+1
source

This is an instance of Writes[Key] , which should be implicit, not the writes() element. The implicit instance must also be in the area where the toJson() method is toJson() .

+2
source

I changed your code a bit. Hope this helps you.

 package models import anorm._ import play.api.libs.json._ trait Card[T] { val NoId: T val id: T val name: String } trait CardModelComponent[T] { val cardModel: CardModel trait CardModel { def findById(id: Long): Option[Card[T]] def findAll: Seq[Card[T]] def delete(id: Long) def create(name: String): Option[Card[T]] } } trait CardModelComponentImpl extends CardModelComponent[Pk[Long]] { type Key = Pk[Long] case class CardImpl(id: Key, name: String) extends Card[Key] { val NoId = NotAssigned } class CardModelImpl extends CardModel { def findById(id: Long): Option[Card[Key]] = { None } def findAll: Seq[Card[Key]] = { Seq(CardImpl(Id(1), "Some card"))} def delete(id: Long) {} def create(name: String): Option[Card[Key]] = { Some(CardImpl(Id(1), name)) } } } object ComponentsRegistry extends CardModelComponentImpl { val cardModel = new CardModelImpl implicit object KeyWrites extends Writes[Key] { def writes(key: Key): JsValue = { key match { case Id(idVal: Long) => JsNumber(idVal) case _ => JsNull } } } } 

And then you can use like this:

 import models.ComponentsRegistry import models.ComponentsRegistry.KeyWrites val card = ComponentsRegistry.cardModel.create("Test card").get Json.toJson(card.id) 
+1
source

All Articles