40

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!

Paweł Kozikowski
  • 974
  • 1
  • 10
  • 21

3 Answers3

50

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.

Travis Brown
  • 135,682
  • 12
  • 352
  • 654
  • Per signature, `Writes.writes` returns `JsValue`, so I don't understand how you can get rid of upcasting (not in the REPL). – Tvaroh Dec 16 '14 at 13:26
  • Tvaroh is right, it return JsValue as far as I can see – elmalto Jan 27 '15 at 23:16
  • 2
    @elmalto 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](http://stackoverflow.com/q/13669974/334519) for details). – Travis Brown Jan 28 '15 at 01:18
  • 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.JsValue` – elmalto Jan 28 '15 at 01:38
  • 1
    @elmalto 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`. – Travis Brown Jan 28 '15 at 01:40
  • @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 JsValue – elmalto Jan 28 '15 at 01:53
  • There's actually no need for the paranthesis. This works: `jsobj + "key" -> entity.key` – Wrench May 19 '15 at 19:25
  • @Wrench That does something completely different. `+` and `-` have the same precedence, so the parentheses are definitely necessary. – Travis Brown May 19 '15 at 21:00
  • Why is `Json.toJson(3)` needed in this scenario. Even though it's not needed when doing `Json.obj("a" -> 3)` ?? – Basil Aug 04 '20 at 20:53
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 :)

Paweł Kozikowski
  • 974
  • 1
  • 10
  • 21
-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

Alex Filatov
  • 2,084
  • 3
  • 30
  • 32