json-lenses icon indicating copy to clipboard operation
json-lenses copied to clipboard

set doesn't work out of the box for nested json that doesn't exist

Open spangaer opened this issue 8 years ago • 0 comments

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.

spangaer avatar Aug 08 '16 13:08 spangaer