I have the following code:
def test = Action {
val Anon = new {
val foo = "foo"
val bar = "bar"
}
Ok(Json.toJson(Anon))
}
And I get this compilation error:
No Json deserializer found for type Object{val foo: String; val bar:
String}. Try to implement an implicit Writes or Format for this type.
What is the quick fix for this problem? I already found another question here that relates to this error, but perhaps it was more specific/complex.
As far as I can tell the only way is to introduce a structural type:
type AnonType = {
def foo:String
def bar:String
}
Then you can either do
implicit val writeAnon1 =
((__ \ "foo").write[String] and
(__ \ "bar").write[String])
{anon:AnonType => (anon.foo, anon.bar)}
or
implicit val writeAnon2 = new Writes[AnonType] {
def writes(o:AnonType) =
Json toJson Map(
"foo" -> o.foo,
"bar" -> o.bar)
}
Related
Here is my json:
{
"stringField" : "whatever",
"nestedObject": { "someProperty": "someValue"}
}
I want to map it to
case class MyClass(stringField: String, nestedObject:String)
nestedObject should not be deserialized, I want json4s to leave it as string.
resulting instance shouldBe:
val instance = MyClass(stringField="whatever", nestedObject= """ { "someProperty": "someValue"} """)
Don't understand how to do it in json4s.
You can define a custom serializer:
case object MyClassSerializer extends CustomSerializer[MyClass](f => ( {
case jsonObj =>
implicit val format = org.json4s.DefaultFormats
val stringField = (jsonObj \ "stringField").extract[String]
val nestedObject = compact(render(jsonObj \ "nestedObject"))
MyClass(stringField, nestedObject)
}, {
case myClass: MyClass =>
("stringField" -> myClass.stringField) ~
("nestedObject" -> myClass.nestedObject)
}
))
Then add it to the default formatter:
implicit val format = org.json4s.DefaultFormats + MyClassSerializer
println(parse(jsonString).extract[MyClass])
will output:
MyClass(whatever,{"someProperty":"someValue"})
Code run at Scastie
I've implemented implicit Json Reads in order to read two fields from JSON, saleId and saleType. I wanted to make getSales method return a tuple (Int, String) representing saleId and saleType accordingly. But when I call the getSales method I'm getting the following errors:
Error:(46, 79) No JSON deserializer found for type (Int, String). Try to implement an implicit Reader or JsonFormat for this type.
val salesData = (salesJson \ "sales").as[(Int, String)]
Error:(46, 79) not enough arguments for method as: (implicit reader: org.json4s.Reader[(Int, String)], implicit mf: Manifest[(Int, String)])(Int, String).
Unspecified value parameters reader, mf.
val salesData = (salesJson \ "sales").as[(Int, String)]
I have implemented implicit Json Reads so really confused with the first error. Here is my implementation:
def getsales(context: SparkContext, saleId: Int): (Int, String)= {
val url= buildUrl(context, saleId)
implicit val salesReader: Reads[(Int, String)] = (
(__ \ "id_from_API").read[Int] and
(__ \ "sale_type").read[String]
).tupled
val salesJson: JValue = parse(httpStringResponse(url, context))
val salesData = (salesJson \ "sales_stats").as[(Int, String)]
salesData
}
Two notes concerning you code:
val salesData = (salesJson \ "sales").as[(Int, String)]
val salesData = (salesJson \ "sales_stats").as[(Int, String)]
might have to be the same.
Instead of JValue you might have wanted to put JsValue in the line
val salesJson: JValue = parse(httpStringResponse(url, context))
Other than that testing your JSON reader code separately from the rest of your code might be helpful.
The following worked for me:
import org.scalatest.WordSpec
import play.api.libs.functional.syntax._
import play.api.libs.json._
class ReadsExample extends WordSpec {
"read" in {
val sales =
"""
{
"sales_stats": {
"id_from_API": 42,
"sale_type": "cheap"
}
}
""".stripMargin
implicit val salesReader: Reads[(Int, String)] = (
(JsPath \ "id_from_API").read[Int] and
(JsPath \ "sale_type").read[String]
).tupled
val salesJson: JsValue = Json.parse(sales)
val salesData = (salesJson \ "sales_stats").as[(Int, String)]
}
}
Please note that the version of play-json used here is 2.3.10.
EDIT
code example to the question in the comment
import org.scalatest.WordSpec
import play.api.libs.json.Json.reads
import play.api.libs.json.{Json, _}
class ReadsExample extends WordSpec {
"read" in {
val sales =
"""
{
"id_from_API": 9,
"sale_type": {
"main" : "a",
"sub" : "b"
}
}
""".stripMargin
val salesJson: JsValue = Json.parse(sales)
val salesData = salesJson.as[Sales]
}
}
case class Sales(id_from_API: Int, sale_type: SaleType)
case class SaleType(main: String, sub: String)
object Sales {
implicit val st: Reads[SaleType] = reads[SaleType]
implicit val of: Reads[Sales] = reads[Sales]
}
I am trying to pass in a POST request to a REST API developped with Play! (2.5) an object that I would like to use a Scala Graph (from the graph-core dependency).
It looks like the graph already has JSON serialization/deserialization methods based on lift-json, but I am not sure how to "plug" that into Play Json library. Until now I was using implicit converters (with Reads/Writes methods) but I would like to avoid having to write my own methods for the graph part since it is already part of the library itself.
For instance, let's say I have this code:
import java.util.UUID
import scalax.collection.Graph
case class Task(
id: UUID,
status: String)
case class Stuff(
id: UUID = UUID.randomUUID(),
name: String,
tasks: Option[Graph[Task, DiEdge]])
implicit val stuffWrites: Writes[Stuff] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "name").write[String]
)(unlift(Stuff.unapply))
implicit val stuffReads: Reads[Stuff] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "name").read[String]
)(Stuff.apply _)
implicit val taskWrite: Writes[Task] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "status").write[String]
)(unlift(Task.unapply))
implicit val taskReads: Reads[Task] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "status").read[String]
)(Task.apply _)
I miss the part to serialize the graph and the parenting. Should I rewrite everything from scratch, or can I rely on methods toJson/fromJson from scalax.collection.io.json ?
Since I struggled a bit to get this working, I thought I would share the code:
class UUIDSerializer extends Serializer[UUID] {
private val UUIDClass = classOf[UUID]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), UUID] = {
case (TypeInfo(UUIDClass, _), json) => json match {
case JString(id) => UUID.fromString(id)
case x => throw new MappingException("Can't convert " + x + " to UUID")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: UUID => JString(x.toString)
}
}
val extraSerializers = new UUIDSerializer :: Nil
implicit val formats = Serialization.formats(NoTypeHints) ++ extraSerializers
val taskDescriptor = new NodeDescriptor[Task](typeId = "Tasks", customSerializers=extraSerializers) {
def id(node: Any) = node match {
case Task(id, _) => id.toString
}
}
val quickJson = new Descriptor[Task](
defaultNodeDescriptor = taskDescriptor,
defaultEdgeDescriptor = Di.descriptor[Task]()
)
implicit val tasksWrites = new Writes[Graph[Task, DiEdge]] {
def writes(graph: Graph[Task, DiEdge]): JsValue = {
val json = graph.toJson(quickJson)
Json.parse(json.toString)
}
}
implicit val tasksReads = new Reads[Graph[Task, DiEdge]] {
def reads(json: JsValue): JsResult[Graph[Task, DiEdge]] = {
try {
val graph = Graph.fromJson[Task, DiEdge](json.toString, quickJson)
JsSuccess(graph)
}
catch {
case e: Exception =>
JsError(e.toString)
}
}
}
implicit def stuffModelFormat = Jsonx.formatCaseClass[Stuff]
You can try writing companion objects for yours case classes where you specify the formatting.
Example:
object Task {
implicit val taskModelFormat = Json.format[Task]
}
object Stuff {
implicit val staffModelFormat = Json.format[Stuff]
}
instead of the above implicits. With this solution compiler will resolve the known formatters for you and you could be only required to specify the missing/unknown types instead of the whole structure.
Suppose I am using json4s to parse JSON:
val str = """{"a":"aaaa", "x": 0}"""
val json = JsonMethods.parse(str)
val a = for(JObject(fields) <- json; JField("a", JString(a)) <- fields) yield a
The type of a is List[String] but I need Option[String], so I am calling headOption:
val a = (
for(JObject(fields) <- json; JField("a", JString(a)) <- fields) yield a
).headOption
Since I found myself calling headOption again and again I tried an implicit conversion:
object X { implicit def foo[A](as: List[A]): Option[A] = as.headOption }
import X.foo
val a: Option[String] =
for(JObject(fields) <- json; JField("a", JString(a)) <- fields) yield a
The implicit conversion is working but I don't like it. What would you suggest ?
One approach is to use json4's typeclasses, e.g. json4s-scalaz has those:
trait JSONR[A] {
def read(json: JValue): Result[A]
}
trait JSONW[A] {
def write(value: A): JValue
}
source
For syntactic simplicity one can define extensions methods for JValue:
implicit class JValueOps(value: JValue) {
def validate[A: JSONR]: ValidationNel[Error, A] = implicitly[JSONR[A]].read(value)
def read[A: JSONR]: Error \/ A = implicitly[JSONR[A]].read(value).disjunction.leftMap(_.head)
}
And then do a traversal and also parse the resulting JValue of the traversal like this:
val str =
"""
|{
| "a": "aaaa",
| "x": 0
|}""".stripMargin
val json = parseJson(str)
(json \ "a").read[Option[String]]
// \/-(Some(aaaa))
(json \ "b").read[Option[String]]
// \/-(None)
(json \ "a").validate[Option[String]]
// Success(Some(aaaa))
(json \ "b").validate[Option[String]]
// Success(None)
Defining your own JSONR[A]/JSONW[A] instances (and putting them in implicit scope) is possible like this:
case class MyA(a: Option[String], x: Int)
implicit val myARead: JSONR[MyA] = JSON.readE[MyA] { json =>
for {
a <- (json \ "a").read[Option[String]]
x <- (json \ "x").read[Int]
} yield MyA(a, x)
}
implicit val myAWrite: JSONW[MyA] = JSON.write[MyA] { myA =>
("a" -> myA.a) ~
("x" -> myA.x)
}
json.read[MyA]
// \/-(MyA(Some(aaaa),0))
json.validate[MyA]
// Success(MyA(Some(aaaa),0))
MyA(Some("aaaa"), 0).toJson
// JObject(List((a,JString(aaaa)), (x,JInt(0))))
Note that the read[A] and write[a] methods are glue code which is not in json4s-scalaz available yet, you can find the source here. There are also more examples.
Then json.read[A] returns a Error \/ A and json.validate[A] yields a Validation types from scalaz. There are similar types in cats.
myARead is an example of monadic parsing style (compose via flatMap). An alternative uses applicative parsing. This has the benefit that all validation errors are accumulated:
val myARead2: JSONR[MyA] = JSON.read[MyA] { json =>
(
(json \ "a").validate[Option[String]] |#|
(json \ "x").validate[Int]
).tupled.map(MyA.tupled)
}
val myARead3: JSONR[MyA] = JSON.read[MyA] {
for {
a <- field[Option[String]]("a") _
x <- field[Int]("x") _
} yield (a |#| x).tupled.map(MyA.tupled)
}
There is also https://github.com/json4s/json4s/blob/3.4/core/src/main/scala/org/json4s/JsonFormat.scala
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"}}