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),)
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 currently have this in scala, and it does what I want:
private def prepareResponse(response: Array[SomeItem]): String = {
implicit val writes = Json.writes[SomeItem]
Json.stringify(JsObject(Map("message" -> Json.toJson(response))))
}
however, I want to generify this so that I could put it anything as the response and, as long as there are Json.writes defined for the type I'm trying to convert to Json, it would stringify it.
For example:
private def prepareResponse(response: Any): String = {
implicit val writes = Json.writes[SomeItem]
implicit val writes2 = Json.writes[SomeOtherItem]
...
Json.stringify(JsObject(Map("message" -> Json.toJson(response))))
}
This doesn't work, of course, as it says that there is no implicit write defined for Any. Adding one for Any also doesn't work, as I get the error:
No unapply or unapplySeq function found
[scalac-2.11] implicit val writeAny = Json.writes[Any]
[scalac-2.11]
What's an ideal way to do this the "right" way (if any)?
Thanks in advance!
import play.api.libs.json._
case class SomeItem(a: String, b: String)
object SomeItem {
implicit val codec = Json.format[SomeItem]
}
case class SomeOtherItem(a: String, b: String, c: String)
object SomeOtherItem {
implicit val codec = Json.format[SomeOtherItem]
}
// ...
object PlayJson extends App {
def prepareResponse[T](response: T)(implicit tjs: Writes[T]): String = {
Json.stringify(JsObject(Map("message" -> Json.toJson(response))))
}
println(prepareResponse(SomeItem("aa", "bb")))
println(prepareResponse(SomeOtherItem("aa", "bb", "cc")))
// ...
}
I have a recursive data structure that I want to write a custom spray-json serializer for.
case class Counts(var count: Int, var properties: mutable.Map[String, Counts])
object MyJsonProtocol extends DefaultJsonProtocol {
import DefaultJsonProtocol._
implicit object CountsJsonFormat extends RootJsonFormat[Counts] {
def read(json: JsValue) = ???
def write(c: Counts) = {
// Flatten count and properties into the same object.
val properties = c.properties.toJson.asJsObject
val fields = properties.fields + ("count" -> JsNumber(c.count))
JsObject(fields.toSeq: _*)
}
}
}
I've seen the documentation for how to do this for a case class if you use the builtin serialization logic, but I have no idea how to apply that to a custom serializer. I get this compiler error:
Cannot find JsonWriter or JsonFormat type class for scala.collection.mutable.Map[String,co.asku.acuity.EventCounter.Counts]
val properties = c.properties.toJson.asJsObject
^
spray-json formats can't handle mutable Maps by default (see this disussion that has happened a while ago on the mailing list). Change the type of properties to be an immutable Map (which I think is better anyways) and your format will work as expected.
To add to edi's answer, using toMap would have worked in the example I posted, to convert the mutable map to immutable.
However, I actually ran into a more complex usecase using nested mutable maps, so I just added a format to serialize them like this:
object JsonProtocol extends DefaultJsonProtocol {
import DefaultJsonProtocol._
implicit def mutableMapFormat[K :JsonFormat, V :JsonFormat] = new RootJsonFormat[mutable.Map[K, V]] {
def read(value: JsValue) = ???
def write(m: mutable.Map[K, V]) = m.toMap.toJson
}
implicit object CountsJsonFormat extends RootJsonFormat[Counts] {
// ...
}
}
This code provides serialization and deserialization support for mutable maps (and can be modified trivially for other mutable collections):
import spray.json._
import spray.json.DefaultJsonProtocol._
import scala.collection.mutable
...
implicit def mutableMapFormat[K : JsonFormat, V : JsonFormat] = new RootJsonFormat[mutable.Map[K, V]] {
def write(m : mutable.Map[K, V]) = mapFormat[K, V].write(m.toMap)
def read(value : JsValue) = mutable.Map.empty[K, V] ++ mapFormat[K, V].read(value)
}
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.
How can i deserialize json array using lift-json to scala vector?
For example:
case class Foo(bar: Vector[Bar])
trait Bar {
def value: Int
}
case class Bar1(value: Int) extends Bar
case class Bar2(value: Int) extends Bar
import net.liftweb.json.{ShortTypeHints, Serialization, DefaultFormats}
implicit val formats = new DefaultFormats {
override val typeHintFieldName = "type"
override val typeHints = ShortTypeHints(List(classOf[Foo],classOf[Bar1],classOf[Bar2]))
}
println(Serialization.writePretty(Foo(Vector(Bar1(1), Bar2(5), Bar1(1)))))
The result is:
{
"type":"Foo",
"bar":[{
"type":"Bar1",
"value":1
},{
"type":"Bar2",
"value":5
},{
"type":"Bar1",
"value":1
}]
}
Good. But when i try to deserialize this string
println(Serialization.read[Foo](Serialization.writePretty(Foo(Vector(Bar1(1), Bar2(5), Bar1(1))))))
i get an exception:
net.liftweb.json.MappingException: Parsed JSON values do not match
with class constructor args=List(Bar1(1), Bar2(5), Bar1(1)) arg
types=scala.collection.immutable.$colon$colon constructor=public
test.Foo(scala.collection.immutable.Vector)
It's means that json array associated with scala list, not vector type that defined in class Foo. I know that there is way to create custom serializer by extending net.liftweb.json.Serializer and include it to formats value. But how can i restore type of objects that stores in Vector. I wanna get result of deserializing like this:
Foo(Vector(Bar1(1), Bar2(5), Bar1(1)))
I've often been annoyed by the List-centricness of Lift, and have found myself needing to do similar things in the past. The following is the approach I've used, adapted a bit for your example:
trait Bar { def value: Int }
case class Bar1(value: Int) extends Bar
case class Bar2(value: Int) extends Bar
case class Foo(bar: Vector[Bar])
import net.liftweb.json._
implicit val formats = new DefaultFormats { outer =>
override val typeHintFieldName = "type"
override val typeHints =
ShortTypeHints(classOf[Bar1] :: classOf[Bar2] :: Nil) +
new ShortTypeHints(classOf[Foo] :: Nil) {
val FooName = this.hintFor(classOf[Foo])
override def deserialize = {
case (FooName, foo) => foo \ "bar" match {
case JArray(bars) => Foo(
bars.map(_.extract[Bar](outer, manifest[Bar]))(collection.breakOut)
)
case _ => throw new RuntimeException("Not really a Foo.")
}
}
}
}
Kind of ugly, and could probably be cleaned up a bit, but it works.
You could add an implicit conversion:
implicit def listToVect(list:List[Bar]):Vector[Bar] = list.map(identity)(breakOut)
after that, Serialization.read[Foo] works as expected.