jsonata-js / jsonata

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

variable binding in transform #496

Open bulba72 opened 3 years ago

bulba72 commented 3 years ago

Hi,

First of all, congratulations and thanks for the fantastic idea of jsonata. I have a question: is variable binding supposed to work in a transform ?

See https://try.jsonata.org/0psVAAWyx - it's a nested structure of arrays - { "cities": [ { "city": "London", "streets": [ { "street": "Main", "buildings": [ { "number": 3, "floors": [ { "floor": "first", "apartments": [ { "apartment": 3 } ] } ] } ] } ] } ] } and I'm trying to set an address in every innermost object - apartment.

Applying a regular transform with explicit location $ ~> |cities.streets.buildings.floors.apartments | { ... } |

works fine, but I cannot seem to be able to use variable binding. The location isn't matching once I add @$variable at the end of any level :

$ ~> |cities@$c.streets@$s.buildings@$b.floors@$f.apartments@$a| { "mark" : 1 }|

Best regards, Marek

markmelville commented 3 years ago

The docs for context variable binding state that "variable binding remains in scope for the remainder of the path expression". So, no, the second expression of the transform does not have those variables in scope.

Sounds like you're using the transform because you want to keep the original structure. The only way I could come up with is not pretty. It uses a series of transformations that propagate the values from each level up until they are all found on the apartment object, where the address can then be built. https://try.jsonata.org/VcR_k1Sd7

$ ~> |cities|{"streets":($a:={'city':city};streets~>|$|$a|)}|
  ~> |cities.streets|{"buildings":($a:={'street':street,'city':city};buildings~>|$|$a|)},['city']|
  ~> |cities.streets.buildings|{"floors":($a:={'number':number,'street':street,'city':city};floors~>|$|$a|)},['city','street']|
  ~> |cities.streets.buildings.floors|{"apartments":($a:={'floor':floor,'number':number,'street':street,'city':city};apartments~>|$|$a|)},['city','street','number']|
  ~> |cities.streets.buildings.floors.apartments|{"address":city & street & number & floor & apartment},['city','street','number','floor']|    

If you perhaps don't need to keep the original structure (say you can make do with a final list of addresses) then any path operators can be used, not just @ but % as well. https://try.jsonata.org/6Xg4td2bk

cities.streets.buildings.floors.apartments.(%.%.number & ' ' & %.%.%.street & ' #' & (%.floor)*10 & apartment & ', ' & %.%.%.%.city)

The reason I used a parent operator expression rather than the expression you originally posted is because I couldn't get your version to work. cities@$c.streets@$s.buildings@$b.floors@$f.apartments@$a doesn't yield any result because it's not using the forwarded context correctly. An expression that does use it correctly like cities@$c.cities.streets@$s.streets.buildings@$b.buildings.floors@$f.floors.apartments@$a doesn't work either because there is something the @ operator does, described in the docs as a "join". You can't see it when there is just one city as in your example, but in https://try.jsonata.org/2-osDJo-h I've added more data and you can see what happens. It does an outer join of all cities with all apartments, resulting in too many combinations and therefore non-existent addresses.

markmelville commented 3 years ago

This could be a better way do it while keeping the structure. It uses nested transformations rather than a series. It captures the value of each level to a variable. https://try.jsonata.org/QgGnNQtLW

$ ~> |cities|{"streets":(
        $city:=city;
        streets~>|$|{"buildings":(
            $street:=street;
            buildings~>|$|{"floors":(
                $building:=number;
                floors~>|$|{"apartments":(
                    $floor:=floor;
                    apartments~>|$|{'address':$building & ' ' & $street & ' #' & ($floor)*10 & apartment & ', ' & $city}|
                )}|
            )}|
        )}|
    )}|