Use Argonaut in Play framework with Reads and Writes - json

I know how to do json parsing using play json library for play application. For example I have following code:
class PersonController extends Controller {
case class Person(age: Int, name: String)
implicit val personReads = Json.reads[Person]
implicit val personWrites = Json.writes[Person]
def create = Action(parse.json) { implicit request =>
rs.body.validate[Person] match {
case s: JsSuccess => ...
case e: JsError => ...
}
}
}
How should I write the code like Reads and Writes using Argonaut instead of Play json?

The docs for argonaut are fairly comprehensive in this regard. You need to generate a Codec with
implicit def PersonCodecJson: CodecJson[Person] =
casecodec2(Person.apply, Person.unapply)("age", "name")
And then use decodeValidation to get a Validation object back.
val result2: Validation[String, Person] =
Parse.decodeValidation[Person](json)

May be this is what you are looking for?
class PersonController extends Controller {
case class Person(age: Int, name: String)
implicit val PersonCodecJson: CodecJson[Person] =
casecodec2(Person.apply, Person.unapply)("age", "name")
val argonautParser = new BodyParser[Person] {
override def apply(v1: RequestHeader): Iteratee[Array[Byte], Either[Result, Person]] =
}
def argonautParser[T]()(implicit decodeJson: DecodeJson[T]) = parse.text map { body =>
Parse.decodeOption[T](body)
}
def create = Action(argonautParser) { implicit request =>
Ok(request.body.name) //request.body is the decoded value of type Person
}
}
Actually, you need a body parser that parses the request body using argonaut instead of play json. Hope this helps.

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!

SCALA How to parse json back to the controller?

I am new to Scala. I want to parse JSON data in scala store to database table.
My GET method looks like this (Please ignore the permissions):
def Classes = withAuth { username =>
implicit request =>
User.access(username, User.ReadXData).map { user =>
implicit val writer = new Writes[Class] {
def writes(entry: Class): JsValue = Json.obj(
"id" -> entry.id,
"name" -> entry.name
)
}
val classes = (Class.allAccessible(user))
Ok(Json.obj("success" -> true, "classes" -> classes))
}.getOrElse(Forbidden(Application.apiMessage("Not authorised"))) }
This GET method returns the json below:
"success":true,"schools":[{"id":93,"name":"Happy unniversity",}]}
I'm currently rendering the JSOn in a datatables js (editor) grid - with success
HOWEVER, I'm unable to parse and POST the JSON and store it to the database (mysql) table.
Thank you for your guidance!
Looks you are using play-json.
For class User
import play.api.libs.json.Json
final case class User(id: String, name: String)
object User {
implicit val userFormat = Json.format[User]
}
object UserJson {
def main(args: Array[String]): Unit = {
val user = User("11", "Peter")
val json = Json.toJson(user).toString()
println("json ===> " + json)
val user2 = Json.parse(json).as[User]
println("name ===> " + user2.name)
}
}
I definitely recommend this lib: "de.heikoseeberger" %% "akka-http-jackson" % "1.27.0" for akka-http.

Building a Json Format for a Case Class with Abstract Members

