Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge a JsValue to JsObject in flat level

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!

like image 317
Joyfulvillage Avatar asked Jul 11 '13 14:07

Joyfulvillage


1 Answers

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.

like image 89
tysonjh Avatar answered Sep 22 '22 09:09

tysonjh