Dynamic JSON Creation in Play - json

I'm trying to dynamically create a JSON object in the following way. Note that the below code will not compile as I'm requesting your help on how to code it.
val favoriteFoods: JsArray = getArray() // gets Array of Array of JSON objects
val json: JsObject = Json.obj(
"name" : JsString("Kevin"),
"FavoriteFood1" -> favoriteFoods.get(0), // note that I made up the get()
"FavoriteFood2" -> favoriteFoods.get(1)
)
Looking at the JsArray docs, I didn't see any way to get the i'th element of a JsArray.
I tried to add an if statement to check if a new FavoriteFood could be added, but it would not compile.

You didn't specify the version of Play Framework you're using. I'm familiar with 1.X, so this answer is from that perspective. Play provides a renderJSON() method. Here's the docs:
http://www.playframework.com/documentation/1.2.4/controllers
Scroll down to the section that reads 'Return a JSON String'

Related

Not able to convert Scala object to JSON String

I am using Play Framework and I am trying to convert a Scala object to a JSON string.
Here is my code where I get my object:
val profile: Future[List[Profile]] = profiledao.getprofile(profileId);
The object is now in the profile value.
Now I want to convert that profile object which is a Future[List[Profile]] to JSON data and then convert that data into a JSON string then write into a file.
Here is the code that I wrote so far:
val jsondata = Json.toJson(profile)
Jackson.toJsonString(jsondata)
This is how I am trying to convert into JSON data but it is giving me the following output:
{"empty":false,"traversableAgain":true}
I am using the Jackson library to do the conversion.
Can someone help me with this ?
Why bother with Jackson? If you're using Play, you have play-json available to you, which uses Jackson under the hood FWIW:
First, you need an implicit Reads to let play-json know how to serialize Profile. If Profile is a case class, you can do this:
import play.api.libs.json._
implicit val profileFormat = Json.format[Profile]
If not, define your own Reads like this.
Then since getprofile (which should follow convention and be getProfile) returns Future[List[Profile]], you can do this to get a JsValue:
val profilesJson = profiledao.getprofile(profileId).map(toJson)
(profiledao should also be profileDao.)
In the end, you can wrap this in a Result like Ok and return that from your controller.

Converting "recursive" object to JSON (Play Framework 2.4 with Scala)

I have reached a point where my code compiles successfully, yet I have doubts about my solution and am posting this question for that reason.
I have a Node class defined as:
case class Node(id: Long, label: String, parent_id: Option[Long])
The reason I quote/unquote recursive is because technically, I do not store a Node within a Node. Rather, each node has a pointer to its parent and I can say: give me all children of Node id=X.
Here is an example tree, for the sake of visualization. I would like to give the root_node's ID, and obtain the tree's conversion to a Json String:
root_node
|_ node_1
| |_ node_11
| |_ node_111
|_ node_2
|_ node_3
The Json would look like:
{"title": "root_node", "children": [...]}
with the children array containing node_1, 2 and 3 etc... recursively
Here is the Writes Converter for Node:
/** json converter of Node to JSON */
implicit val NodeWrites = new Writes[Node] {
def writes(node: Node) = Json.obj(
"title" -> node.label,
"children" -> Node.getChildrenOf(node.id)
)
}
Quoting Play docs:
The Play JSON API provides implicit Writes for most basic types, such
as Int, Double, String, and Boolean. It also supports Writes for
collections of any type T that a Writes[T] exists.
I need to point out that Node.getChildrenOf(node.id) returns a List of Nodes from the DB. So according to Play's docs, I should be able to convert a List[Node] to Json. It seems that doing this within the Writes converter itself is a bit more troublesome.
Here is the resulting error from running this code:
type mismatch;
found : List[models.Node]
required: play.api.libs.json.Json.JsValueWrapper
Note: implicit value NodeWrites is not applicable here because it comes after the application point and it lacks an explicit result type
I added the "explicit result type" to my Writes converter, here is the result:
/** json converter of Node to JSON */
implicit val NodeWrites: Writes[Node] = new Writes[Node] {
def writes(node: Node) = Json.obj(
"title" -> node.label,
"children" -> Node.getChildrenOf(node.id)
)
}
The code now executes properly, I can visualize the tree on the browser.
Even though this looks to me like the cleanest working solution, IntelliJ still complains about the line:
"children" -> Node.getChildrenOf(node.id)
saying:
Type mismatch: found(String, List[Node]), required (String, Json.JsValueWrapper)
Could it be that IntelliJ's error reporting is not based off of the Scala compiler?
Finally, is the overall approach of the JSON converter terrible?
Thanks and sorry for the long post.
The problem lies in "children" -> Node.getChildrenOf(node.id). Node.getChildrenOf(node.id) returns a List[Node]. Whereas any attribute in the Json.obj expects JsValueWrappers. In this case a JsArray.
Something like this should work:
implicit val writes = new Writes[Node] {
def writes(node: Node) = Json.obj(
"title" -> node.label,
// Note that we have to pass this since the writes hasn't been defined just yet.
"children" -> JsArray(Node.getChildrenOf(node).map(child => Json.toJson(child)(this)))
)
}
This at least compiles, I haven't tested it with any data though.