I am using the Play Framework and trying to build JSON validator for a class with abstract members. Shown below, the DataSource class is the base class which I am trying to validate the format against.
// SourceTypeConfig Trait.
trait SourceTypeConfig
final case class RDBMSConfig(...) extends SourceTypeConfig
object RDBMSConfig { implicit val fmt = Json.format[RDBMSConfig] }
final case class DirectoryConfig(
path: String,
pathType: String // Local, gcloud, azure, aws, etc.
) extends SourceTypeConfig
object DirectoryConfig { implicit val fmt = Json.format[DirectoryConfig] }
// FormatConfig trait.
trait FormatConfig
final case class SQLConfig(...) extends FormatConfig
object SQLConfig { implicit val fmt = Json.format[SQLConfig]}
final case class CSVConfig(
header: String,
inferSchema: String,
delimiter: String
) extends FormatConfig
object CSVConfig { implicit val fmt = Json.format[CSVConfig]}
// DataSource base class.
case class DataSource(
name: String,
sourceType: String,
sourceTypeConfig: SourceTypeConfig,
format: String,
formatConfig: FormatConfig
)
What I am hoping to accomplish:
val input: JsValue = Json.parse(
"""
{
"name" : "test1",
"sourceType" : "directory",
"sourceTypeConfig" : {"path" : "gs://test/path", "pathType" "google"},
"format" : "csv",
"formatConfig" : {"header" : "yes", "inferSchema" : "yes", "delimiter" : "|"}
}
"""
)
val inputResult = input.validate[DataSource]
What I am struggling with is building the DataSource object and defining its reads/writes/format. I would like it to contain a match based on the sourceType and format values that direct it to point towards the associated sourceTypeConfig and formatConfig's formats so it can parse out the JSON.
Instead of building a parser at the DataSource level, I defined parsers at the SourceConfig and FormatConfig levels, similar to what is shown below.
sealed trait SourceConfig{val sourceType: String}
object SourceConfig{
implicit val fmt = new Format[SourceConfig] {
def reads(json: JsValue): JsResult[SourceConfig] = {
def from(sourceType: String, data: JsObject): JsResult[SourceConfig] = sourceType match {
case "RDBMS" => Json.fromJson[RDBMSConfig](data)(RDBMSConfig.fmt)
case "directory" => Json.fromJson[DirectoryConfig](data)(DirectoryConfig.fmt)
case _ => JsError(s"Unknown source type: '$sourceType'")
}
for {
sourceType <- (json \ "sourceType").validate[String]
data <- json.validate[JsObject]
result <- from(sourceType, data)
} yield result
}
def writes(source: SourceConfig): JsValue =
source match {
case b: RDBMSConfig => Json.toJson(b)(RDBMSConfig.fmt)
case b: DirectoryConfig => Json.toJson(b)(DirectoryConfig.fmt)
}
}
}
Then, DataSource could be simply defined as:
object DataSource { implicit val fmt = Json.format[DataSource] }
Another option is to use play-json-derived-codecs library:
libraryDependencies += "org.julienrf" %% "play-json-derived-codecs" % "4.0.0"
import julienrf.json.derived.flat
implicit val format1: OFormat[RDBMSConfig] = Json.format[RDBMSConfig]
implicit val format2: OFormat[DirectoryConfig] = Json.format[DirectoryConfig]
implicit val format3: OFormat[SourceTypeConfig] = flat.oformat((__ \ "sourceType").format[String])

Error on validate Json: No implicit format for Map

I am new in Scala+Play. I am trying to import a code from the other project that creates an object o mongodb but before it, the code use Json validator with implicits. My object is formed of two types: Valunit and CreateDatumRequest. The error is on the line "implicit val createDatumRequestFmt = Json.format[CreateDatumRequest]" but says about the Valunit object on the above line.
No implicit format for Map[String,Option[nl.amc.ebioscience.rosemary.models.core.Valunit]] available.
.
#Singleton
class DataController #Inject() (securityService: SecurityService) extends Controller with JsonHelpers {
.....
case class CreateDatumRequest(
name: String,
parent: Option[Datum.Id],
remarks: Option[String],
category: Tag.Id, // Datum Category tag
dict: Map[String, Option[Valunit]]) {
def validate(workspaceId: Tag.Id): Either[String, Map[DefaultModelBase.Id, BaseEntity]] = {
........
}
object CreateDatumRequest {
implicit val valunitFmt = Json.format[Valunit]
implicit val createDatumRequestFmt = Json.format[CreateDatumRequest]
}
......
}
Try defining Reads for dict: Map[String, Option[Valunit]]).You can read more here.https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators#complex-reads

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.