Play Writes with generic type parameters - json

I have a trait Processor that looks like this:
trait Processor[A] {
def process(in: Seq[Byte]): Result[A]
}
trait Result[A]{
val ok: Boolean
val errorMessage: Option[String]
val data: Option[A]
}
A concrete implementation:
class StringProc extends Processor[String] {
def process(in: Seq[Byte]): StrResult
}
case class StrResult(...) extends Result[String]
object StrResult {
implicit val writes = Json.writes[StrResult]
}
When using a StringProc instance as type Processor[String], the return type of process unsurprisingly is Result[String], not StrResult. Unfortunately, the Writes[StrResult] seems to be useless in this case:
No Json serializer found for type Result[String]
How could I handle this situation?

You can try
object Result {
implicit def resWrites[T](implicit nested: Writes[T]): Writes[Result[T]] = OWrites[Result[T]] { res =>
Json.obj("ok" -> res.ok, "errorMessage" -> res.errorMessage,
"data" -> nested.writes(res.data))
}
}

Related

Play Json Reads nested generic serialized Json

Consider the following JSON
{
"a": "{\"b\": 12, \"c\": \"test\"}"
}
I would like to define a generic reads Reads[Outer[T]] for this kind of serialized Json
import play.api.libs.json.{Json, Reads, __}
final case class Outer[T](inner: T)
final case class SpecializedInner(b: Int, c: String)
object SpecializedInner {
implicit val reads: Reads[SpecializedInner] = Json.reads[SpecializedInner]
}
object Outer {
implicit def reads[T](implicit readsT: Reads[T]): Reads[Outer[T]] = ???
}
How can I achieve my goal? I tried to flatMap a string-reads for the field "outer.a" but got stuck since I am not able to produce a Reads[T] from the validated JSON
object Outer {
implicit def reads[T](implicit readsT: Reads[T]): Reads[Outer[T]] =
(__ \ "a").read[String].flatMap(x => Json.parse(x).validate[T])
}
You need just add map inside Outer.reads construction after validate[T] invocation.
Please, see next code for example:
object App {
final case class Outer[T](inner: T)
object Outer {
implicit def reads[T](implicit innerReads: Reads[T]): Reads[Outer[T]] = { json: JsValue =>
json.validate[String].flatMap(string => Json.parse(string).validate[T].map(Outer.apply[T]))
}
}
final case class Root[T](a: Outer[T])
object Root {
implicit def reads[T](implicit innerReads: Reads[T]): Reads[Root[T]] = Json.reads
}
final case class SpecializedInner(b: Int, c: String)
object SpecializedInner {
implicit val reads: Reads[SpecializedInner] = Json.reads
}
def main(args: Array[String]): Unit = {
val rawJson = "{\"a\": \"{\\\"b\\\": 12, \\\"c\\\": \\\"test\\\"}\"}"
println(Json.parse(rawJson).validate[Root[SpecializedInner]])
}
}
Which produced next result in my case:
JsSuccess(Root(Outer(SpecializedInner(12,test))),)
Hope this helps!

convert json to array of scala objects using spray json

I am not much familier with spray json, but I have to convert the below json into Array[myTest]
Below is the code, but it doesnt work. It throws the following errors: How do I fix them?
Error:(19, 54) Cannot find JsonReader or JsonFormat type class for Array[A$A61.this.myTest]
lazy val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
^
Error:(19, 54) not enough arguments for method convertTo: (implicit evidence$1: spray.json.JsonReader[Array[A$A61.this.myTest]])Array[A$A61.this.myTest].
Unspecified value parameter evidence$1.
lazy val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
^
Error:(10, 61) could not find implicit value for evidence parameter of type spray.json.DefaultJsonProtocol.JF[Map[String,Any]]
implicit val format: RootJsonFormat[myTest] = jsonFormat3(myTest.apply)
^
Code: ^
import spray.json.DefaultJsonProtocol._
import spray.json._
case class myTest (
id: String,
classDetails: Map[String, Any],
school: Map[String, Any])
object myTest {
implicit val format: RootJsonFormat[myTest] = jsonFormat3(myTest.apply)
}
val trainingDataRef = """[{"id":"my-id","classDetails":{"sec":"2","teacher":"John"},"school":{"name":"newschool"}}]"""
println(trainingDataRef.getClass)
val converted= trainingDataRef.toJson.convertTo[Array[myTest]]
println(converted)
spray-json has good documentation, try take a look there. Basically, you have to define your case classes and implement JsonFormat for them:
import spray.json.DefaultJsonProtocol._
import spray.json._
case class ClassDetails(sec: String, teacher: String)
object ClassDetails {
implicit val format: RootJsonFormat[ClassDetails] = jsonFormat2(ClassDetails.apply)
}
case class School(name: String)
object School {
implicit val format: RootJsonFormat[School] = jsonFormat1(School.apply)
}
case class ClassInfo
(
id: String,
classDetails: ClassDetails,
school: School
)
object ClassInfo {
implicit object ClassInfoFormat extends RootJsonFormat[ClassInfo] {
def write(c: ClassInfo): JsValue = JsObject(
"id" -> JsString(c.id),
"classDetails" -> c.classDetails.toJson,
"school" -> c.school.toJson
)
def read(value: JsValue): ClassInfo = {
value.asJsObject.getFields("id", "classDetails", "school") match {
case Seq(JsString(name), details, school) =>
new ClassInfo(name, details.convertTo[ClassDetails], school.convertTo[School])
case _ => throw new DeserializationException("ClassInfo expected")
}
}
}
}
val json = """[{"id":"my-id","classDetails":{"sec":"2","teacher":"John"},"school":{"name":"newschool"}}]"""
// JSON string to case classes
val classInfos = json.parseJson.convertTo[Seq[ClassInfo]]
classInfos.zipWithIndex.foreach { case (c, idx) =>
println(s"$idx => $c")
}
println
// Seq[ClassInfo] to JSON
println(s"$classInfos: ")
println(classInfos.toJson.prettyPrint)

