This question has come up a few times recently, so I'm FAQ-ing it here. Suppose I've got some case classes like this:
import io.circe._, io.circe.generic.semiauto._
object model {
case class A(a: String)
case class B(a: String, i: Int)
case class C(i: Int, b: Boolean)
implicit val encodeA: Encoder[A] = deriveEncoder
implicit val encodeB: Encoder[B] = deriveEncoder
implicit val encodeC: Encoder[C] = deriveEncoder
implicit val decodeA: Decoder[A] = deriveDecoder
implicit val decodeB: Decoder[B] = deriveDecoder
implicit val decodeC: Decoder[C] = deriveDecoder
}
And I want to encode a value that could be any one of these as JSON using circe and Shapeless coproducts.
import io.circe.shapes._, io.circe.syntax._
import shapeless._
import model._
type ABC = A :+: B :+: C :+: CNil
val c: ABC = Coproduct[ABC](C(123, false))
This looks fine at first:
scala> c.asJson
res0: io.circe.Json =
{
"i" : 123,
"b" : false
}
But the problem is that I can never decode a coproduct containing a B element, since any valid JSON document that could be decoded as B can also be decoded as A, and the coproduct decoders provided by circe-shapes try the elements in the order they appear in the coproduct.
scala> val b: ABC = Coproduct[ABC](B("xyz", 123))
b: ABC = Inr(Inl(B(xyz,123)))
scala> val json = b.asJson
json: io.circe.Json =
{
"a" : "xyz",
"i" : 123
}
scala> io.circe.jawn.decode[ABC](json.noSpaces)
res1: Either[io.circe.Error,ABC] = Right(Inl(A(xyz)))
How can I disambiguate the elements of my coproduct in my encoding?
The circe-shapes module encodes ordinary hlists and coproducts without labels, as seen above: a coproduct as the bare JSON representation of the element, and an hlist ends up as just a JSON array (the same as the default tuple encoding):
scala> ("xyz" :: List(1, 2, 3) :: false :: HNil).asJson.noSpaces
res2: String = ["xyz",[1,2,3],false]
In the case of hlists there's no danger of ambiguity because of overlapping names, but for coproducts there is. In both cases, though, you can add labels to the JSON representation using Shapeless's labeling mechanism, which tags values with a type-level symbol.
An hlist with labels is called a "record", and a coproduct with labels is a "union". Shapeless provides special syntax and operations for both of these, and circe-shapes treats both differently from unlabeled hlists or coproducts. For example (assuming the definitions and imports above):
scala> import shapeless.union._, shapeless.syntax.singleton._
import shapeless.union._
import shapeless.syntax.singleton._
scala> type ABCL = Union.`'A -> A, 'B -> B, 'C -> C`.T
defined type alias ABCL
scala> val bL: ABCL = Coproduct[ABCL]('B ->> B("xyz", 123))
bL: ABCL = Inr(Inl(B(xyz,123)))
scala> val jsonL = bL.asJson
jsonL: io.circe.Json =
{
"B" : {
"a" : "xyz",
"i" : 123
}
}
scala> io.circe.jawn.decode[ABCL](jsonL.noSpaces)
res3: Either[io.circe.Error,ABCL] = Right(Inr(Inl(B(xyz,123))))
The encoding for records similarly includes the member names:
scala> ('a ->> "xyz" :: 'b ->> List(1) :: 'c ->> false :: HNil).asJson.noSpaces
res4: String = {"c":false,"b":[1],"a":"xyz"}
In general if you want your hlist and coproduct encodings to include labels (and look like the encodings you'd get from circe-generic for case classes and sealed trait hierarchies), you can use records or coproducts to tell circe-shapes to do this with a minimal amount of extra syntactic and runtime overhead.
Related
I'm paraphrasing a question from the circe Gitter channel here.
Suppose I've got a Scala sealed trait hierarchy (or ADT) like this:
sealed trait Item
case class Cake(flavor: String, height: Int) extends Item
case class Hat(shape: String, material: String, color: String) extends Item
…and I want to be able to map back and forth between this ADT and a JSON representation like the following:
{ "tag": "Cake", "contents": ["cherry", 100] }
{ "tag": "Hat", "contents": ["cowboy", "felt", "black"] }
By default circe's generic derivation uses a different representation:
scala> val item1: Item = Cake("cherry", 100)
item1: Item = Cake(cherry,100)
scala> val item2: Item = Hat("cowboy", "felt", "brown")
item2: Item = Hat(cowboy,felt,brown)
scala> import io.circe.generic.auto._, io.circe.syntax._
import io.circe.generic.auto._
import io.circe.syntax._
scala> item1.asJson.noSpaces
res0: String = {"Cake":{"flavor":"cherry","height":100}}
scala> item2.asJson.noSpaces
res1: String = {"Hat":{"shape":"cowboy","material":"felt","color":"brown"}}
We can get a little closer with circe-generic-extras:
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.auto._
implicit val configuration: Configuration =
Configuration.default.withDiscriminator("tag")
And then:
scala> item1.asJson.noSpaces
res2: String = {"flavor":"cherry","height":100,"tag":"Cake"}
scala> item2.asJson.noSpaces
res3: String = {"shape":"cowboy","material":"felt","color":"brown","tag":"Hat"}
…but it's still not what we want.
What's the best way to use circe to derive instances like this generically for ADTs in Scala?
Representing case classes as JSON arrays
The first thing to note is that the circe-shapes module provides instances for Shapeless's HLists that use an array representation like the one we want for our case classes. For example:
scala> import io.circe.shapes._
import io.circe.shapes._
scala> import shapeless._
import shapeless._
scala> ("foo" :: 1 :: List(true, false) :: HNil).asJson.noSpaces
res4: String = ["foo",1,[true,false]]
…and Shapeless itself provides a generic mapping between case classes and HLists. We can combine these two to get the generic instances we want for case classes:
import io.circe.{ Decoder, Encoder }
import io.circe.shapes.HListInstances
import shapeless.{ Generic, HList }
trait FlatCaseClassCodecs extends HListInstances {
implicit def encodeCaseClassFlat[A, Repr <: HList](implicit
gen: Generic.Aux[A, Repr],
encodeRepr: Encoder[Repr]
): Encoder[A] = encodeRepr.contramap(gen.to)
implicit def decodeCaseClassFlat[A, Repr <: HList](implicit
gen: Generic.Aux[A, Repr],
decodeRepr: Decoder[Repr]
): Decoder[A] = decodeRepr.map(gen.from)
}
object FlatCaseClassCodecs extends FlatCaseClassCodecs
And then:
scala> import FlatCaseClassCodecs._
import FlatCaseClassCodecs._
scala> Cake("cherry", 100).asJson.noSpaces
res5: String = ["cherry",100]
scala> Hat("cowboy", "felt", "brown").asJson.noSpaces
res6: String = ["cowboy","felt","brown"]
Note that I'm using io.circe.shapes.HListInstances to bundle up just the instances we need from circe-shapes together with our custom case class instances, in order to minimize the number of things our users have to import (both as a matter of ergonomics and for the sake of keeping down compile times).
Encoding the generic representation of our ADTs
That's a good first step, but it doesn't get us the representation we want for Item itself. To do that we need some more complex machinery:
import io.circe.{ JsonObject, ObjectEncoder }
import shapeless.{ :+:, CNil, Coproduct, Inl, Inr, Witness }
import shapeless.labelled.FieldType
trait ReprEncoder[C <: Coproduct] extends ObjectEncoder[C]
object ReprEncoder {
def wrap[A <: Coproduct](encodeA: ObjectEncoder[A]): ReprEncoder[A] =
new ReprEncoder[A] {
def encodeObject(a: A): JsonObject = encodeA.encodeObject(a)
}
implicit val encodeCNil: ReprEncoder[CNil] = wrap(
ObjectEncoder.instance[CNil](_ => sys.error("Cannot encode CNil"))
)
implicit def encodeCCons[K <: Symbol, L, R <: Coproduct](implicit
witK: Witness.Aux[K],
encodeL: Encoder[L],
encodeR: ReprEncoder[R]
): ReprEncoder[FieldType[K, L] :+: R] = wrap[FieldType[K, L] :+: R](
ObjectEncoder.instance {
case Inl(l) => JsonObject("tag" := witK.value.name, "contents" := (l: L))
case Inr(r) => encodeR.encodeObject(r)
}
)
}
This tells us how to encode instances of Coproduct, which Shapeless uses as a generic representation of sealed trait hierarchies in Scala. The code may be intimidating at first, but it's a very common pattern, and if you spend much time working with Shapeless you'll recognize that 90% of this code is essentially boilerplate that you see any time you build up instances inductively like this.
Decoding these coproducts
The decoding implementation is a little worse, even, but follows the same pattern:
import io.circe.{ DecodingFailure, HCursor }
import shapeless.labelled.field
trait ReprDecoder[C <: Coproduct] extends Decoder[C]
object ReprDecoder {
def wrap[A <: Coproduct](decodeA: Decoder[A]): ReprDecoder[A] =
new ReprDecoder[A] {
def apply(c: HCursor): Decoder.Result[A] = decodeA(c)
}
implicit val decodeCNil: ReprDecoder[CNil] = wrap(
Decoder.failed(DecodingFailure("CNil", Nil))
)
implicit def decodeCCons[K <: Symbol, L, R <: Coproduct](implicit
witK: Witness.Aux[K],
decodeL: Decoder[L],
decodeR: ReprDecoder[R]
): ReprDecoder[FieldType[K, L] :+: R] = wrap(
decodeL.prepare(_.downField("contents")).validate(
_.downField("tag").focus
.flatMap(_.as[String].right.toOption)
.contains(witK.value.name),
witK.value.name
)
.map(l => Inl[FieldType[K, L], R](field[K](l)))
.or(decodeR.map[FieldType[K, L] :+: R](Inr(_)))
)
}
In general there will be a little more logic involved in our Decoder implementations, since each decoding step can fail.
Our ADT representation
Now we can wrap it all together:
import shapeless.{ LabelledGeneric, Lazy }
object Derivation extends FlatCaseClassCodecs {
implicit def encodeAdt[A, Repr <: Coproduct](implicit
gen: LabelledGeneric.Aux[A, Repr],
encodeRepr: Lazy[ReprEncoder[Repr]]
): ObjectEncoder[A] = encodeRepr.value.contramapObject(gen.to)
implicit def decodeAdt[A, Repr <: Coproduct](implicit
gen: LabelledGeneric.Aux[A, Repr],
decodeRepr: Lazy[ReprDecoder[Repr]]
): Decoder[A] = decodeRepr.value.map(gen.from)
}
This looks very similar to the definitions in our FlatCaseClassCodecs above, and the idea is the same: we're defining instances for our data type (either case classes or ADTs) by building on the instances for the generic representations of those data types. Note that I'm extending FlatCaseClassCodecs, again to minimize imports for the user.
In action
Now we can use these instances like this:
scala> import Derivation._
import Derivation._
scala> item1.asJson.noSpaces
res7: String = {"tag":"Cake","contents":["cherry",100]}
scala> item2.asJson.noSpaces
res8: String = {"tag":"Hat","contents":["cowboy","felt","brown"]}
…which is exactly what we wanted. And the best part is that this will work for any sealed trait hierarchy in Scala, no matter how many case classes it has or how many members those case classes have (although compile times will start to hurt once you're into the dozens of either), assuming all of the member types have JSON representations.
I have a dummy class Foo which has three members:
import play.api.libs.json.Json
case class Foo(id: String, fooType: FooType, nextId: Option[String])
object Foo {
implicit val fooReads = Json.reads[Foo]
implicit val fooFormat = Json.format[Foo]
}
where FooType is defined as
import play.api.libs.json.Json
case class FooType(a: String, b: String)
object FooType {
implicit val fooTypeReads = Json.reads[FooType]
implicit val fooTypeFormat = Json.format[FooType]
}
I find some interesting behavior when serializing a Foo object. If I serialize a Foo to JSON, parse the JSONified Foo, I find that all the members are parsed correctly:
val id = "id"
val fooType = FooType("a", "b")
val nextId = None
val foo = Foo(id, fooType, nextId)
val jsonFoo = Json.toJson(foo)
val parsedFoo = Json.parse(jsonFoo.toString).as[Foo]
assert(parsedFoo == foo)
assert(parsedFoo.id == id)
assert(parsedFoo.fooType == fooType)
assert(parsedFoo.nextId.isEmpty)
This is good, because that's what I expect.
However, in my next test, I find that the nextId field is not serializable at all:
val id = "id"
val fooType = FooType("a", "b")
val nextId = None
val foo = Foo(id, fooType, nextId)
val jsonFoo = Json.toJson(foo)
assert((jsonFoo \ "id").as[String] == id)
assert((jsonFoo \ "fooType").as[FooType] == fooType)
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)
This fails with the following error:
Error:(38, 35) No Json deserializer found for type Option[String]. Try to implement an implicit Reads or Format for this type.
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)
Error:(38, 35) not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[Option[String]])Option[String].
Unspecified value parameter fjs.
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)
Similarly, I find that when I print the JSON object dumped by Json.toJson(foo), the nextId field is missing from the JSON object:
println(Json.prettyPrint(jsonFoo))
{
"id" : "id",
"fooType" : {
"a" : "a",
"b" : "b"
}
}
I can, however, parse the nextId field with toOption; i.e.,
assert((jsonFoo \ "nextId").toOption.isEmpty)
How can my object be correctly parsed from JSON if one of its members isn't deserializable natively?
The nextId field is serializable, otherwise, you wouldn't have been able to write a Foo to JSON at all.
jsonFoo: play.api.libs.json.JsValue = {"id":"id","fooType":{"a":"a","b":"b"}}
The problem you're having is that there are no Reads for Option[A]. Options are handled specially by Play JSON. When using JSON combinators, we use readNullable[A] and writeNullable[A] instead of read[Option[A]] and write[Option[A]]. Likewise, when using methods to pull individual fields from a JsValue, calling as will not work because it requires an implicit Reads[A] for the type you give it (in this case a Reads[Option[String]], which does not exist).
Instead, you need to use asOpt, which will correctly handle the Option underneath:
scala> (jsonFoo \ "nextId").asOpt[String]
res1: Option[String] = None
nextId doesn't appear in the printed JSON because the value you are serializing is None. This is what is expected to happen. Since the value is optional, it gets omitted from the JSON (it's just undefined in JavaScript). If it has a value, it will appear:
scala> Json.toJson(Foo("id", FooType("a", "b"), Option("abcdef")))
res3: play.api.libs.json.JsValue = {"id":"id","fooType":{"a":"a","b":"b"},"nextId":"abcdef"}
If something wasn't serializable with Play JSON, it simply wouldn't compile.
I'm doing a thing in Scala. I have the following case class:
import shapeless._
case class Foo(param1: String, param2: HList)
I'd like to obtain a JSON representation of this type, using Circe. I'd also like to map the resulting JSON string back to the type.
The module circe-shapes does automatic derivation of HLists, and it's easy to to from HList to JSON and back. See this example:
scala> import shapeless._
import shapeless._
scala> import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
scala> import io.circe.shapes._
import io.circe.shapes._
scala> val myList = 30 :: "car" :: HNil
myList: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 30 :: car :: HNil
scala> val listJson = myList.asJson
listJson: io.circe.Json =
[
30,
"car"
]
scala> listJson.as[HList] // won't work
<console>:32: error: could not find implicit value for parameter d: io.circe.Decoder[shapeless.HList]
listJson.as[HList]
^
scala> listJson.as[::[Int, ::[String, HNil]]]
res3: io.circe.Decoder.Result[shapeless.::[Int,shapeless.::[String,shapeless.HNil]]] = Right(30 :: car :: HNil)
Case classes containing "standard" types are also trivial:
scala> case class Bar(one: String, a: Double, andAn: Int)
defined class Bar
scala> val myBar = Bar("pie", 4.35, 2)
myBar: Bar = Bar(pie,4.35,2)
scala> val barJson = myBar.asJson
barJson: io.circe.Json =
{
"one" : "pie",
"a" : 4.35,
"andAn" : 2
}
scala> barJson.as[Bar]
res5: io.circe.Decoder.Result[Bar] = Right(Bar(pie,4.35,2))
Being explicit with the type of the HList works wonders, but it kinda defeats the purpose of the HList:
scala> case class Foo2(a: String, b: ::[Int, ::[String, HNil]])
defined class Foo2
scala> val myFoo2 = Foo2("ark", 42 :: "meg" :: HNil)
myFoo2: Foo2 = Foo2(ark,42 :: meg :: HNil)
scala> val foo2Json = myFoo2.asJson
foo2Json: io.circe.Json =
{
"a" : "ark",
"b" : [
42,
"meg"
]
}
scala> foo2Json.as[Foo2]
res8: io.circe.Decoder.Result[Foo2] = Right(Foo2(ark,42 :: meg :: HNil))
Can Circe decode an arbitrary HList?
Yes, circe can do this, but you'll need to change your case class to make it hold on to more information about the HList:
import shapeless._
case class Foo[L <: HList](param1: String, param2: L)
And then the imports:
import io.circe.generic.auto._, io.circe.shapes._, io.circe.parser._, io.circe.syntax._
And then you can call asJson, etc.:
scala> val foo = Foo("ark", 42 :: "meg" :: HNil)
foo: Foo[shapeless.::[Int,shapeless.::[String,shapeless.HNil]]] = Foo(ark,42 :: meg :: HNil)
scala> foo.asJson
res0: io.circe.Json =
{
"param1" : "ark",
"param2" : [
42,
"meg"
]
}
scala> decode[Foo[Int :: String :: HNil]](res0.noSpaces).right.foreach(println)
Foo(ark,42 :: meg :: HNil)
In this case specifically, the derivation mechanisms in circe need the static information about the elements of the hlist in order to produce encoders and decoders, but more generally when you're working with hlists you'll want to avoid having members or values typed as plain old HList.
There just isn't very much you can do with something that's of type HList. You can prepend values to it, and you can get a string representation via toString, but that's about it—you can't use any of Shapeless's ops type classes, you can't recover any information about the individual types, etc. You'll almost always (probably always always) want an L <: HList, which allows you to keep the information that makes the type useful.
I am trying to follow the tutorial https://www.jamesward.com/2012/02/21/play-framework-2-with-scala-anorm-json-coffeescript-jquery-heroku but of course play-scala has changed since the tutorial (as seems to be the case with every tutorial I find). I am using 2.4.3 This requires I actually learn how things work, not necessarily a bad thing.
One thing that is giving me trouble is the getOrElse method.
Here is my Bar.scala model
package models
import play.api.db._
import play.api.Play.current
import anorm._
import anorm.SqlParser._
case class Bar(id: Option[Long], name: String)
object Bar {
val simple = {
get[Option[Long]]("id") ~
get[String]("name") map {
case id~name => Bar(id, name)
}
}
def findAll(): Seq[Bar] = {
DB.withConnection { implicit connection =>
SQL("select * from bar").as(Bar.simple *)
}
}
def create(bar: Bar): Unit = {
DB.withConnection { implicit connection =>
SQL("insert into bar(name) values ({name})").on(
'name -> bar.name
).executeUpdate()
}
}
}
and my BarFormat.scala Json formatter
package models
import play.api.libs.json._
import anorm._
package object Implicits {
implicit object BarFormat extends Format[Bar] {
def reads(json: JsValue):JsResult[Bar] = JsSuccess(Bar(
Option((json \ "id").as[Long]),
(json \ "name").as[String]
))
def writes(bar: Bar) = JsObject(Seq(
"id" -> JsNumber(bar.id.getOrElse(0L)),
"name" -> JsString(bar.name)
))
}
}
and for completeness my Application.scala controller:
package controllers
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import javax.inject.Inject
import javax.inject._
import play.api.i18n.{ I18nSupport, MessagesApi, Messages, Lang }
import play.api.libs.json._
import views._
import models.Bar
import models.Implicits._
class Application #Inject()(val messagesApi: MessagesApi) extends Controller with I18nSupport {
val barForm = Form(
single("name" -> nonEmptyText)
)
def index = Action {
Ok(views.html.index(barForm))
}
def addBar() = Action { implicit request =>
barForm.bindFromRequest.fold(
errors => BadRequest,
{
case (name) =>
Bar.create(Bar(None, name))
Redirect(routes.Application.index())
}
)
}
def listBars() = Action { implicit request =>
val bars = Bar.findAll()
val json = Json.toJson(bars)
Ok(json).as("application/json")
}
and routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
POST /addBar controllers.Application.addBar
GET / controllers.Application.index
GET /listBars controllers.Application.listBars
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
When I try to run my project I get the following error:
now bar.id is defined as an Option[Long] so bar.id.getOrElse(0L) should return a Long as far as I can tell, but it is clearly returning an Any. Can anyone help me understand why?
Thank You!
That's the way type inference works in Scala...
First of all there is an implicit conversion from Int to BigDecimal:
scala> (1 : Int) : BigDecimal
res0: BigDecimal = 1
That conversion allows for Int to be converted before the option is constructed:
scala> Some(1) : Option[BigDecimal]
res1: Option[BigDecimal] = Some(1)
If we try getOrElse on its own where the type can get fixed we get expected type Int:
scala> Some(1).getOrElse(2)
res2: Int = 1
However, this does not work (the problem you have):
scala> Some(1).getOrElse(2) : BigDecimal
<console>:11: error: type mismatch;
found : Any
required: BigDecimal
Some(1).getOrElse(2) : BigDecimal
^
Scala's implicit conversions kick in last, after type inference is performed. That makes sense, because if you don't know the type how would you know what conversions need to be applied. Scala can see that BigDecimal is expected, but it has an Int result based on the type of the Option it has. So it tries to widen the type, can't find anything that matches BigDecimal in Int's type hierarchy and fails with the error.
This works, however because the type is fixed in the variable declaration:
scala> val v = Some(1).getOrElse(2)
v: Int = 1
scala> v: BigDecimal
res4: BigDecimal = 1
So we need to help the compiler somehow - any type annotation or explicit conversion would work. Pick any one you like:
scala> (Some(1).getOrElse(2) : Int) : BigDecimal
res5: BigDecimal = 1
scala> Some(1).getOrElse[Int](2) : BigDecimal
res6: BigDecimal = 1
scala> BigDecimal(Some(1).getOrElse(2))
res7: scala.math.BigDecimal = 1
Here is the signature for Option.getOrElse method:
getOrElse[B >: A](default: ⇒ B): B
The term B >: A expresses that the type parameter B or the abstract type B refer to a supertype of type A, in this case, Any being the supertype of Long:
val l: Long = 10
val a: Any = l
So, we can do something very similar here with getOrElse:
val some: Option[Long] = Some(1)
val value: Any = option.getOrElse("potatos")
val none: Option[Long] = None
val elseValue: Any = none.getOrElse("potatos")
Which brings us to your scenario: the returned type from getOrElse will be a Any and not a BigDecimal, so you will need another way to handle this situation, like using fold, per instance:
def writes(bar: Bar) = {
val defaultValue = BigDecimal(0)
JsObject(Seq(
"id" -> JsNumber(bar.id.fold(defaultValue)(BigDecimal(_))),
"name" -> JsString(bar.name)
))
}
Some other discussions that can help you:
Why is Some(1).getOrElse(Some(1)) not of type Option[Int]?
Option getOrElse type mismatch error
I need for a frontend jquery-component a specific json object like this (ajax response):
[
{"division":"IT", "contacts":[
{“firstname”:”Carl”, “surname”:”Smith”, “empID”:1},
{“firstname”:”Henry”, “surname”:”Miller”, “empID”:2}
]},
{"division":"Sales", "contacts":[
{“firstname”:”Nancy”, “surname”:”McDonald”, “empID”:3},
{“firstname”:”Susan”, “surname”:”McBright”, “empID”:4}
]}
]
In Backend the data is read via anorm (MySQL) and transformed in following object:
List(Map("division" -> "IT", "contacts" -> List(c3,c4)), Map("division" -> "Sales", "contacts" -> List(c3,c4)))
Then I try to serialize the Object to JSon but without success (including implicit Writes converters). Below I made a simplified test case Idea-Worksheet in the same manner:
import play.api.libs.json.{JsValue, Json, Writes}
case class Contact(firstname: String, surname: String, empID: Option[Int])
case class ContactDivisionList(division: String, contacts: Seq[Contact])
implicit val ctWrites = new Writes[Contact] {
def writes(ct: Contact) = Json.obj(
"firstname" -> ct.firstname,
"surname" -> ct.surname,
"empid" -> ct.empID
)
}
implicit val sdlWrites = new Writes[ContactDivisionList] {
def writes(dlist: ContactDivisionList) = Json.obj(
"division" -> dlist.division,
"contacts" -> Json.toJson(dlist.contacts)
)
}
/*
Example
*/
val c1 = Contact("Carl","Smith",Option(1))
val c2 = Contact("Henry","Miller",Option(2))
val c3 = Contact("Nancy","McDonald",Option(3))
val c4 = Contact("Susan","McBright",Option(4))
//Test case 1 ->OK
Json.toJson(List(c1,c2,c3,c4))
//Test case 2 ->OK
val c_comp1=List(Map("contacts" -> List(c1,c2)),Map("contacts" -> List(c3,c4)))
//RESULT --> c_comp1: List[scala.collection.immutable.Map[String,List[Contact]]] = List(Map(contacts -> List(Contact(Carl,Smith,Some(1)), Contact(Henry,Miller,Some(2)))), Map(contacts -> List(Contact(Nancy,McDonald,Some(3)), Contact(Susan,McBright,Some(4)))))
Json.toJson(c_comp1)
//res1: play.api.libs.json.JsValue = [{"contacts": [{"firstname":"Carl","surname":"Smith","empid":1},{"firstname":"Henry","surname":"Miller","empid":2}]},{"contacts":[{"firstname":"Nancy","surname":"McDonald","empid":3},{"firstname":"Susan","surname":"McBright","empid":4}]}]
//Test case 3 ->Fail!!!
val c_comp2 = List(Map("division" -> "IT", "contacts" -> List(c1,c2)),Map("division" -> "Sales", "contacts" -> List(c3,c4)))
//sdlWrites: play.api.libs.json.Writes[ContactDivisionList]{def writes(dlist: ContactDivisionList): play.api.libs.json.JsObject} = $anon$2#3738baec
Json.toJson(c_comp2)
//!!!!!Error messages
/*Error:(39, 13) No Json serializer found for type List[scala.collection.immutable.Map[String,java.io.Serializable]]. Try to implement an implicit Writes or Format for this type.
Json.toJson(c_comp2)
^
Error:(39, 13) not enough arguments for method toJson: (implicit tjs: play.api.libs.json.Writes[List[scala.collection.immutable.Map[String,java.io.Serializable]] ])play.api.libs.json.JsValue.
Unspecified value parameter tjs.
Json.toJson(c_comp2)
^
*/
At the end of the script you can see "Test case 3" that got an error when i execute Json.toJson(c_comp2) -->"No Json serializer found for type..". I try a lot of things but i don't get it right. The only difference to successful "Test case 2" is that i extend the Map with a String-Tuppel.
I hope sombody can help me with that issue, Thx
Best regards
Karsten
Your problem is that the Map you have there is mapping String to the least upper bound of String (your division name) and List[Contact], which happens to be java.io.Serializable.
scala> case class Contact(firstname: String, surname: String, empID: Option[Int])
defined class Contact
scala> case class ContactDivisionList(division: String, contacts: Seq[Contact])
defined class ContactDivisionList
scala> val c1 = Contact("Carl","Smith",Option(1))
c1: Contact = Contact(Carl,Smith,Some(1))
scala> val c2 = Contact("Henry","Miller",Option(2))
c2: Contact = Contact(Henry,Miller,Some(2))
scala> val c3 = Contact("Nancy","McDonald",Option(3))
c3: Contact = Contact(Nancy,McDonald,Some(3))
scala> val c4 = Contact("Susan","McBright",Option(4))
c4: Contact = Contact(Susan,McBright,Some(4))
scala> Map("division" -> "IT", "contacts" -> List(c1,c2))
res10: scala.collection.immutable.Map[String,java.io.Serializable] = Map(division -> IT, contacts -> List(Contact(Carl,Smith,Some(1)), Contact(Henry,Miller,Some(2))))
I'm not entirely sure the nature of your problem, but if you already have List[ContactDivisionList], it's pretty straightforward to serialize that to JSON:
scala> implicit val contactWrites = Json.writes[Contact]
contactWrites: play.api.libs.json.OWrites[Contact] = play.api.libs.json.OWrites$$anon$2#3676af92
scala> implicit val contactDivisionListWrites = Json.writes[ContactDivisionList]
contactDivisionListWrites: play.api.libs.json.OWrites[ContactDivisionList] = play.api.libs.json.OWrites$$anon$2#2999d17d
scala> Json.toJson(List(ContactDivisionList("IT", List(c1,c2)), ContactDivisionList("Sales", List(c3, c4))))
res2: play.api.libs.json.JsValue = [{"division":"IT","contacts":[{"firstname":"Carl","surname":"Smith","empID":1},{"firstname":"Henry","surname":"Miller","empID":2}]},{"division":"Sales","contacts":[{"firstname":"Nancy","surname":"McDonald","empID":3},{"firstname":"Susan","surname":"McBright","empID":4}]}]
It seems to me that you should avoid having that Map in the first place. I've never worked with anorm before, but I think where you need to be looking at is the code that data structure, because at that point, you've lost typesafety. You should ideally work with ContactDivisionList or construct your object using the JsValue cases directly.