[2.2] Coast-to-coast JSON: pickBranch with array of certain Reads?

134 views
Skip to first unread message

Jari Pennanen

unread,
Oct 13, 2013, 1:53:11 PM10/13/13
to play-fr...@googlegroups.com
I have following code, note that "r" and "?" are just shorthands for pickBranch so the JSON code is more readable:
  
  val emptyObj = __.json.put(Json.obj())
  def ?[A <: JsValue](jspath: JsPath)(r : Reads[A]) = (jspath.json.pickBranch(r) or emptyObj)
  def r[A <: JsValue](jspath: JsPath)(r : Reads[A]) = jspath.json.pickBranch(r)
  
  val invoiceRowJson = (
    r(__ \ 'title)(Reads.of[JsString]) and
    ?(__ \ 'quantity)(Reads.of[JsNumber]) and
    ?(__ \ 'quantityUnit)(Reads.of[JsString]) and
    ?(__ \ 'price)(Reads.of[JsNumber]) and
    ?(__ \ 'tax)(Reads.of[JsNumber]) and
    ?(__ \ 'taxPrice)(Reads.of[JsNumber]) and
    ?(__ \ 'totalTaxPrice)(Reads.of[JsNumber]) and
    ?(__ \ 'totalPrice)(Reads.of[JsNumber])
  ).reduce
  
  val invoiceJson = (
    r(__ \ 'title)(Reads.of[JsString]) and
    ?(__ \ 'client)(Reads.of[JsString]) and
    ?(__ \ 'invoiceRows)(Reads.arr(invoiceRowJson)) // PSEUDO CODE "Reads.arr"
  ).reduce

See the PSEUDO code line (I've made up the Reads.arr). The problem: how can I do the reading of array of JsObjects of certain Reads?

Thanks.

- Jari

Jari Pennanen

unread,
Oct 13, 2013, 2:21:28 PM10/13/13
to play-fr...@googlegroups.com
Sorry, been trying to find a solution for few hours but as soon as I posted this I figured it out:

  ?(__ \ 'invoiceRows)(Reads.of[JsArray] keepAnd Reads.list(invoiceRowJson))

magical keepAnd was the answer.

Pascal Voitot Dev

unread,
Oct 13, 2013, 3:40:29 PM10/13/13
to play-fr...@googlegroups.com
Can you try this?


val invoiceJson = (
    r(__ \ 'title)(Reads.of[JsString]) and
    ?(__ \ 'client)(Reads.of[JsString]) and
    ?(__ \ 'invoiceRows)(Reads.list(invoiceRowJson).map(JsArray(_)))
  ).reduce

Regards
Pascal
 
Thanks.

- Jari

--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Jari Pennanen

unread,
Oct 13, 2013, 4:01:58 PM10/13/13
to play-fr...@googlegroups.com

On Sunday, October 13, 2013 10:40:29 PM UTC+3, Pascal wrote:
Can you try this?

Hey, that works too. Thanks. 

Pascal, Do you know if it's possible to validate e.g. foreign key (which requires Future handling) in same swoop?

E.g. 

  val invoiceJson = (
    r(__ \ 'title)(of[JsString]) and
    ?(__ \ 'client)(of[JsString] keepAnd foreignkeyInCollection("client"))
  )

I can come up foreignkeyInCollection function that returns Future but I can't seem to synchronize it:

  def foreignkeyInCollection(col: String)(implicit reads: Reads[String]) =
     Reads[String](js => reads.reads(js).flatMap { id => {
      val f = db.collection[JSONCollection](col).find(Json.obj("_id" -> id))
      .cursor[JsObject].headOption.map(c => c match {
        case Some(o) =>
          o.transform(fromObjectId).map {
            js =>
              JsSuccess(js)
          } recoverTotal (e => JsError("error.foreignkey"))
        case _ => JsError("error.foreignkey")
      })
      
      // wait for F's result here somehow?
      f
     }
    }

Pascal Voitot Dev

unread,
Oct 13, 2013, 4:44:01 PM10/13/13
to play-fr...@googlegroups.com
Actually, you must deal with Reads[Future[JsValue]] and manage it yourself! Json transformers don't work with it!

Maybe you should do it in 2 steps... first extract the client and keep the full object... then get your data from DB and use the Future[T] to build a new Reads[Future[...]]

val invoiceJson: Reads[Future[JsValue]] = (
  (__ \ "client").read[String] and
  __.read[JsObject](yourReader)
).tupled.flatMap{ case (client, obj) =>
  foreignkeyInCollection(client).map{
    case Some(o) => Reads{ ... }
    case _ => Reads{ ... }
  }
}

If you see what I mean from these fragments :)

pascal

Jari Pennanen

unread,
Oct 16, 2013, 5:21:58 PM10/16/13
to play-fr...@googlegroups.com
Pascal! I got it working.

However, I know this maybe blocking the request. But does it matter if perform this only inside the Action.async?

  def foreignkeyInCollection(col: String)(implicit reads: Reads[String]) =
    Reads[JsValue](js => reads.reads(js).flatMap { id =>

      val f = db.collection[JSONCollection](col).find(Json.obj("_id" -> Json.obj("$oid" -> id)))
        .cursor[JsObject].headOption.map(c => c match {
          case Some(o) =>
            o.transform(fromObjectId).map {
              js =>
                JsSuccess(js)
            } recoverTotal (e => JsError("error.foreignkey"))
          case _ => JsError("error.foreignkey")
        })

      Await.result(f, 100 seconds)
    })

This is really amazing, the syntax for JSON developer is super clean: 

  val invoiceJson = (
    r(__ \ 'title)(of[JsString]) and
    ?(__ \ 'client)(foreignkeyInCollection("client")) and
    ?(__ \ 'address)(Client.addressJson) and
    ?(__ \ 'invoiceRows)(list(invoiceRowJson).map(JsArray(_)))).reduce

Reply all
Reply to author
Forward
0 new messages