Fantastic question; I'd been meaning to learn about Play's JSON transformers for ages, and finally a great situation to try them out.
I should note that I was completely unable to get this to work correctly until I added the all-important functional.syntax._
import:
import play.api.libs.functional.syntax._
The solution started out from the Gizmo -> Gremlin transformer example on the transformers page; that's where I got the usage of the and
and reduce
, which completely transforms (pardon the pun) how the whole thing works.
So here it is:
private def toVersioned(deleted: Boolean) = (__.json.update(
( __ \ '_id ).json.copyFrom((__ \ '_id \ '$oid).json.pickBranch) and
( __ \ '_id \ '_version).json.copyFrom((__ \ '_version).json.pick) and
(__ \ '_version).json.update(
of[JsValue].map { case JsNumber(oldVersion) =>
if (deleted) JsString(s"deleted:$oldVersion") else JsNumber(oldVersion)
}
) and
(__ \ '_timestamp).json.put(Json.toJson(LocalDateTime.now)) reduce
)
andThen
( __ \ '_id \ '$oid ).json.prune
)
Key points:
I couldn't (within a reasonable amount of time) achieve a "move" of the
_id
node in the one transform, hence the first pass copies it "down" one level, and the second pass (after theandThen
) deletes the old$oid
. There's almost-certainly a way...The use of
and
seems to change the scope of thecopyFrom
, allowing the_version
to be picked out of the top-level correctly - when usingandThen
it seemed to only work if descending into nodes "below" the targetI'm quite happy with the handling of
deleted
here - themap
seems both Scala- and PlayJSON-idiomatic. TheOf[JsValue]
is necessary because we return either aJsString
or aJsNumber
here, andJsValue
seemed like a suitable superclass