jsonata-js / jsonata

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

Feature Request: Allow any expression in object transform operator (update) or a set mode #273

Open admosity opened 6 years ago

admosity commented 6 years ago

Currently the object transform operator only supports expressions that resolve to arrays or objects for the update. Would it be possible to loosen this constraint and allow any type. For example:

$ ~> | Account."Account Name" | "Fireflies" |

Or possibly add flags to the syntax? This would be similar to how regexes handle specific modes and handling. Like a set mode:

head ~> | location | update [, delete][ | flags]

For example (s for set):

$ ~> | Account."Account Name" | "Fireflies" | s

A bit of a contrived example. This will change "Account Name" to have the contents { "firstName": "John", "lastName": "Smith"}:

$ ~> | Account."Account Name" | { "firstName": "John", "lastName": "Smith"} | s

Currently the way to do the examples above is:

$ ~> | Account | { "Account Name": { "firstName": "John", "lastName": "Smith"}} |
$ ~> | Account | { "Account Name": "Fireflies"} |

Motivations Our main motivation is that the update part needs to know a bit about the location. We're currently trying to orchestrate setting parts of an object/array through a UI and it is non trivial to determine the write location or possibly the data shape of the location at times.

Here's one possible example of that: http://try.jsonata.org/BkZ_sRepm

Input data:

[
     [100,200,100],
    [50,50,50],
    [255,255,255]
]

Jsonata:

(
  $toRgb := function($tuple){'rgb(' & $tuple[0] & ',' & $tuple[1] & ',' & $tuple[2] & ')'};
  $ ~> | $[] | $toRgb($) |
)

Ideal result:

[
  "rgb(100, 200, 100)",
  "rgb(50, 50, 50)",
  "rgb(255, 255, 255)"
]

Related conversation that has similar syntax: #70

andrew-coleman commented 5 years ago

Hi @admosity, I'm wondering what your objection is to using the supported $ ~> | Account | { "Account Name": "Fireflies"} | syntax rather than your desired $ ~> | Account."Account Name" | "Fireflies" | s, since they would be equivalent. Even if you are generating these expressions from a UI tool, couldn't the first expression be generated? I'm happy to look at this if you can give some more context.

In your last example, the following expression will generate the desired output:

(
  $toRgb := function($tuple){'rgb(' & $tuple[0] & ',' & $tuple[1] & ',' & $tuple[2] & ')'};
  $.$toRgb($)
)
admosity commented 5 years ago

Hi @andrew-coleman, thanks for getting back to me. It appears that I lack a certain understanding on JSONata to understand how that second result could be achieved, but really cool stuff that it works.

So much of the issue has to deal with the state of the JSON is unknown at the time the query is executed.

Here's an example of the situation:

Given input JSON:

{}

JSONata query:

$ ~> | Account | { "Account Name": { "firstName": "John", "lastName": "Smith"}} |

Something to produce:

{
  "Account": {
    "Account Name": {
      "firstName": "John",
      "lastName": "Smith"
    }
  }
}

Actual observed result of running that query is:

This would be similar to perl's autovivification functionality.

Right now we are getting by with lodash's set to achieve some similar functionality, but we lose the flexibility of JSONata and having a single query/functions language in what we are trying to achieve.

markmelville commented 4 years ago

Since "Account" may or may not exist on the input, the transformation must begin at the root, adding it if missing but preserving it if present, hence the $merge:

$ ~> | $ | { 'Account': $merge([Account]) } |
  ~> | Account | { "Account Name": { "firstName": "John", "lastName": "Smith"}} |
andrew-coleman commented 4 years ago

Thanks Mark. @admosity does this achieve what you need? If so, please feel free to close this issue.

markmelville commented 3 years ago

I think the "allow any type" part of this request (not the flags syntax) would be a great language feature. Someone asked on Slack how to do this: https://try.jsonata.org/bFvBNyXr4. I suggested:

data ~> $each(function($v,$k){{$k:$v.value}}) ~> $merge()

I need to do this sort of transformation ALL the time: iterate entries of an object to change the values, with the the result still an object. And my expression always ends up being this $each~>$merge type of expression. If you need access to the object key then $each is the only way. But in this case, we don't care, so imagine if we could use the transform operator but specify a scalar rather than an object:

data~>|*|value|
markmelville commented 3 years ago

I started to work on a PR for this, as I've been wanting to contribute something to Jsonata for a while. I now see why it has to be an object. Many fundamental things about the code would have to change to allow for the value to be set rather than updated by reference.