How to merge a JsValue to JsObject in flat level

18,400

Solution 1

Play has a lot of new features for JSON right now. This would be a nice showcase for the Format[A] trait (see Scala Json Inception) which you could include implicitly as I will show, or explicitly to the methods that require an implicit Format[A]/Reads[A]/Writes[A].

Create a case class to represent your JSON objects,

case class Book(id: Int, name: String)
case class BookDetail(id: Int, author: String, publicationDate: Int, pages: Int)

Create companion objects that contain the implicit Format[A] so that Format/Reads/Writes will automatically be in scope when you need them.

object Book { 
  implicit val fmt: Format[Book] = Json.format[Book] 
}

object BookDetail { 
  implicit val fmt: Format[BookDetail] = Json.format[BookDetail] 
}

Now you could do something like this,

val bookJson = Json.toJson(Book(1, "A Brief History Of Time"))
val bookDetailJson = Json.toJson(BookDetail(1, "Steven Hawking", 1988, 256))
bookJson.as[JsObject].deepMerge(bookDetailJson.as[JsObject])

And you will have an object like this,

{
  id: 1,
  name: "A Brief History Of Time",
  author: "Steven Hawking",
  publicationDate: 1988,
  pages: 256
}

I've tried this in the REPL but it does not work, in a Play application it does just fine though. Also in a production scenario we would likely use asOpt[T] in place of as[T].

Here is an example of why asOpt[T] may be better suited, suppose instead of a valid JSON object for book you get,

val bookJson = Json.toJson("not a book")

You will end up with a

[JsResultException: JsResultException(errors:List((,List(ValidationError(validate.error.expected.jsobject,WrappedArray())))))]

But suppose instead you change your method to use asOpt[T],

bookJson.asOpt[JsObject].getOrElse(Json.obj()).deepMerge(bookDetailJson.asOpt[JsObject].getOrElse(Json.obj()))

Now you will end up with at least a partial JSON object,

{
  id: 1,
  author: "Steven Hawking",
  publicationDate: 1988,
  pages: 256
}

So depending on how you would like to handle improperly formatted JSON you could choose either option.

Solution 2

JsObject is subtype of JsValue.

JsValue can be simple converted to the JsObject using as or asOpt methods from JsValue. Example:

val someJsValue = ....
val asObject:JsObject = someJsValue.as[JsObject]
val asObjectMaybe:Option[JsObject] = v.asOpt[JsObject]

In the case of JsArray you can not use above code. If you use play and parse JSON with array, then Json.toJson(...) produces JsValue which is JsArray actually. You need to convert JsArray as following:

val someJsValueButArray = ....
val asJsArray:JsArray = Json.toJson(someJsValueButArray).as[JsArray]
val asSeqOfJsObjects:Seq[JsObject] = asJsArray.value.map(_.as[JsObject])
Share:
18,400

Related videos on Youtube

Joyfulvillage
Author by

Joyfulvillage

Updated on June 04, 2022

Comments

  • Joyfulvillage
    Joyfulvillage almost 2 years

    I have two JsValue created from case class, i.e. Book and Book detail

    val bookJson = Json.tojson(Book)
    val bookDetailJson = Json.tojson(BookDetail)
    

    and the format would be:

    //Book
    {
      id: 1,
      name: "A Brief History of Time"
    }
    
    //BookDetail
    {
      bookId: 1,
      author: "Steven Hawking",
      publicationDate: 1988,
      pages: 256
    }
    

    How can I merge them to a single Json in play-framework 2.10? i.e.

    //Book with detail
    {
      id: 1,
      name: "A Brief History of Time",
      bookId: 1,
      author: "Steven Hawking",
      publicationDate: 1988,
      pages: 256
    }
    

    I was trying the transformation and failed to iterate through the second JsValue:

    val mapDetail = (__).json.update(
                      __.read[JsObject].map { o =>
                      o.deepMerge( JsObject(Seq(("detail", bookDetailJson))) )
                    })
    
    bookJson.validate(mapDetail).get
    

    It would become one level down, which I don't really want.

    //Book with detail
    {
      id: 1,
      name: "A Brief History of Time",
      detail: {
                bookId: 1,
                author: "Steven Hawking",
                publicationDate: 1988,
                pages: 256
              }
    }
    

    Please let me know if any trick could provide on this Json transform. Many Thanks!

  • toidiu
    toidiu over 8 years
    +1 Thanks soo much deepMerge was exactly what I was looking for but the Scala api is so big that its easy to overlook!