I have such model: two enumerations and one case class with two fields of these enums types:
// see later, why objects are implicit
implicit object Fruits extends Enumeration {
val Apple = Value("apple")
val Orange = Value("orange")
}
implicit object Vegetables extends Enumeration {
val Potato = Value("potato")
val Cucumber = Value("cucumber")
val Tomato = Value("tomato")
}
type Fruit = Fruits.Value
type Vegetable = Vegetables.Value
case class Pair(fruit: Fruit, vegetable: Vegetable)
I want to parse/generate JSONs to/from Pairs with spray-json. I don't want to declare separate JsonFormats for fruits and vegetables. So, I'd like to do something like this:
import spray.json._
import spray.json.DefaultJsonProtocol._
// enum is implicit here, that's why we needed implicit objects
implicit def enumFormat[A <: Enumeration](implicit enum: A): RootJsonFormat[enum.Value] =
new RootJsonFormat[enum.Value] {
def read(value: JsValue): enum.Value = value match {
case JsString(s) =>
enum.withName(s)
case x =>
deserializationError("Expected JsString, but got " + x)
}
def write(obj: enum.Value) = JsString(obj.toString)
}
// compilation error: couldn't find implicits for JF[Fruit] and JF[Vegetable]
implicit val pairFormat = jsonFormat2(Pair)
// expected value:
// spray.json.JsValue = {"fruit":"apple","vegetable":"potato"}
// but actually doesn't even compile
Pair(Fruits.Apple, Vegetables.Potato).toJson
Sadly, enumFormat doesn't produce implicit values for jsonFormat2. If I write manually two implicit declarations before pairFormat for fruits and vegetables formats, then json marshalling works:
implicit val fruitFormat: RootJsonFormat[Fruit] = enumFormat(Fruits)
implicit val vegetableFormat: RootJsonFormat[Vegetable] = enumFormat(Vegetables)
implicit val pairFormat = jsonFormat2(Pair)
// {"fruit":"apple","vegetable":"potato"}, as expected
Pair(Fruits.Apple, Vegetables.Potato).toJson
So, two questions:
How to get rid of these fruitFormat and vegetableFormat declarations?
Ideally it would be great to not make enumeration objects implicit, while keeping enumFormat function generic. Is there a way to achieve this? Maybe, using scala.reflect package or something like that.
You just have to replace enum.Value with A#Value.
Looking at spray-json #200 you can find an example of a well defined implicit enumFormat, slightly modified to leverage implicit enu retrieval:
implicit def enumFormat[T <: Enumeration](implicit enu: T): RootJsonFormat[T#Value] =
new RootJsonFormat[T#Value] {
def write(obj: T#Value): JsValue = JsString(obj.toString)
def read(json: JsValue): T#Value = {
json match {
case JsString(txt) => enu.withName(txt)
case somethingElse => throw DeserializationException(s"Expected a value from enum $enu instead of $somethingElse")
}
}
}
You can't, Scala doesn't allow implicit chaining, because it would lead to combinatorial explosions that would make the compiler too slow.
See https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html
Scala does not allow two such implicit conversions taking place, however, so one cannot got from A to C using an implicit A to B and another implicit B to C.
You'll have to explicitly produce a JsonFormat[T] for each T you want to use.
Related
I would like to design a base trait/class in Scala that can produce the following json:
trait GenericResource {
val singularName: String
val pluralName: String
}
I would inherit this trait in a case class:
case class Product(name: String) extends GenericResource {
override val singularName = "product"
override val pluralName = "products"
}
val car = Product("car")
val jsonString = serialize(car)
the output should look like: {"product":{"name":"car"}}
A Seq[Product] should produce {"products":[{"name":"car"},{"name":"truck"}]} etc...
I'm struggling with the proper abstractions to accomplish this. I am open to solutions using any JSON library (available in Scala).
Here's about the simplest way I can think of to do the singular part generically with circe:
import io.circe.{ Decoder, Encoder, Json }
import io.circe.generic.encoding.DerivedObjectEncoder
trait GenericResource {
val singularName: String
val pluralName: String
}
object GenericResource {
implicit def encodeResource[A <: GenericResource](implicit
derived: DerivedObjectEncoder[A]
): Encoder[A] = Encoder.instance { a =>
Json.obj(a.singularName -> derived(a))
}
}
And then if you have some case class extending GenericResource like this:
case class Product(name: String) extends GenericResource {
val singularName = "product"
val pluralName = "products"
}
You can do this (assuming all the members of the case class are encodeable):
scala> import io.circe.syntax._
import io.circe.syntax._
scala> Product("car").asJson.noSpaces
res0: String = {"product":{"name":"car"}}
No boilerplate, no extra imports, etc.
The Seq case is a little trickier, since circe automatically provides a Seq[A] encoder for any A that has an Encoder, but it doesn't do what you want—it just encodes the items and sticks them in a JSON array. You can write something like this:
implicit def encodeResources[A <: GenericResource](implicit
derived: DerivedObjectEncoder[A]
): Encoder[Seq[A]] = Encoder.instance {
case values # (head +: _) =>
Json.obj(head.pluralName -> Encoder.encodeList(derived)(values.toList))
case Nil => Json.obj()
}
And use it like this:
scala> Seq(Product("car"), Product("truck")).asJson.noSpaces
res1: String = {"products":[{"name":"car"},{"name":"truck"}]}
But you can't just stick it in the companion object and expect everything to work—you have to put it somewhere and import it when you need it (otherwise it has the same priority as the default Seq[A] instances).
Another issue with this encodeResources implementation is that it just returns an empty object if the Seq is empty:
scala> Seq.empty[Product].asJson.noSpaces
res2: String = {}
This is because the plural name is attached to the resource at the instance level, and if you don't have an instance there's no way to get it (short of reflection). You could of course conjure up a fake instance by passing nulls to the constructor or whatever, but that seems out of the scope of this question.
This issue (the resource names being attached to instances) is also going to be trouble if you need to decode this JSON you've encoded. If that is the case, I'd suggest considering a slightly different approach where you have something like a GenericResourceCompanion trait that you mix into the companion object for the specific resource type, and to indicate the names there. If that's not an option, you're probably stuck with reflection or fake instances, or both (but again, probably not in scope for this question).
I'm trying to wrap my head around Circe.
So, here's the model I've been given:
object Gender extends Enumeration {
type Gender = Value
val Male, Female, Unisex, Unknown = Value
}
case class Product(id: String, gender: Gender.Value)
I want to
a) encode the following example as JSON
val product = Product(id = "1234", gender = Gender.Female)
b) map the resulting JSON back onto the Product case class.
My own attempt didn't get me far:
object JsonProtocol {
implicit val productDecoder: Decoder[Product] = deriveDecoder
implicit val productEncoder: Encoder[Product] = deriveEncoder
}
result is a compile time error
Error:(52, 49) could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[A]
implicit val productDecoder: Decoder[Product] = deriveDecoder
^
I've no idea why this exception is thrown and what the solution could look like. Maybe it's the usage of the Enumeration type?
Try defining your own encoders and decoders for the enum using:
Decoder.enumDecoder[E <: Enumeration](enum: E)
Encoder.enumEncoder[E <: Enumeration](enum: E)
something like:
object JsonProtocol {
implicit val genderDecoder: Decoder[Gender.Value] = Decoder.enumDecoder(Gender)
implicit val genderEncoder: Encoder[Gender.Value] = Encoder.enumEncoder(Gender)
implicit val productDecoder: Decoder[Product] = deriveDecoder
implicit val productEncoder: Encoder[Product] = deriveEncoder
}
These are needed because the automatic/semiautomatic derivers only work for hierarchies of sealed traits and case classes as far as I know. The reason you see that error is because the derived codecs for Product will implicitly require encoders/decoders for the types of each of it's parameters. An encoder/decoder for String is a standard part of Circe, but you'll probably need to create ones for your own enumerations.
The accepted answer is deprecated (circe 0.12.0).
Circe provides now these functions:
Decoder.decodeEnumeration[E <: Enumeration](enum: E)
Encoder.encodeEnumeration[E <: Enumeration](enum: E)
With the example:
implicit val genderDecoder: Decoder[Gender.Value] = Decoder.decodeEnumeration(Gender)
implicit val genderEncoder: Encoder[Gender.Value] = Encoder.encodeEnumeration(Gender)
Have a look at enumeratum if you want to use enumerations with circe. You could then try something like this:
import enumeratum._
sealed trait Gender extends EnumEntry
case object Gender extends CirceEnum[Gender] with Enum[Gender] {
case object Male extends Gender
case object Female extends Gender
case object Unisex extends Gender
case object Unknown extends Gender
val values = findValues
}
Gender.values.foreach { gender =>
assert(gender.asJson == Json.fromString(gender.entryName))
}
This should work with circe's automatic derivation for use with your case classes.
For Scala 3 there is no solution today within Circe.
However there is a library that works nicely: circe-tagged-adt-codec
Here an example that works for me (the rest I do with semiautomatic derivation from Circe):
enum TestOverrideType derives JsonTaggedAdt.PureEncoder:
case Exists, NotExists, IsEquals, HasSize
I want to derive Encoder instances for value classes. With the semiauto mechanism I am not able to derive nested classes.
Image the following case class structure
{
case class Recipient(email: Recipient.Email, name: Recipient.Name)
object Recipient {
case class Email(value: String) extends AnyVal
case class Name(value: String) extends AnyVal
}
}
In an ammonite shell ( also add the Recipient case classes )
load.ivy("io.circe" %% "circe-core" % "0.6.1")
load.ivy("io.circe" %% "circe-generic" % "0.6.1")
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
Now deriving a decoder for Email results as expected in
Recipient.Email("ab#cd.com").asJson(deriveEncoder[Recipient.Email])
Json = {
"value" : "ab#cd.com"
}
Deriving an Encoder[Recipient] doesn't work
deriveDecoder[Recipient]
could not find Lazy implicit value of type
io.circe.generic.decoding.DerivedDecoder[$sess.cmd5.Recipient]
What I would like to do is deriving an Encoder[Recipient.Email] that returns the wrapped type. This little piece works if I derive the codec explicitly.
import shapeless.Unwrapped
implicit def encodeAnyVal[W <: AnyVal, U](
implicit unwrapped: Unwrapped.Aux[W, U],
encoderUnwrapped: Encoder[U]): Encoder[W] = {
Encoder.instance[W](v => encoderUnwrapped(unwrapped.unwrap(v)))
}
Recipient.Email("ab#cd.com").asJson(encodeAnyVal[Recipient.Email, String])
res11: Json = "ab#cd.com"
Still I can't derive a Encoder[Recipient]
implicit val emailEncoder: Encoder[Recipient.Email] = encodeAnyVal[Recipient.Email, String]
implicit val nameEncoder: Encoder[Recipient.Name] = encodeAnyVal[Recipient.Name, String]
deriveDecoder[Recipient]
cmd14.sc:1: could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[$sess.cmd5.Recipient]
Has anyone done something similar?
Thanks in advance,
Muki
You should add implicit witness instance instead of type bound. Going to end up with something like this:
implicit def encodeAnyVal[W, U](
implicit ev: W <:< Anyval,
unwrapped: Unwrapped.Aux[W, U],
encoderUnwrapped: Encoder[U]): Encoder[W] = {
Encoder.instance[W](v => encoderUnwrapped(unwrapped.unwrap(v)))
}
So I have two classes in my project
case class Item(id: Int, name: String)
and
case class Order(id: Int, items: List[Item])
I'm trying to make reads and writes properties for Order but I get a compiler error saying:
"No unapply or unapplySeq function found"
In my controller I have the following:
implicit val itemReads = Json.reads[Item]
implicit val itemWrites = Json.writes[Item]
implicit val listItemReads = Json.reads[List[Item]]
implicit val listItemWrites = Json.writes[List[Item]]
The code works for itemReads and itemWrites but not for the bottom two. Can anyone tell me where I'm going wrong, I'm new to Play framework.
Thank you for your time.
The "No unapply or unapplySeq function found" error is caused by these two:
implicit val listItemReads = Json.reads[List[Item]]
implicit val listItemWrites = Json.writes[List[Item]]
Just throw them away. As Ende said, Play knows how to deal with lists.
But you need Reads and Writes for Order too! And since you do both reading and writing, it's simplest to define a Format, a mix of the Reads and Writes traits. This should work:
case class Item(id: Int, name: String)
object Item {
implicit val format = Json.format[Item]
}
case class Order(id: Int, items: List[Item])
object Order {
implicit val format = Json.format[Order]
}
Above, the ordering is significant; Item and the companion object must come before Order.
So, once you have all the implicit converters needed, the key is to make them properly visible in the controllers. The above is one solution, but there are other ways, as I learned after trying to do something similar.
You don't actually need to define those two implicits, play already knows how to deal with a list:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> case class Item(id: Int, name: String)
defined class Item
scala> case class Order(id: Int, items: List[Item])
defined class Order
scala> implicit val itemReads = Json.reads[Item]
itemReads: play.api.libs.json.Reads[Item] = play.api.libs.json.Reads$$anon$8#478fdbc9
scala> implicit val itemWrites = Json.writes[Item]
itemWrites: play.api.libs.json.OWrites[Item] = play.api.libs.json.OWrites$$anon$2#26de09b8
scala> Json.toJson(List(Item(1, ""), Item(2, "")))
res0: play.api.libs.json.JsValue = [{"id":1,"name":""},{"id":2,"name":""}]
scala> Json.toJson(Order(10, List(Item(1, ""), Item(2, ""))))
res1: play.api.libs.json.JsValue = {"id":10,"items":[{"id":1,"name":""},{"id":2,"name":""}]}
The error you see probably happens because play uses the unapply method to construct the macro expansion for your read/write and List is an abstract class, play-json needs concrete type to make the macro work.
This works:
case class Item(id: Int, name: String)
case class Order(id: Int, items: List[Item])
implicit val itemFormat = Json.format[Item]
implicit val orderFormat: Format[Order] = (
(JsPath \ "id").format[Int] and
(JsPath \ "items").format[JsArray].inmap(
(v: JsArray) => v.value.map(v => v.as[Item]).toList,
(l: List[Item]) => JsArray(l.map(item => Json.toJson(item)))
)
)(Order.apply, unlift(Order.unapply))
This also allows you to customize the naming for your JSON object. Below is an example of the serialization in action.
Json.toJson(Order(1, List(Item(2, "Item 2"))))
res0: play.api.libs.json.JsValue = {"id":1,"items":[{"id":2,"name":"Item 2"}]}
Json.parse(
"""
|{"id":1,"items":[{"id":2,"name":"Item 2"}]}
""".stripMargin).as[Order]
res1: Order = Order(1,List(Item(2,Item 2)))
I'd also recommend using format instead of read and write if you are doing symmetrical serialization / deserialization.
I am learning more and more about Scala and that nice playframework. But there are some things that bother me and that I can't get to work.
I like using Generics for some kind of collections, for example. But I need those to be stored in our database, in JSON. There is this cool auto conversion thing, but it does not work for generics, in no way I have tried :-/
Okay, to be concrete, code first:
case class InventorySlot(id: Long, item: Option[Item])
object InventorySlot {
implicit val fmt = Json.format[InventorySlot]
}
case class Inventory[T <: Item](slots: Vector[InventorySlot]) {
def length = slots.length
def items: Vector[T] = slots.map(slot => slot.item).flatten.asInstanceOf[Vector[T]]
def item(id: Long): Option[T] = {
slots.find(_.id == id) match {
case Some(slot: InventorySlot) =>
Some(slot.item.asInstanceOf[T])
case None =>
Logger.warn(s"slot with id $id not found")
None
}
}
}
object Inventory {
implicit val fmt = Json.format[Inventory]
}
Item is a basic abstract class of different items that can be put in that inventory. It doesn't matter. But sometimes I want to have an inventory, that just works for ItemType A, lets call it AItem.
So I want to create my inventory with something like this:
val myInventory = Inventory[AItem]("content of vector here") and when I call myInventory.item(2), then I want to get the item in slot 2, and it should be an object of type AItem, not just Item. (That's the reason why I am using generics here)
So the problem
The implicit format for Inventory does not work, obviously.
Item does, also with all special items, I can post the code for it below, and InventorySlot should work as well.
The error when compiling is:
Error:(34, 34) Play 2 Compiler:
C:\depot\mars\mainline\server\app\models\Test.scala:34: class Inventory takes type parameters
implicit val fmt = Json.format[Inventory]
^
I tried to write the read and write explicitly, like
implicit val fmt = (
(__ \ "slots").format[Vector[InventorySlot]]
)(Inventory.apply, unlift(Inventory.unapply))
wich is not even working in my IDE, and I can't find the problem.
I am confused. I don't know where my error lies, or if I am doing something wrong, or if I just miss something.
Any help will be appreciated.
I am so helpless, I even have considered doing a class for all possible inventory types, like
case class AItemInventory(protected var slots: Vector[InventorySlot]) extends Inventory[AItem](slots)
object AItemInventory {
implicit val fmt = Json.format[AItemInventory]
}
wich works. No problems, everything fine. So... I don't understand. Why is this working if it seems to be exactly the same, just hardcoded?
Appendix
The item formatter, wich works:
implicit val itemFormat = new Format[Item] {
override def reads(json: JsValue): JsResult[Item] = {
(json \ "itemType").as[ItemType] match {
case ItemType.AITEM => fmtAItem.reads(json)
}
}
override def writes(item: Item): JsValue = item match {
case subItem: AItem => fmtAItem.writes(subItem)
case _ => JsNumber(item.itemType.id)
}
}
object Inventory {
implicit def fmt[T <: Item](implicit fmt: Format[T]): Format[Inventory[T]] = new Format[Inventory[T]] {
def reads(json: JsValue): Inventory[T] = new Inventory[T] (
(json \ "blah").as[String]
)
def writes(i: Inventory[T]) = JsObject(Seq(
"blah" -> JsString(i.blah)
))
}
}
Source: documentation explains how to do it for reads and writes, and what I've done here is to combine these two for the format.