I am looking for a way to re-write the following code using a JSON parser. This is currently using scala.util.parsing.json.JSONObject which has been deprecated.
val notebookInfo = Map("notebookURL" -> notebookURL,
"user" -> user,
"name" -> name,
"mounts" -> dbutils.fs.ls("/mnt").map(_.path),
"timestamp" -> System.currentTimeMillis)
val notebookInfoJson = scala.util.parsing.json.JSONObject(notebookInfo)
I have tried the following with Circe JSON parser but getting errors:
val notebookInfo = Json.obj("notebookURL" -> Json.fromString(notebookURL),
"user" -> Json.fromString(user),
"name" -> Json.fromString(name),
"mounts" -> Json.arr(Seq(dbutils.fs.ls("/mnt").map(Json.fromString) :_*),
"timestamp" -> Json.fromLong(System.currentTimeMillis)))
val notebookInfoJson = notebookInfo
Here are the errors I am seeing:
error: type mismatch;
found : String => io.circe.Json
required: com.databricks.backend.daemon.dbutils.FileInfo => ?
"mounts" -> Json.arr(Seq(dbutils.fs.ls("/mnt").map(Json.fromString) :_*),
^
error: type mismatch;
found : (String, io.circe.Json)
required: io.circe.Json
"timestamp" -> Json.fromLong(System.currentTimeMillis)))
Please go through this article. It has few examples with JSON parsing frameworks that could be used with Scala.
import net.liftweb.json.JsonAST
import net.liftweb.json.JsonDSL._
import net.liftweb.json.Printer.{compact,pretty}
object LiftJsonWithCollections extends App {
val json = List(1, 2, 3)
println(compact(JsonAST.render(json)))
val map = Map("fname" -> "Alvin", "lname" -> "Alexander")
println(compact(JsonAST.render(map)))
}
That program prints the following output:
[1,2,3]
{"fname":"Alvin","lname":"Alexander"}
If needed also have a look on this one for creating a JSON String from Scala classes that have collections.
Related
I have a String with some arbitrary JSON in it. I want to construct a JsObject with my JSON string as a JSON object value, not a string value. For example, assuming my arbitrary string is a boring {} I want {"key": {}} and not {"key": "{}"}.
Here's how I'm trying to do it.
val myString = "{}"
Json.obj(
"key" -> Json.parse(myString)
)
The error I get is
type mismatch; found :
scala.collection.mutable.Buffer[scala.collection.immutable.Map[String,java.io.Serializable]]
required: play.api.libs.json.Json.JsValueWrapper
I'm not sure what to do about that.
"{}" is an empty object.
So, to get {"key": {}} :
Json.obj("key" -> Json.obj())
Update:
Perhaps you have an old version of Play. This works under Play 2.3.x:
scala> import play.api.libs.json._
scala> Json.obj("foo" -> Json.parse("{}"))
res2: play.api.libs.json.JsObject = {"foo":{}}
I have a case class with a json.Writes[T] defined on it.
If I have an Option[T], and with an implicit write in scope, I can call Json.toJson(Option[T]); and this works
However if I call Json.toJson(T)(json.Writes[T]) - I get a compile error
type mismatch;
found : play.api.libs.json.Writes[models.WorkflowTemplateMilestone]{def writes(o: models.WorkflowTemplateMilestone): play.api.libs.json.JsObject}
required: play.api.libs.json.Writes[Option[models.WorkflowTemplateMilestone]]
Note: implicit value workflowTemplateMilestoneAPIWrites is not applicable here because it comes after the application point and it lacks an explicit result type
Am I passing the Writer in incorrectly? How does it work between T and Option[T] with the implicit write?
The actual code is below; in case I've mis diagnosed the issue
Custom writer
implicit val workflowTemplateMilestoneAPIWrites = new Writes[WorkflowTemplateMilestone] {
def writes(o: WorkflowTemplateMilestone) = Json.obj(
"id" -> o.id,
"name" -> o.name,
"order" -> o.order
)
}
WORKS
implicit val workflowTemplateMilestoneAPIWrites = new Writes[List[(WorkflowTemplate, Option[WorkflowTemplateMilestone])]] {
def writes(l: List[(WorkflowTemplate, Option[WorkflowTemplateMilestone])]) = Json.obj(
"id" -> l.head._1.id,
"name" -> l.head._1.name,
"milestones" ->
l.map(o =>
Json.toJson(o._2) **// Writer is picked up through Implict this WORKS**
)
)
}
GIVES COMPILE ERROR
implicit val workflowTemplateMilestoneAPIWrites = new Writes[List[(WorkflowTemplate, Option[WorkflowTemplateMilestone])]] {
def writes(l: List[(WorkflowTemplate, Option[WorkflowTemplateMilestone])]) = Json.obj(
"id" -> l.head._1.id,
"name" -> l.head._1.name,
"milestones" ->
l.map(o =>
Json.toJson(o._2)**(WorkflowTemplateMilestone.workflowTemplateMilestoneAPIWrites)** // But if I explicitly pass in the Writer, I get the compile error
)
)
}
Thanks,
Brent
This question is based on the following example, which is an attempt to deserialize a case class Node[Bird] from JSON.
import play.api.libs.json._
import play.api.libs.functional.syntax._
object ValidationTest {
case class Node[T](_id: Int, thing: T)
case class Bird(name: String, color: String)
// ERROR -- No apply function found matching unapply parameters
implicit val birdNodeFormat = Json.format[Node[Bird]]
val birdNodesJson: JsValue = Json.arr(
Json.obj(
"_id" -> 0,
"thing" -> Json.obj(
"name" -> "Cardinal",
"color" -> "Red"
)
),
Json.obj(
"_id" -> 1,
"thing" -> Json.obj(
"name" -> "Bluejay",
"color" -> "Blue"
)
)
)
val birdNodesResult = birdNodesJson.validate[Seq[Node[Bird]]]
}
In the preceding example, Scala is unable to resolve the proper apply/unapply functions for Node[Bird] for the format macro.
// ERROR -- No apply function found matching unapply parameters
implicit val birdNodeFormat = Json.format[Node[Bird]]
However, there is no problem with using a non-generic case class such as BirdNode.
case class BirdNode(_id: Int, thing: Bird)
// WORKS
implicit val birdNodeFormat = Json.format[BirdNode]
...
// WORKS
val birdNodesResult = birdNodesJson.validate[Seq[BirdNode]]
What is the best way to serialize/deserialize something like a Node[Bird] to/from JSON in Play 2.2?
You might have to define the format for Node[T] without using the macro, but this works:
implicit val birdFormat: Format[Bird] = Json.format[Bird]
implicit def nodeFormat[T : Format]: Format[Node[T]] =
((__ \ "_id").format[Int] ~
(__ \ "thing").format[T]
)(Node.apply, unlift(Node.unapply))
I have json rest api application based on play framework and want to receive information about validation errors when I parse incoming requests. Everything is working fine except conversion from json array to json value.
Json structure I want to achieve:
{
"errors": {
"name": ["invalid", "tooshort"],
"email": ["invalid"]
}
}
When I tried to implement a sample it worked perfectly:
def error = Action {
BadRequest(obj(
"errors" -> obj(
"name" -> arr("invalid", "tooshort"),
"email" -> arr("invalid")
)
))
}
When I tried to extract the changing part like this:
def error = Action {
val e = Seq("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
// in real app it will be Seq[(JsPath, Seq[ValidationError])]
BadRequest(obj(
"errors" -> obj(e: _*)
))
}
I got a compiler error:
type mismatch; found : Seq[(String, play.api.libs.json.JsArray)] required: Seq[(String, play.api.libs.json.Json.JsValueWrapper)]
Maybe there is some implicit conversion I'm missing from JsArray to JsValueWrapper? But then, why does the sample works fine in the same file with the same imports?
Play 2.1.1, Scala 2.10.0
UPDATE:
Problem resolved thanks to Julien Lafont, the final code:
implicit val errorsWrites = new Writes[Seq[(JsPath, Seq[ValidationError])]] {
/**
* Maps validation result of Ember.js json request to json validation object, which Ember can understand and parse as DS.Model 'errors' field.
*
* #param errors errors collected after json validation
* #return json in following format:
*
* {
* "errors": {
* "error1": ["message1", "message2", ...],
* "error2": ["message3", "message4", ...],
* ...
* }
* }
*/
def writes(errors: Seq[(JsPath, Seq[ValidationError])]) = {
val mappedErrors = errors.map {
e =>
val fieldName = e._1.toString().split("/").last // take only last part of the path, which contains real field name
val messages = e._2.map(_.message)
fieldName -> messages
}
obj("errors" -> toJson(mappedErrors.toMap)) // Ember requires root "errors" object
}
}
Usage:
def create = Action(parse.json) { // method in play controller
request =>
fromJson(request.body) match {
case JsSuccess(pet, path) => Ok(obj("pet" -> Pets.create(pet)))
case JsError(errors) => UnprocessableEntity(toJson(errors)) // Ember.js expects 422 error code in case of errors
}
}
You can simply define your errors in a Map[String, Seq[String]], and transform it in Json with Json.toJson (there are built-in writers for Map[String,Y] and Seq[X])
scala> val e = Map("name" -> Seq("invalid", "tooshort"), "email" -> Seq("invalid"))
e: scala.collection.immutable.Map[String,Seq[String]] = Map(name -> List(invalid, tooshort), email -> List(invalid))
scala> Json.toJson(e)
res0: play.api.libs.json.JsValue = {"name":["invalid","tooshort"],"email":["invalid"]}
scala> Json.obj("errors" -> Json.toJson(e))
res1: play.api.libs.json.JsObject = {"errors":{"name":["invalid","tooshort"],"email":["invalid"]}}
The reason the long handed version works is because scala's automatic type inference triggers an implicit conversion toJsFieldJsValueWrapper.
For example
scala> import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json._
scala> import play.api.libs.functional.syntax._
import play.api.libs.functional.syntax._
scala> import Json._
import Json._
scala> arr("invalid", "tooshort")
res0: play.api.libs.json.JsArray = ["invalid","tooshort"]
scala> obj("name" -> arr("invalid", "tooshort"))
res1: play.api.libs.json.JsObject = {"name":["invalid","tooshort"]}
scala> obj _
res3: Seq[(String, play.api.libs.json.Json.JsValueWrapper)] => play.api.libs.json.JsObject = <function1>
Notice that arr returns a JsArray, however obj requires a JsValueWrapper. Scala is able to convert the JsArray to JsValueWrapper when it constructs the arguments for obj. However it is not able to convert a Seq[(String, JsArray)] to a `Seq[(String, JsValueWrapper)].
If you provide the expected type when of the sequence in advance, the Scala compiler's type inferencer will perform the conversion as it creates the sequence.
scala> Seq[(String, JsValueWrapper)]("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
res4: Seq[(String, play.api.libs.json.Json.JsValueWrapper)] = List((name,JsValueWrapperImpl(["invalid","tooshort"])), (email,JsValueWrapperImpl(["invalid"])))
However once the sequence is created it cannot be converted unless there is an implicit conversion in scope that can convert sequences.
The final code snippet looks like:
def error = Action {
val e = Seq[(String, JsValueWrapper)]("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
// in real app it will be Seq[(JsPath, Seq[ValidationError])]
BadRequest(obj(
"errors" -> obj(e: _*)
))
}
This is my code:
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.json.Json._
import play.api.libs.json.Writes._
class BaseController extends Controller with Secured with DefaultWrites {
private implicit def str2json(str: String) = new {
def asSuccessJson = toJson(Map("success" -> true, "message" -> str)) // (*)
def asFailedJson = toJson(Map("success" -> false, "message" -> str)) // (*)
}
}
But it can't be compiled on two (*) lines. The error message is:
Multiple markers at this line
- No Json deserializer found for type scala.collection.immutable.Map[java.lang.String,Any]. Try
to implement an implicit Writes or Format for this type.
- not enough arguments for method toJson: (implicit tjs:
play.api.libs.json.Writes[scala.collection.immutable.Map[java.lang.String,Any]])
play.api.libs.json.JsValue.Unspecified value parameter tjs.
I have to write it as:
def asSuccessJson = toJson(Map("success" -> true.toString, "message" -> str))
Notice true.toString. It works but boring.
How to fix it?
It's quite logical: you try to convert an heterogeneous map into a JsValue:
Map("success" -> true, "message" -> str) is a Map[String, Any].
There is no implicit writer able to convert a Map[String, Any] into a JsValue (and there can't be any).
When you write Map("success" -> true.toString, "message" -> str), you create a Map[String, String] and there is a writer for this.
I would write:
def asSuccessJson = JsObject(Seq("success" -> JsBoolean(true), "message" -> JsString(str))) // (*)
BTW, the JSON API will certainly be "beautified" a bit in Play 2's next releases...