I have this:
package models
import play.api.libs.json._
import play.api.libs.functional.syntax._
object ModelWrites {
implicit val tmoWrites= Json.writes[TestModelObject]
implicit val ihWrites = Json.writes[IntHolder]
}
case class TestModelObject(s1:String, s2:String)
case class IntHolder(i1:Int, i2:Int)
trait HasInts {
val ints: List[IntHolder]
}
When I do this:
scala> val tmo = new TestModelObject("hello", "world") with HasInts {
val ints = List(IntHolder(1,2), IntHolder(3,4))
}
scala> Json.toJson(tmo)
res0: play.api.libs.json.JsValue = {"s1":"hello","s2":"world"}
how can I implicity serialize the mixed-in val 'ints'? Like:
scala> val someInts = List(IntHolder(8,9), IntHolder(10,11))
someInts: List[models.IntHolder] = List(IntHolder(8,9), IntHolder(10,11))
scala> Json.toJson(someInts)
res1: play.api.libs.json.JsValue = [{"i1":8,"i2":9},{"i1":10,"i2":11}]
Note: if I try: implicit val hasIntsWrites = Json.writes[HasInts] I (expectedly?) get:
[error] Models.scala:10: No unapply function found
[error] implicit val hasIntsWrites = Json.writes[HasInts]
[error] ^
You're not going to be able to use the experimental "Inception" feature (Json.writes[...]) directly here, since that only works on case classes. You can, however, build on the Writes instances that Inception can provide to accomplish what you want with only a very little boilerplate.
Note that I'm ignoring the question of whether mixing in a trait when instantiating a case class like this is a good idea—it probably isn't—but the approach I give here will work in the more general case as well.
First for the classes and imports (no changes here):
case class TestModelObject(s1: String, s2: String)
case class IntHolder(i1: Int, i2: Int)
trait HasInts { val ints: List[IntHolder] }
import play.api.libs.json._
import play.api.libs.functional.syntax._
Now we need to put all our lower-priority instances into a trait to make sure that the compiler will pick the right one, since TestModelObject with HasInts is a subtype of both TestModelObject and HasInts:
trait LowPriorityWritesInstances {
implicit val tmoWrites = Json.writes[TestModelObject]
implicit val ihWrites = Json.writes[IntHolder]
implicit object hiWrites extends OWrites[HasInts] {
def writes(hi: HasInts) = Json.obj("ints" -> hi.ints)
}
}
And now the main event:
object WritesInstances extends LowPriorityWritesInstances {
implicit val tmowhiWrites = new Writes[TestModelObject with HasInts] {
def writes(o: TestModelObject with HasInts) =
tmoWrites.writes(o) ++ implicitly[OWrites[HasInts]].writes(o)
}
}
And we're done:
scala> import WritesInstances._
import WritesInstances._
scala> val tmo = new TestModelObject("hello", "world") with HasInts {
| val ints = List(IntHolder(1, 2), IntHolder(3, 4))
| }
scala> println(Json.toJson(tmo))
{"s1":"hello","s2":"world","ints":[{"i1":1,"i2":2},{"i1":3,"i2":4}]}
As desired.
Related
I'm playing with the example ADT in the circe documentation to reproduce an issue that I have with JSON decoding.
To achieve that, I'm using ShapesDerivation :
scala> object ShapesDerivation {
|
| implicit def encodeAdtNoDiscr[Event, Repr <: Coproduct](implicit
| gen: Generic.Aux[Event, Repr],
| encodeRepr: Encoder[Repr]
| ): Encoder[Event] = encodeRepr.contramap(gen.to)
|
| implicit def decodeAdtNoDiscr[Event, Repr <: Coproduct](implicit
| gen: Generic.Aux[Event, Repr],
| decodeRepr: Decoder[Repr]
| ): Decoder[Event] = decodeRepr.map(gen.from)
|
| }
defined object ShapesDerivation
The ADT to decode is composed by two values : a simple case class and another one that I have dedicated Encoder / Decoder (to reproduce in minimal example the issue that I really have) :
scala> :paste
// Entering paste mode (ctrl-D to finish)
sealed trait Event
object Event {
case class Foo(i: Int) extends Event
case class Bar(f : FooBar) extends Event
case class FooBar(x : Int)
implicit val encoderFooBar : Encoder[FooBar] = new Encoder[FooBar] {
override def apply(a: FooBar): Json = Json.obj(("x", Json.fromInt(a.x)))
}
implicit val decodeFooBar: Decoder[FooBar] = new Decoder[FooBar] {
override def apply(c: HCursor): Result[FooBar] =
for {
x <- c.downField("x").as[Int]
} yield FooBar(x)
}
}
Then when I try to decode a simple value like this, it's working well :
scala> import ShapesDerivation._
import ShapesDerivation._
scala> decode[Event](""" { "i" : 10 }""")
res1: Either[io.circe.Error,Event] = Right(Foo(10))
But if I tried to decode something that should be a Bar that contains a Foobar, I get a decoding failure :
scala> decode[Event](""" { "x" : 10 }""")
res2: Either[io.circe.Error,Event] = Left(DecodingFailure(CNil, List()))
But this one works because I explicitely put the case class field name :
scala> decode[Event](""" { "f" : { "x" : 10 }}""")
res7: Either[io.circe.Error,Event] = Right(Bar(FooBar(10)))
I don't what to put the case class field, directly the JSON but I think it's not possible to achieve a such behaviour. The reason why I think it's impossible is how it will know to match the good case class if there is not the field but I want to be sure that there is no way with circe to do that
Here's how you do it using just semi-auto derivation.
import io.circe.Decoder.Result
import io.circe.{Decoder, Encoder, HCursor, Json}
import io.circe.parser._
import io.circe.generic.semiauto._
object Example extends App {
sealed trait Event
object Event {
case class Foo(i: Int) extends Event
object Foo {
implicit val decoder: Decoder[Foo] = deriveDecoder
}
case class Bar(f: FooBar) extends Event
object Bar {
implicit val decoder: Decoder[Bar] = Decoder[FooBar].map(Bar.apply)
}
implicit val decoder: Decoder[Event] = Decoder[Foo].widen.or(Decoder[Bar].widen)
}
case class FooBar(x: Int)
object FooBar {
implicit val encoderFooBar: Encoder[FooBar] = deriveEncoder
implicit val decodeFooBar: Decoder[FooBar] = deriveDecoder
}
println(decode[Event](""" { "x" : 10 }"""))
}
Outputs
Right(Bar(FooBar(10)))
It gets a bit noisy with the explicit decoders, but if you care about compilation speed, it's the way to go since you'll only derive decoders once.
I'd like to have a reads like Reads[Patch[T]] for a case class like this
sealed trait Patch[+T]
case class Update[+T](value: T) extends Patch[T]
case object Delete extends Patch[Nothing]
case object Ignore extends Patch[Nothing]
where a missing json value reads to Ignore, a null json value reads to Delete and a valid present value reads to Patch.
Is it possible to implement a Reads like this?
Json4s has a JNothing type, does play json have some way to achieve the same functionality (I know there is no nothing type under JsValue)?
Edit: for context on how this might be used see the json merge patch rfc.
Leaving aside discussions of whether Patch[Nothing] is a good idea, if we use this family of objects:
sealed trait Patch[+T]
case class Update[+T](value: T) extends Patch[T]
case object Delete extends Patch[Nothing]
case object Ignore extends Patch[Nothing]
We can get the desired behaviour by implementing a wrapper class:
case class PatchContainer[T](patch: Patch[T])
We have to do this as otherwise we lose the all-important distinction between a null value and a completely missing patch.
Now we can write a Reads for a PatchContainer[T] as long as we supply a suitable Reads[T] (e.g. for a String or Int etc):
class PatchContainerJson[T](implicit val rdst:Reads[T]) {
implicit val patchContainerReads = new Reads[PatchContainer[T]] {
override def reads(json: JsValue): JsResult[PatchContainer[T]] = {
json.validate[JsObject].map { obj =>
(obj \ "patch").asOpt[T].fold[PatchContainer[T]] {
if (obj.keys.contains("patch")) {
PatchContainer(Delete)
} else {
PatchContainer(Ignore)
}
} { v =>
PatchContainer(Update(v))
}
}
}
}
}
The "trick" here is detecting whether there is a patch key in the object (using keys.contains), to get the desired Delete vs Ignore behaviour.
Examples of usage:
scala> import play.api.libs.json._
scala> val json = Json.parse(""" { "patch": 42 } """ )
json: play.api.libs.json.JsValue = {"patch":42}
scala> val pcti = new PatchContainerJson[Int]()
scala> import pcti._
scala> val result = json.validate[PatchContainer[Int]]
result: play.api.libs.json.JsResult[models.PatchContainer[Int]] = JsSuccess(PatchContainer(Update(42)),)
scala> result.get.patch
res0: models.Patch[Int] = Update(42)
and
...
scala> val ignoredJson = Json.parse(""" { } """)
scala> ignoredJson.validate[PatchContainer[Int]]
res1: play.api.libs.json.JsResult[models.PatchContainer[Int]] = JsSuccess(PatchContainer(Ignore),)
and
scala> val deleteJson = Json.parse(""" { "patch": null } """)
scala> deleteJson.validate[PatchContainer[Int]]
res2: play.api.libs.json.JsResult[models.PatchContainer[Int]] = JsSuccess(PatchContainer(Delete),)
I'm generating JSON for a speed where the units may vary. I have a SpeedUnit trait and classes that extend it (Knots, MetersPerSecond, MilesPerHour). The JSON Play documentation said "To convert your own models to JsValues, you must define implicit Writes converters and provide them in scope." I got that to work in most places but not when I had a class extending a trait. What am I doing wrong? Or is there an Enum variant I could or should have used instead?
// Type mismatch: found (String, SpeedUnit), required (String, Json.JsValueWrapper)
// at 4th line from bottom: "speedunit" -> unit
import play.api.libs.json._
trait SpeedUnit {
// I added this to SpeedUnit thinking it might help, but it didn't.
implicit val speedUnitWrites = new Writes[SpeedUnit] {
def writes(x: SpeedUnit) = Json.toJson("UnspecifiedSpeedUnit")
}
}
class Knots extends SpeedUnit {
implicit val knotsWrites = new Writes[Knots] {
def writes(x: Knots) = Json.toJson("KT")
}
}
class MetersPerSecond extends SpeedUnit {
implicit val metersPerSecondWrites = new Writes[MetersPerSecond] {
def writes(x: MetersPerSecond) = Json.toJson("MPS")
}
}
class MilesPerHour extends SpeedUnit {
implicit val milesPerHourWrites = new Writes[MilesPerHour] {
def writes(x: MilesPerHour) = Json.toJson("MPH")
}
}
// ...
class Speed(val value: Int, val unit: SpeedUnit) {
implicit val speedWrites = new Writes[Speed] {
def writes(x: Speed) = Json.obj(
"value" -> value,
"speedUnit" -> unit // THIS LINE DOES NOT TYPE-CHECK
)
}
}
Writes is an example of a type class, which means you need a single instance of a Writes[A] for a given A, not for every A instance. If you're coming from a Java background, think Comparator instead of Comparable.
import play.api.libs.json._
sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit
object SpeedUnit {
implicit val speedUnitWrites: Writes[SpeedUnit] = new Writes[SpeedUnit] {
def writes(x: SpeedUnit) = Json.toJson(
x match {
case Knots => "KTS"
case MetersPerSecond => "MPS"
case MilesPerHour => "MPH"
}
)
}
}
case class Speed(value: Int, unit: SpeedUnit)
object Speed {
implicit val speedWrites: Writes[Speed] = new Writes[Speed] {
def writes(x: Speed) = Json.obj(
"value" -> x.value,
"speedUnit" -> x.unit
)
}
}
And then:
scala> Json.toJson(Speed(10, MilesPerHour))
res0: play.api.libs.json.JsValue = {"value":10,"speedUnit":"MPH"}
I've put the Writes instances in the companion objects for the two types, but they can go elsewhere (if you don't want to mix up serialization concerns in your model, for example).
You can also simplify (or at least concise-ify) this a lot with Play JSON's functional API:
sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit
case class Speed(value: Int, unit: SpeedUnit)
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val speedWrites: Writes[Speed] = (
(__ \ 'value).write[Int] and
(__ \ 'speedUnit).write[String].contramap[SpeedUnit] {
case Knots => "KTS"
case MetersPerSecond => "MPS"
case MilesPerHour => "MPH"
}
)(unlift(Speed.unapply))
Which approach you take (functional or explicit) is largely a matter of taste.
I'm on a quest to create a JSON API where some of the models could be nicely generalized. I'm a newbie in Spray, so I started out a spike with an over simplified example.
However I can't figure out what is going on with the bellow code...
I have imported both
my custom implicits and
spray.httpx.SprayJsonSupport._
As I understand this is what I have to do in order to have an implicit in scope that can convert from JsonFormat to Marshaller.
Compiler error:
TestService.scala:15: could not find implicit value for parameter um: spray.httpx.unmarshalling.FromRequestUnmarshaller[my.company.Test[my.company.X]]
Code:
package my.company
import spray.routing.HttpService
import spray.json.{JsValue, JsObject, JsonFormat, DefaultJsonProtocol}
trait TestService extends HttpService {
import my.company.TestImplicits._
import spray.httpx.SprayJsonSupport._
val test =
path("test") {
post {
entity(as[Test[X]]) {
test => {
complete(s"type: ${test.common}")
}
}
}
}
}
trait Common {
def commonData: String
}
case class X(id: Long, commonData: String) extends Common
case class Y(commonData: String) extends Common
case class Test[T <: Common](comment: String, common: T)
object TestImplicits extends DefaultJsonProtocol {
implicit val xFormat = jsonFormat2(X)
implicit val yFormat = jsonFormat1(Y)
implicit val yTestFormat: JsonFormat[Test[Y]] = new JsonFormat[Test[Y]] {
def write(test: Test[Y]) = JsObject()
def read(js: JsValue) = Test("test", Y("y"))
}
implicit val xTestFormat: JsonFormat[Test[X]] = new JsonFormat[Test[X]] {
def write(test: Test[X]) = JsObject()
def read(js: JsValue) = Test("test", X(1L, "y"))
}
}
I would appreciate any help. Thanks in advance.
SOLVED
Solution was (as #jrudolp suggested) both to:
Move implicit definitions on top of the file (surprising)
Create RootJsonFormat rather than JsonFormat.
I am wondering, would you please let me know how can I use lift-json to serialize a simple bean class into json string (I'm using v2.0-M1).
I tried:
val r = JsonDSL.pretty(JsonAST.render(myBean))
and I am getting
[error] found : MyBean
[error] required: net.liftweb.json.JsonAST.JValue
You can "decompose" a case class into JSON and then render it. Example:
scala> import net.liftweb.json.JsonAST._
scala> import net.liftweb.json.Extraction._
scala> import net.liftweb.json.Printer._
scala> implicit val formats = net.liftweb.json.DefaultFormats
scala> case class MyBean(name: String, age: Int)
scala> pretty(render(decompose(MyBean("joe", 35))))
res0: String =
{
"name":"joe",
"age":35
}
But sometimes it is easier to use DSL syntax:
scala> import net.liftweb.json.JsonDSL._
scala> val json = ("name" -> "joe") ~ ("age" -> 35)
scala> pretty(render(json))
res1: String =
{
"name":"joe",
"age":35
}