Implicit parameters not found

I'm having some trouble figuring out why compiler complains about not finding an implicit parameter for reads because I'm almost sure that it is in the scope. The error is the following:
Error:(13, 18) No Json deserializer found for type Config. Try to implement an implicit Reads or Format for this type.
test.validate[Config].map {
^
Error:(13, 18) not enough arguments for method validate: (implicit rds: play.api.libs.json.Reads[Config])play.api.libs.json.JsResult[wings.m2m.conf.model.Config].
Unspecified value parameter rds.
test.validate[Config].map {
^
and it happens in the following code:
import play.api.libs.json._
import play.api.libs.json.Reads._
import Config.JsonImplicits._
import scala.util.Try
object Test {
def main(args: Array[String]) {
val test = Json.obj("action" -> Config.Action.nameAcquisitionRequest.toString, "value" -> "hola")
test.validate[Config].map {
t => println(t)
t
}
}
}
/**
* Config companion object
*/
object Config {
type ValueType = String
val ActionKey = "action"
val ValueKey = "value"
object Action extends Enumeration {
type Action = Value
val nameAcquisitionRequest = Value("nameAcquisitionRequest")
val nameAcquisitionReject = Value("nameAcquisitionReject")
val nameAcquisitionAck = Value("nameAcquisitionAck")
val broadcast = Value("broadcast")
}
/**
* Json implicit conversions
*/
object JsonImplicits {
implicit object ConfigReads extends Reads[Config] {
def hTypeCast(action: Config.Action.Value, value: Config.ValueType): Config = {
action match {
case Config.Action.nameAcquisitionRequest => NameAcquisitionRequest(value)
case Config.Action.nameAcquisitionReject => NameAcquisitionReject(value)
case Config.Action.nameAcquisitionAck => NameAcquisitionAck(value)
}
}
override def reads(json: JsValue): JsResult[Config] = json match {
case json: JsObject =>
val action = (json \ ActionKey).as[String]
Try(Config.Action.withName(action)) map {
a =>
val value = (json \ ValueKey).as[String]
JsSuccess(hTypeCast(a, value))
} getOrElse (JsError("Can't convert to Config"))
case _ => JsError("Can't convert to Config")
}
}
implicit object ConfigWrites extends OWrites[Config] {
def jsObjectCreator(action: Config.Action.Value, value: Config.ValueType): JsObject = {
Json.obj(ActionKey -> action.toString, ValueKey -> Json.toJson(value))
}
override def writes(o: Config): JsObject = o match {
case c: NameAcquisitionRequest => jsObjectCreator(Config.Action.nameAcquisitionRequest, c.value)
case c: NameAcquisitionReject => jsObjectCreator(Config.Action.nameAcquisitionReject, c.value)
case c: NameAcquisitionAck => jsObjectCreator(Config.Action.nameAcquisitionAck, c.value)
}
}
}
}
sealed trait Config {
val value: Config.ValueType
}
/**
* Intermediate config message
* #param value
*/
case class NameAcquisitionRequest(override val value: String)
extends Config
case class NameAcquisitionReject(override val value: String)
extends Config
case class NameAcquisitionAck(override val value: String)
extends Config
case class Broadcast(override val value: String)
extends Config
the error occurs when executing the main method on the Test object. To make this example work, make sure to add the following dependency in the SBT: "com.typesafe.play" %% "play-json" % "2.4.1" . And I'm not sure, but maybe this resolver is needed: resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"
I am not sure what you are trying to achieve and whether this solves your problem but here you go:
test.validate[Config](Config.JsonImplicits.ConfigReads).map {
t => println(t)
t
}

Play Framework / Scala: abstract repository and Json de/serialization

This question is maybe more about Scala than Play, but here it is: I am trying to achieve an abstraction of a repository for common DB operations.
trait Entity {
def id: UUID
}
trait Repository[T <: Entity] {
val JSON_KEY_ID = "_id"
def collection: JSONCollection
def insert(t: T): Future[Either[String, T]] = {
collection.insert(t).map(wr => if (wr.ok) Right(t) else Left(wr.getMessage()))
.recover { case t => Left(t.getMessage) }
}
def update(t: T): Future[Either[String, T]] = {
val selector = Json.obj(JSON_KEY_ID -> t.id.toString)
collection.update(selector, t).map(wr => if (wr.ok) Right(t) else Left(wr.getMessage()))
.recover { case t => Left(t.getMessage) }
}
}
Then I have the objects I would like to use this with:
case class UserDAO(id: UUID) extends Entity[UserDAO]
object UserDAO {
val JSON_KEY_ID = "_id"
implicit val userDaoWrites: OWrites[UserDAO] = new OWrites[UserDAO] {
def writes(user: UserDAO): JsObject = Json.obj(
JSON_KEY_ID -> JsString(user.id.toString)
)
}
implicit val userDaoReads: Reads[UserDAO] = (
(__ \ JSON_KEY_ID).read[UUID]
)(UserDAO.apply _)
}
and then I define its repository like this:
class UserRepository #Inject()(val reactiveMongoApi: ReactiveMongoApi) extends Repository[UserDAO] {
val db = reactiveMongoApi.db
override def collection: JSONCollection = db.collection[JSONCollection]("users")
}
The error I get is
No Json serializer as JsObject found for type T. Try to implement an implicit OWrites or OFormat for this type.
collection.insert(t).map(wr => if (wr.ok) Right(t) else Left(wr.getMessage()))
^
I tried to provide implicit OWrites[T], or even implicit OWrites[_] to no avail. Maybe what I am trying to achieve is impossible. If not, how could I solve this? Thank you very much.
You should be able to just use a context bound.
trait Entity {
def id: UUID
}
class Repository[T <: Entity : Writes] {
...
}
That will ensure that if there exists an implicit Writes[T] in scope, it will be available for your insert and update functions.

Json.writes[List[Foo]] as "map"

I have a case class Foo(bars: List[Bar]) who is rendered as json via Json inception as an object with an array :
{"bars": [
{
"key: "4587-der",
"value": "something"
}
]
}
But I want to render the bars: List[Bar] as a "map" where Bar.key is used as key :
{"bars":{
"4587-der": {
"value": "something"
}
}
}
How can I obtains that without modifying my case class Foo ?
Thanks a lot
You can define a Writes for Bar by extending Writes[Bar] and implementing a writes method for it:
case class Bar(key: String, value: String)
implicit object BarWrites extends Writes[Bar] {
def writes(bar: Bar): JsValue = Json.obj(
bar.key -> Json.obj("value" -> bar.value)
)
}
scala> Json.stringify(Json.toJson(Bar("4587-der", "something")))
res0: String = {"4587-der":{"value":"something"}}
For those that may be interested, here is a (somewhat) crude implementation of Reads[Bar]:
implicit object BarReads extends Reads[Bar] {
def reads(js: JsValue): JsResult[Bar] = js match {
case JsObject(Seq((key, JsObject(Seq(("value", JsString(value))))))) => JsSuccess(Bar(key, value))
case _ => JsError(Seq())
}
}
scala> Json.parse(""" [{"4587-der":{"value": "something"}}] """).validate[List[Bar]]
res11: play.api.libs.json.JsResult[List[Bar]] = JsSuccess(List(Bar(4587-der,something)),)
Edit, since the OP wants the Bars merged into an object rather than an array:
You'll also have to define a special Writes[List[Bar]] as well:
implicit object BarListWrites extends Writes[List[Bar]] {
def writes(bars: List[Bar]): JsValue =
bars.map(Json.toJson(_).as[JsObject]).foldLeft(JsObject(Nil))(_ ++ _)
}
scala> val list = List(Bar("4587-der", "something"), Bar("1234-abc", "another"))
list: List[Bar] = List(Bar(4587-der,something), Bar(1234-abc,another))
scala> Json.stringify(Json.toJson(list))
res1: String = {"4587-der":{"value":"something"},"1234-abc":{"value":"another"}}