jsonata-js / jsonata

JSONata query and transformation language - http://jsonata.org
MIT License
2.06k stars 220 forks source link

Closure function in block expression #364

Closed Nenzyz closed 5 years ago

Nenzyz commented 5 years ago

I'm trying to reuse closure function in multiple places. It looks like 'root' is not available inside closure function.

(
  $in := function() {$exists(name)};

  $exists($$._user_)
    and
  $$._user_.($in())
)

Data

{
  "_user_": {
    "name": "Username"
  }
}

The result of the evaluation is false, but when I rewrite like the expression below, it is true as expected.

$exists($$._user_) and $$._user_.($exists(name))

andrew-coleman commented 5 years ago

Closures enclose the environment that is in-scope at the point at which the closure is defined (lexical scoping). This enclosed environment also includes the context ($) and root ($$) bindings. See https://docs.jsonata.org/programming#functions-are-closures. Since your function is defined at the top level, the context is the root of the input json, so name will be looking for a top level property. The following code will return true:

(
  $in := function() {$exists(_user_.name)};

  $exists($$._user_)
    and
  $$._user_.($in())
)

If you want the function to use the context from where it is invoked, then it needs to passed in as an argument. You can even specify the function signature to substitute the first argument with the context value as shown below:

(
  $in := function($arg)<x-:b> {$exists($arg.name)};

  $exists($$._user_)
    and
  $$._user_.($in())
)

See https://docs.jsonata.org/programming#function-signatures

Nenzyz commented 5 years ago

Thanks, this explains a lot.

Hope it will be helpful for other people to know.