3

I have this:

sealed trait Block

sealed case class Header(param1: String,
                                      param2: String,
                                      ...) extends Block

... 
(more sealed case classes that follows the same pattern)

Later, i'm grouping this blocks in a Seq, as follows:

val blocks: Seq[Block with Product with Serializable] = Seq(structure.header, structure.body, ...)

And i want to serialize every block as Json (with Play). I'm doing it as follows:

blocks.map{
      x =>
          serializeBlock(x)
    }

Definition of "serializeBlock":

def serializeBlock[A<:Block](block:A): String = {
    block match {
        case block: Header => Json.toJson(block).toString()
        case block: Body => Json.toJson(block).toString()
        ... n-times for every possible case class that mixes the block trait
    }
}

I have readers and writers for every concrete block (header, body...) but, as you see, when i mix these blocks in a Seq Scala treats it as generic type Block, for this reason i'm doing the pattern matching with every possible block type (implicit casting?). If i simply call "Json.toJson" in the Map Play complains about not finding a Reader/Writer for "block" type.

A "Block" is a fragment of a relatively big JSON. I'm getting the JSON, i'm splitting it in qualified "blocks" and then i'm saving it as a String in a Database:

"Big" JSON:

{
    "header" : {
        "param1" : "",
        ...
    },
    "body" : {
        "param1" : "",
        ...
    }
    ...
}

Blocks

{
    "param1" : "",
    ...
}

My question is: Is there any way to do the serialization without repeating n-times the "block: type" pattern? I mean: is there any way to get the concrete type of that block (knowing that the Seq is typed as superclass "block" and not as the "concrete" type of that block)?

EDIT

I have a Reader/Writer for every block as follows:

implicit val headerReader: Reads[Header] = (
    (JsPath \ "param1").read[String] and
    (JsPath \ "param2").read[String] and
    ...
)(Header.apply _)

implicit val headerWriter: Writes[Header] = (
    (JsPath \ "param1").write[String] and
    (JsPath \ "param2").write[String] and
    ...
)(unlift(Header.unapply))

EDIT 2:

Is Shapeless a way to solve this?

EDIT 3:

As Andrzej Jozwik noted: "param1" and "param2" are 'wildcard' params that i've used to define my JSON Structure here. Every block has different params.

Alejandro Echeverri
  • 1,178
  • 2
  • 16
  • 30

1 Answers1

1

shapeless' HList seems like a possible solution for you. Here's an example that seems pretty close to what you want to do:

import shapeless._

sealed trait Block
case class Test1(a: String, b: String) extends Block
object Test1 {
  implicit val writes = (
    (JsPath \ "a").write[String] and
    (JsPath \ "b").write[String]
  )(unlift(Test1.unapply))
}

case class Test2(c: String, d: String) extends Block
object Test2 {
  implicit val writes =(
    (JsPath \ "c").write[String] and
    (JsPath \ "d").write[String]
  )(unlift(Test2.unapply))
}

object serializeBlock extends Poly1 {
  implicit def default[T <: Block](implicit w: Writes[T]) = at[T] { x =>
    Json.toJson(x).toString
  }
}

val blocks = Test1("hi", "hello") :: Test2("what", "why") :: HNil

blocks.map(serializeBlock).toList // List[String]({"a": "hi", "b": "hello"}, {"c": "what", "d": "why"})

Note that each member of the HList must have an implicit Writes available for that type. If it doesn't the error you get isn't wildly helpful:

val list = Test1("hi", "hello") :: Test2("a", "b") :: NoWrites("wrong") :: HNil
list.map(serializeBlock)

with the error:

could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[serializeBlock.type,shapeless.::[Test1,shapeless.::[Test2,shapeless.::[NoWrites,shapeless.HNil]]]]

This just means that it is not possible to call serializeBlock for some member of the HList you called map on.

In this case, it can't call serializeBlock on the NoWrites object because there is no implicit Writes[NoWrites] available in the current scope.

You will get a similar error if any object in the HList does not extend Block.

gregghz
  • 3,725
  • 6
  • 38
  • 65