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.
Related
I have a sealed trait that encapsulates an enum type like below:
sealed trait MyType
object MyType {
case object Type1 extends MyType
case object Type2 extends MyType
}
I will get this as a JSON request and I'm finding it a bit difficult to read the incoming String to a case object. Here is what I have:
implicit val myFormat: Format[MyType] = new Format[MyType] {
override def reads(json: JsValue): JsResult[MyType] = (json \ "type").as[String] match {
case "MyType1" => MyType.Type1
case "MyType2" => MyType.Type2
}
.....
.....
It however fails compilation saying that:
Expected JsResult[MyType] but found MyType.Type1.type
What is wrong with my approach?
Fixed it like this:
implicit val myTypeReads: Reads[MyType] = Reads {
case JsString("MyType1") => JsSuccess(MyType1)
case JsString("MyType2") => JsSuccess(MyType2)
case jsValue => JsError(s"MyType $jsValue is not one of supported [${MyType.values.mkString(",")}]")
}
I have a case class with some logical constraints implemented as requires in the case class body. When trying to decode this case class from JSON representing a syntactically correct but logically invalid instance, the exception is actually thrown on the calling thread instead of being returned as a Left from decode.
Reproducing code snippet:
case class Foo(bar: String) {
require(bar.length < 5)
}
object Sandbox extends App {
import io.circe.generic.auto._
import io.circe.parser._
val foo1 = decode[Foo](""" {"bar":"abc"} """)
println(s"foo1: $foo1")
//expected: Left(IllegalArgumentException("requirement failed"))
//actual: throws IllegalArgumentException("requirement failed")
val foo2 = decode[Foo](""" {"bar":"abcdefg"} """)
println(s"foo2: $foo2")
}
Is it possible to have this exception returned as Left from decode without throwing? Any ideas / suggestions are welcome...
TIA
M.
Use refined to derive these codecs automatically with constraints.
Or if you want to validate JSON but not the constructor, you can always adjust codecs like
case class Foo(bar: String)
object Foo {
implicit val decoder: Decoder[Foo] = deriveDecoder[Foo].emapTry(foo =>
Try(require(foo.bar.length < 5))
)
}
Sometimes you can also use intermediate type for derivation and map over it:
case class Foo(bar: String) {
require(bar.length < 5)
}
object Foo {
// allows usage of derivation, especially if you would derive several typeclasses
// and then adjust their behavior
private case class FooHelper(bar: String)
implicit val decoder: Decoder[Foo] = deriveDecoder[FooHelper].emapTry(helper =>
Try(Foo(helper.foo))
)
}
In general I would suggest not using require, especially in constructor and use a smart constructor instead.
sealed abstract case class Foo private (bar: String)
object Foo {
def parse(bar: String): Either[String, Foo] =
if (bar.length < 5) Right(new Foo(bar) {})
else Left(s"Invalid bar value: $bar")
def parseUnsafe(bar: String): Foo =
parse(bar).fold(error => throw new Exception(error), foo => foo)
private case class FooHelper(bar: String)
implicit val decoder: Decoder[Foo] = deriveDecoder[FooHelper].emapTry(helper =>
Try(parseUsafe(helper.foo))
)
}
I want to extract a case class from a JSON String, and reuse the code for every class.
Something like this question would have been perfect. But this means that I have to write for every class I want to extract.
I was hoping to do something like:
abstract class SocialMonitorParser[C <: SocialMonitorData] extends Serializable {
def toJSON(socialMonitorData: C): String = {
Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
implicit val formats = DefaultFormats
write(socialMonitorData)
}
def fromJSON(json: String): Option[C] = {
implicit val formats = DefaultFormats // Brings in default date formats etc.
val jsonObj = liftweb.json.parse(json)
try {
val socialData = jsonObj.extract[C]
Some(socialData)
} catch {
case e: Exception => {
Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
None
}
}
}
}
But it gives me the following error:
Error:(43, 39) No Manifest available for C.
val socialData = jsonObj.extract[C]
Error:(43, 39) not enough arguments for method extract: (implicit formats: net.liftweb.json.Formats, implicit mf: scala.reflect.Manifest[C])C.
Unspecified value parameter mf.
val socialData = jsonObj.extract[C]
I was hoping I could do something like this, and maybe there is a way. But I can't wrap my head around this.
I will try to extend the question with some other information. Supposing I have Twitter and Facebook data, in case class like these:
case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
case class TwitterData(...) extends SocialMonitorData{ ...}
I wish I could reuse the fromJSON and toJSON just once passing the Upper Bound type
class TwitterParser extends SocialMonitorParser[TwitterData] {
override FromJSON}
class FacebookParser extends SocialMonitorParser[FacebookData]
Much obliged.
I'm not sure why you want SocialMonitorParser to be abstract or Serializable or how are you going to use it but if you look closer at the error you may see that the compilers wants a Manifest for C. Manifest is a Scala way to preserve type information through the type erasure enforced onto generics by JVM. And if you fix that, then the code like this compiles:
import net.liftweb.json._
import net.liftweb.json.Serialization._
trait SocialMonitorData
case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
class SocialMonitorParser[C <: SocialMonitorData : Manifest] extends Serializable {
def toJSON(socialMonitorData: C): String = {
// Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
implicit val formats = DefaultFormats
write(socialMonitorData)
}
def fromJSON(json: String): Option[C] = {
implicit val formats = DefaultFormats // Brings in default date formats etc.
val jsonObj = parse(json)
try {
val socialData = jsonObj.extract[C]
Some(socialData)
} catch {
case e: Exception => {
// Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
None
}
}
}
}
and you can use it as
def test(): Unit = {
val parser = new SocialMonitorParser[FacebookData]
val src = FacebookData("fb_raw_data", "fb_id", "fb_social")
println(s"src = $src")
val json = parser.toJSON(src)
println(s"json = $json")
val back = parser.fromJSON(json)
println(s"back = $back")
}
to get the output exactly as one would expect.
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 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")))
// ...
}