//Item.scala
package model
trait Item {val id: String}
class MItem(override val id: String, val name: String) extends Item
class DItem(override val id: String, override val name: String, val name2: String)
extends MItem(valid, name, name2)
object ItemWrites {
implicit val MItemWrites = new Writes[MItem] {
def writes(dItem: MItem) = Json.obj(
"id" -> mItem.id,
"name" -> mItem.name
)
}
implicit val DItemWrites = new Writes[DItem] {
def writes(dItem: DItem) = Json.obj(
"id" -> dItem.id,
"name" -> dItem.name,
"name2" -> dItem.name2
)
}
}
//Response.scala
package model
import ItemWrites.MItemWrites
import ItemWRites.DItemWrites
trait Response {
val title : String,
val items : Seq[Item],
}
case class MResponse(title: String, items: Seq[MItem]) extends Response
case class DResponse(title: String, items: Seq[DItem]) extends Response
object ResponseWrites {
implicit val mResponseWrite = new Writes[MResponse] {
def writes(mResponse: MResponse) = Json.obj(
"title" -> mResponse.title,
"items" -> mResponse.items
)
}
implicit val dResponseWrite = new Writes[DResponse] {
def writes(dResponse: DResponse) = Json.obj(
"title" -> dResponse.title,
"items" -> dResponse.items // **compile time error**
)
}
}
I get compile time error on line dResponse.items.
> [error] Note: implicit value dtcTopicPageResponseWrite is not
> applicable here because it comes after the application point and it
> lacks an explicit result type
> [error] "items" -> dResponse.items
Seems that this is happening as DItem inherits form MItem and the compile is getting confused between subclass and super class. How do i get around this.
I was able to get your code compiling in Scala 2.11, Play 2.5.x by explicitly stating the types of the Writes you've declared in ItemWrites - namely:
object ItemWrites {
implicit val MItemWrites:Writes[MItem] = new Writes[MItem] {
def writes(mItem: MItem) = Json.obj(
"id" -> mItem.id,
"name" -> mItem.name
)
}
implicit val DItemWrites:Writes[DItem] = new Writes[DItem] {
def writes(dItem: DItem) = Json.obj(
"id" -> dItem.id,
"name" -> dItem.name,
"name2" -> dItem.name2
)
}
}
Once I did that, Scala was able to find out which implicit was right and away it went.
You should explicitly set which Writer[T] is going to be used because they use inheritance. I didn't test the code below but it should compile and work;
implicit val dResponseWrite = new Writes[DResponse] {
import ItemWrites.DItemWrites
def writes(dResponse: DResponse) = Json.obj(
"title" -> dResponse.title,
"items" -> Json.toJson(dResponse.items) // **compile time error**
)
}
I think it's better for you to have a Writes for the super type Item that will just do match case to check it's type and do toJson. Don't forget to have it implicitly imported
object ItemWrites {
implicit val MItemWrites = new Writes[MItem] {
def writes(dItem: MItem) = Json.obj(
"id" -> mItem.id,
"name" -> mItem.name
)
}
implicit val DItemWrites = new Writes[DItem] {
def writes(dItem: DItem) = Json.obj(
"id" -> dItem.id,
"name" -> dItem.name,
"name2" -> dItem.name2
)
}
implicit val ItemWrites = new Writes[Item] {
def writes(item: Item) = item match {
case i: DItem => i.toJson //code for converting DItem to Json
case i: MItem => i.toJson //code to convert MItem to Json
case _ => throws new RuntimeException("Unsupported Type.")
}
}
}
Related
I've got a List which holds some Personalization-objects. The latter is defined like this:
sealed case class Personalization(firstname: String, lastname: String, keycardId: String)
I need to map this list to a Json-Array structure which has to look like this:
"personalization": [
{
"id": "firstname",
"value": "John"
},
{
"id": "lastname",
"value": "Doe"
}...
I am struggling with the part of mapping the field information to id/value pairs. Normally, I would create a play.api.libs.json.Format out of the Personalization class and let it map automatically -> Json.format[Personalization] - but this time, I need to create an array where an entry can hold n attributes.
Therefore I am asking for advice, if there is a possibility to use the Scala Play-Framework?
Any input is much appreciated, thank you!
Writing as such JSON representation is not quite complex, using Writes.transform.
import play.api.libs.json._
case class Personalization(firstname: String, lastname: String, keycardId: String) // No need to seal a case class
implicit def writes: Writes[Personalization] = {
val tx: JsValue => JsValue = {
case JsObject(fields) => Json.toJson(fields.map {
case (k, v) => Json.obj("id" -> k, "value" -> v)
})
case jsUnexpected => jsUnexpected // doesn't happen with OWrites
}
Json.writes[Personalization].transform(tx)
}
Which can be tested as bellow.
val personalization = Personalization(
firstname = "First",
lastname = "Last",
keycardId = "Foo")
val jsonRepr = Json.toJson(personalization)
// => [{"id":"firstname","value":"First"},{"id":"lastname","value":"Last"},{"id":"keycardId","value":"Foo"}]
Reading is a little bit tricky:
implicit def reads: Reads[Personalization] = {
type Field = (String, Json.JsValueWrapper)
val fieldReads = Reads.seq(Reads[Field] { js =>
for {
id <- (js \ "id").validate[String]
v <- (js \ "value").validate[JsValue]
} yield id -> v
})
val underlying = Json.reads[Personalization]
Reads[Personalization] { js =>
js.validate(fieldReads).flatMap { fields =>
Json.obj(fields: _*).validate(underlying)
}
}
}
Which can be tested as bellow.
Json.parse("""[
{"id":"firstname","value":"First"},
{"id":"lastname","value":"Last"},
{"id":"keycardId","value":"Foo"}
]""").validate[Personalization]
// => JsSuccess(Personalization(First,Last,Foo),)
Note that is approach can be used for any case class format.
Probably it is possible to do it a more elegant way I did, but you can use the following snippet:
case class Field(id: String, value: String)
object Field {
implicit val fieldFormatter: Format[Field] = Json.format[Field]
}
sealed case class Personalization(firstname: String, lastname: String, keycardId: String)
object Personalization {
implicit val personalizationFormatter: Format[Personalization] = new Format[Personalization] {
override def reads(json: JsValue): JsResult[Personalization] =
Try {
val data = (json \ "personalization").as[JsValue]
data match {
case JsArray(value) =>
val fields = value.map(_.as[Field]).map(f => f.id -> f.value).toMap
val firstname = fields.getOrElse("firstname", throw new IllegalArgumentException("Mandatory field firstname is absent."))
val lastname = fields.getOrElse("lastname", throw new IllegalArgumentException("Mandatory field lastname is absent."))
val keycardId = fields.getOrElse("keycardId", throw new IllegalArgumentException("Mandatory field keycardId is absent."))
Personalization(firstname, lastname, keycardId)
case _ => throw new IllegalArgumentException("Incorrect json format for Personalization.")
}
}.toEither.fold(e => JsError(e.getMessage), JsSuccess(_))
override def writes(o: Personalization): JsValue = {
val fields = List(Field("firstname", o.firstname), Field("lastname", o.lastname), Field("keycardId", o.keycardId))
JsObject(List("personalization" -> Json.toJson(fields)))
}
}
}
It converts {"personalization":[{"id":"firstname","value":"John"},{"id":"lastname","value":"Doe"},{"id":"keycardId","value":"1234"}]} to Personalization(John,Doe,1234) and vice versa
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])
I am using anorm to query and save elements into my postgres database.
I have a json column which I want to read as class of my own.
So for example if I have the following class
case class Table(id: Long, name:String, myJsonColumn:Option[MyClass])
case class MyClass(site: Option[String], user:Option[String])
I am trying to write the following update:
DB.withConnection { implicit conn =>
val updated = SQL(
"""UPDATE employee
|SET name = {name}, my_json_column = {myClass}
|WHERE id = {id}
""".stripMargin)
.on(
'name -> name,
'myClass -> myClass,
'custom -> id
).executeUpdate()
}
}
I also defined a implicit convertor from json to my object
implicit def columnToSocialData: Column[MyClass] = anorm.Column.nonNull[MyClass] { (value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case json: org.postgresql.util.PGobject => {
val result = Json.fromJson[MyClass](Json.parse(json.getValue))
result.fold(
errors => Left(TypeDoesNotMatch(s"Cannot convert $value: ${value.asInstanceOf[AnyRef].getClass} to Json for column $qualified")),
valid => Right(valid)
)
}
case _ => Left(TypeDoesNotMatch(s"Cannot convert $value: ${value.asInstanceOf[AnyRef].getClass} to Json for column $qualified"))
}
And the error I get is:
type mismatch;
found : (Symbol, Option[com.MyClass])
required: anorm.NamedParameter
'myClass -> myClass,
^
The solution is just to add the following:
implicit val socialDataToStatement = new ToStatement[MyClass] {
def set(s: PreparedStatement, i: Int, myClass: MyClass): Unit = {
val jsonObject = new org.postgresql.util.PGobject()
jsonObject.setType("json")
jsonObject.setValue(Json.stringify(Json.toJson(myClass)))
s.setObject(i, jsonObject)
}
}
and:
implicit object MyClassMetaData extends ParameterMetaData[MyClass] {
val sqlType = "OTHER"
val jdbcType = Types.OTHER
}
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"}}
there are two classes Foo and Bar. Foo contains a field of Bar. The question is, how do I implement an implicit json Writes for class Foo?
Here is the code:
package models
import play.api.libs.json._
case class Foo(id: String, bar: Bar)
object Foo {
implicit val implicitFooWrites = new Writes[Foo] {
def writes(foo: Foo): JsValue = {
Json.obj(
"id" -> foo.id,
"bar" -> foo.bar
)
}
}
}
case class Bar(x: String, y: Int)
object Bar {
implicit val implicitBarWrites = new Writes[Bar] {
def writes(bar: Bar): JsValue = {
Json.obj(
"x" -> bar.x,
"y" -> bar.y
)
}
}
}
When I try to compile, I get the following error:
No Json deserializer found for type models.Bar. Try to implement an
implicit Writes or Format for this type.
I don't understand this compiler error, since I implemented an implicit Writes for models.Bar class. What is the problem here?
It's a question of visibility, when declaring the implicit Writes[Foo] you are not making visible the implicit Writes[Bar] to it:
scala> :paste
// Entering paste mode (ctrl-D to finish)
import play.api.libs.json._
case class Bar(x: String, y: Int)
object Bar {
implicit val implicitBarWrites = new Writes[Bar] {
def writes(bar: Bar): JsValue = {
Json.obj(
"x" -> bar.x,
"y" -> bar.y
)
}
}
}
case class Foo(id: String, bar: Bar)
object Foo {
import Bar._
implicit val implicitFooWrites = new Writes[Foo] {
def writes(foo: Foo): JsValue = {
Json.obj(
"id" -> foo.id,
"bar" -> foo.bar
)
}
}
}
// Exiting paste mode, now interpreting.
import play.api.libs.json._
defined class Bar
defined module Bar
defined class Foo
defined module Foo
scala> Json.prettyPrint(Json.toJson(Foo("23", Bar("x", 1))))
res0: String =
{
"id" : "23",
"bar" : {
"x" : "x",
"y" : 1
}
}
Also, if you're using Play 2.1+ make sure to check out the brand new use of 2.10's macros: http://www.playframework.com/documentation/2.1.0/ScalaJsonInception
If you're happy with the use of the case classes and the val/vars' names being used as keys in the json output, as in your case BTW, then you can use the two one-liners:
implicit val barFormat = Json.writes[Bar]
implicit val fooFormat = Json.writes[Foo]
That will give you the exact equivalent:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> case class Bar(x: String, y: Int)
defined class Bar
scala> case class Foo(id: String, bar: Bar)
defined class Foo
scala> implicit val barWrites = Json.writes[Bar]
barWrites: play.api.libs.json.OWrites[Bar] = play.api.libs.json.OWrites$$anon$2#257cae95
scala> implicit val fooWrites = Json.writes[Foo]
fooWrites: play.api.libs.json.OWrites[Foo] = play.api.libs.json.OWrites$$anon$2#48f97e2a
scala> Json.prettyPrint(Json.toJson(Foo("23", Bar("x", 1))))
res0: String =
{
"id" : "23",
"bar" : {
"x" : "x",
"y" : 1
}
}