How to test if the case classes I have created for the parser are correct using json4s libraries in scala?

I have a huge json object and I need to parse it and then write some tests to see if everything goes as expected.
case class User(id: Identification, age:Int, name: String ...)
case class Identification(id: Int, hash: String ....)
... a lot more classes
Now I'm trying to write the tests
val json = parse(Source.fromFile(/path).getLines.mkString("\n"))
import org.json4s.DefaultFormats
implicit val formats = DefaultFormats
So my question is how can i test if the case classes are ok?
I thought maybe I should try to extract for ex. the users and then to check parameter by parameter if they are correct, but I don't thing that is a good way because it is not me who created the json so I'm not interested about the content.
Thanks
This is what I found working with JSON and case classes overt the years the minimum to test.
This three things should be tested always
Serialization with deserialiaztion combined
val example = MyCaseClass()
read[MyCaseClass](write(example)) should Equal example
Checks if a class can be transformed to JSON, read back and still has the same values. This one breaks more often than one would think.
Deserialization: JSON String -> CaseClasses
val exampleAsJSON : String
val exampleAsCaseClass : MyCaseClass
read(exampleAsJSON) shouldEqual exampleAsCaseClass
Checks if JSON still can be deserialized.
Serialization: CaseClasses -> JSON String
val exampleAsJSON : String
val exampleAsCaseClass : MyCaseClass
write(exampleAsCaseClass) shouldEqual exampleAsJSON
Checks if String/ JSON Representation stays stable. Here it is hard to keep the data up to date and often some not nice whitespace changes lead to false alarms.
Additional things to test
Are there optional parameters present? If yes all tests should be done with and without the optional parameters.

Scala Convert a string into a map

What is the fastest way to convert this
{"a":"ab","b":"cd","c":"cd","d":"de","e":"ef","f":"fg"}
into mutable map in scala ? I read this input string from ~500MB file. That is the reason I'm concerned about speed.
If your JSON is as simple as in your example, i.e. a sequence of key/value pairs, where each value is a string. You can do in plain Scala :
myString.substring(1, myString.length - 1)
.split(",")
.map(_.split(":"))
.map { case Array(k, v) => (k.substring(1, k.length-1), v.substring(1, v.length-1))}
.toMap
That looks like a JSON file, as Andrey says. You should consider this answer. It gives some example Scala code. Also, this answer gives some different JSON libraries and their relative merits.
The fastest way to read tree data structures in XML or JSON is by applying streaming API: Jackson Streaming API To Read And Write JSON.
Streaming would split your input into tokens like 'beginning of an object' or 'beginning of an array' and you would need to build a parser for these token, which in some cases is not a trivial task.
Keeping it simple. If reading a json string from file and converting to scala map
import spray.json._
import DefaultJsonProtocol._
val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]
// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]
// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)

Inserting JsNumber into Mongo

When trying to insert a MongoDBObject that contains a JsNumber
val obj: DBObject = getDbObj // contains a "JsNumber()"
collection.insert(obj)
the following error occurs:
[error] play - Cannot invoke the action, eventually got an error: java.lang.IllegalArgumentException: can't serialize class scala.math.BigDecimal
I tried to replace the JsNumber with an Int, but I got the same error.
EDIT
Error can be reproduced via this test code. Full code in scalatest (https://gist.github.com/kman007us/6617735)
val collection = MongoConnection()("test")("test")
val obj: JsValue = Json.obj("age" -> JsNumber(100))
val q = MongoDBObject("name" -> obj)
collection.insert(q)
There are no registered handlers for Plays JSON implementation - you could add handlers to automatically translate plays Js Types to BSON types. However, that wont handle mongodb extended json which has a special structure dealing with non native json types eg: date and objectid translations.
An example of using this is:
import com.mongodb.util.JSON
val obj: JsValue = Json.obj("age" -> JsNumber(100))
val doc: DBObject = JSON.parse(obj.toString).asInstanceOf[DBObject]
For an example of a bson transformer see the joda time transformer.
It seems that casbah driver isn't compatible with Plays's JSON implementation. If I look through the cashbah code than it seems that you must use a set of MongoDBObject objects to build your query. The following snippet should work.
val collection = MongoConnection()("test")("test")
val obj = MongoDBObject("age" -> 100)
val q = MongoDBObject("name" -> obj)
collection.insert(q)
If you need the compatibility with Play's JSON implementation then use ReactiveMongo and Play-ReactiveMongo.
Edit
Maybe this Gist can help to convert JsValue objects into MongoDBObject objects.