json-lenses
json-lenses copied to clipboard
set doesn't work out of the box for nested json that doesn't exist
When trying to write a nested js:
{"a" : { "b" : "c"}}
using a single lens
val ab = 'a / 'b
JsObject.update(ab ! set("c"))
That fails. So far I solved this with a rather poor solution, another combining operator:
trait EskoLenses extends ScalarLenses with OptionLenses with SeqLenses with Operations with JsonPathIntegration with ExtraImplicits {
def writeIfAbscentCombine[M[_], M2[_], R[_]](outer: Lens[M], inner: Lens[M2])(implicit ev: Join[M2, M, R]): Lens[R] =
new LensImpl[R]()(ev.get(inner.ops, outer.ops)) {
def retr: JsValue => Validated[R[JsValue]] = parent =>
for {
outerV <- outer.retr(parent)
innerV <- ops.allRight(outer.ops.flatMap(outerV)(x => inner.ops.toSeq(inner.retr(x))))
} yield innerV
def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
outer.updated {
case l: Left[_, _] =>
inner.updated(f)(JsObject())
case r @ Right(js) =>
inner.updated(f)(js)
}(parent)
}
}
object EskoLenses extends EskoLenses {
implicit class WriteIfAbscenCombinator(outer: ScalarLens) {
/**
* When using this operator, the resulting lens join will not fail when trying to to do inner level writes, it will instead initialize an empty object
*
* @param inner
* @return
*/
def /?(inner: ScalarLens): ScalarLens = {
EskoLenses.writeIfAbscentCombine(outer, inner)
}
}
}
I'm sure better solutions could be thought of. E.g. a combining lens could be a class which can be matched against and from which the inner and outer can be recovered. That way the need for parent node creation could be detected and executed before attempting to write the nested json.