Play Framework - add a field to JSON object
Asked Answered
T

3

42

I have a problem with adding a field to Json object in Play Framework using Scala:

I have a case class containing data. For example:

case class ClassA(a:Int,b:Int)

and I am able to create a Json object using Json Writes:

val classAObject = ClassA(1,2)
implicit val classAWrites= Json.writes[ClassA]
val jsonObject = Json.toJson(classAObject)

and the Json would look like:

{ a:1, b:2 }

Let's suppose I would like to add an additional 'c' field to the Json object. Result:

{ a:1, b:2, c:3 }

How do I do that without creating a new case class or creating my Json object myself using Json.obj? I am looking for something like:

jsonObject.merge({c:3}) 

Any help appreciated!

Translator answered 4/4, 2014 at 6:57 Comment(0)
C
49

JsObject has a + method that allows you to add fields to an object, but unfortunately your jsonObject is statically typed as a JsValue, not a JsObject. You can get around this in a couple of ways. The first is to use as:

 scala> jsonObject.as[JsObject] + ("c" -> Json.toJson(3))
 res0: play.api.libs.json.JsObject = {"a":1,"b":2,"c":3}

With as you're essentially downcasting—you're telling the compiler, "you only know that this is a JsValue, but believe me, it's also a JsObject". This is safe in this case, but it's not a good idea. A more principled approach is to use the OWrites directly:

scala> val jsonObject = classAWrites.writes(classAObject)
jsonObject: play.api.libs.json.JsObject = {"a":1,"b":2}

scala> jsonObject + ("c" -> Json.toJson(3))
res1: play.api.libs.json.JsObject = {"a":1,"b":2,"c":3}

Maybe someday the Json object will have a toJsonObject method that will require a OWrites instance and this overly explicit approach won't be necessary.

Chader answered 4/4, 2014 at 7:31 Comment(9)
Per signature, Writes.writes returns JsValue, so I don't understand how you can get rid of upcasting (not in the REPL).Foliose
Tvaroh is right, it return JsValue as far as I can seeBahadur
@Bahadur Try it out—classAWrites will be statically typed as OWrites[ClassA] (in both Play 2.2 and 2.3 and on 2.10 and 2.11). This is due to "underspecified but intended" behavior of Scala's macros (see my question here for details).Chader
Skipping the definition for the class and writer for brevity, but this is what the repl gives me: scala> SessionCreatorWriter.writes(s) res2: play.api.libs.json.JsValueBahadur
@Bahadur If you define a Writes instance you'll get JsValue, but if you define an OWrites instance—either manually or using the Json.writes macro, which is what the OP is doing—you'll get a JsObject.Chader
@TravisBrown maybe I should've posted it then: implicit val SessionCreatorWriter = Json.writes[SessionCreator] val s = SessionCreator("User1",None,None,None,1,None,None) SessionCreatorWriter.writes(s) and I get a JsValueBahadur
There's actually no need for the paranthesis. This works: jsobj + "key" -> entity.keyCercaria
@Cercaria That does something completely different. + and - have the same precedence, so the parentheses are definitely necessary.Chader
Why is Json.toJson(3) needed in this scenario. Even though it's not needed when doing Json.obj("a" -> 3) ??Roble
T
8

I found a solution myself. In fact the JsValue, which is the return type of Json.toJson has no such method, but the JsObject (http://www.playframework.com/documentation/2.2.x/api/scala/index.html#play.api.libs.json.JsObject) does, so the solution is:

val jsonObject = Json.toJson(classAObject).as[JsObject]
jsonObject + ("c", JsNumber(3)) 

I hope someone will find this useful :)

Translator answered 4/4, 2014 at 7:29 Comment(0)
D
-1

simpler way is to use argoanut (http://argonaut.io/)

var jField : Json.JsonField = "myfield" //Json.JsonField is of type String
obj1.asJson.->:(jField, obj2.asJson)  // adds a field to obj1.asJson

here obj1.asJson creates a JSON object and obj2 is the object to be added to the json created by obj1.asJson

Duffer answered 23/5, 2015 at 8:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.