Difference between properties and accessors in Nashorn Javascript Engine

Summary: in the JavaScript snaps, what is the difference between doc.get(“something”) and doc[“something”] ??

Details and Background:

I am very well-versed in C++, SQL, and a smattering of other languages, and have been developing for a long time. But I am only a pedestrian Javascript programmer at best.

We have a lot of Javascript snaps that were written by an offshore team. I regularly see stuff that seems confused or random to me, for example:

execute: function() {
    this.log.info("Executing Transform Script");
    while (this.input.hasNext()) {
        var doc = this.input.next();
        for (var i = 0; i < doc.get("Whatever").length; i++) {
            var numThings = doc["Whatever"][i]["Things"].length;
            for (var j = 0; j < doc["Whatever"][i]["Things"].toArray().length; j++) {
                var foo = doc["Whatever"][i]["Things"]["something"];
                // do some work
            }
        ...

When I’ve asked some of these developers why they used doc.get("Whatever") and what it does, I get uninformative answers such as “it gets the Whatever property” (duh!) or “well, that’s just how you have to do it.” (Oh, really? Why?)

I get the same kind of non-answers for the reason why there is a call to .toArray() method in line 7 above, but not one in line 6.

Reading online hasn’t helped me much, because every example seems to refer to newer versions of Javascript than what we use in SnapLogic.

In the Nashorn engine (and our Javascript snaps specifically):

  1. If the Things property is sometimes an array and sometimes a scalar, would the lack of .toArray() cause the script to fail?
  2. If Things is sometimes completely absent, do either of the options shown offer any protection? (I’m guessing not, and that you’d need to check typeof for 'undefined'.)
  3. Does the .get() provide any additional safety (or do anything else beyond what doc[“Something”] does)?
  4. Let’s assume the arrays shown above are always arrays. Could someone just as easily (and more clearly) have written the following?
execute: function() {
    this.log.info("Executing Transform Script");
    while (this.input.hasNext()) {
        var doc = this.input.next();
        for (var i = 0; i < doc.Whatever.length; i++) {
            var numThings = doc.Whatever[i].Things.length;
            for (var j = 0; j < numThings; j++) {
                var foo = doc.Whatever[i].Things[j].something;
                // do some work
            }
        ...

Enquiring minds want to know!

PS: The examples above are elided from real code. This should go without saying, but there’s a lot of other cruft in between the lines shown here. The code does actually do something! :sweat_smile:

Hi @ForbinCSD,

The main difference between doc.get("something") and doc["something"] is that with the .get() method if the field is not found in the input data it returns null, if you are using it without it, it will return an empty object on the output.

var value = doc["readFromInput"];

If readFromInput does not exist in the input data this variable will simply return undefined.
If you are using the .get() method in that case this will return null.

Example:

var valueWithoutGet = doc["readFromInput"];
var valueWithGet = doc.get("readFromInput");

var output = new LinkedHashMap();
output.put("valueWithoutGet", valueWithoutGet);
output.put("valueWithGet", valueWithGet);
this.output.write(output);

You will simply get the following output:
image

In both cases there is an output.
If you want to control whether to output that data you can write the logic in the script:

if (valueWithGet != null) {
    output.put("value", valueWithGet);
}

Now for the other questions:

  1. If the Things property is sometimes an array and sometimes a scalar, would the lack of .toArray() cause the script to fail?
  • If you have a string value you can obviously traverse it because JavaScript allows it. If you have anything other than a string, you have to convert that to an array. You can use Arrays.asList(doc["scalar"]) to convert the scalar into an array.

Example input data:

[
    {
        "scalar": "test string"
    }
]

Script:

var doc = this.input.next();
var getValue = doc["scalar"];
                
var output = new LinkedHashMap();

for (var i = 0; i < getValue.length; i++) {
    output.put("Char at " + i, getValue[i]);
}

this.output.write(output);

Example to check if input is array or not and if it is not an array convert it to array:
Note: you have to import the ArrayList and Arrays class in this case.

importClass(java.util.ArrayList);
importClass(java.util.Arrays);
var doc = this.input.next();

var output = new LinkedHashMap();

if (doc["scalar"] instanceof ArrayList) {
    output.put("scalar", doc["scalar"])
} else {
    output.put("scalar", Arrays.asList(doc["scalar"]));
}

this.output.write(output);

By using the same sample, the “scalar” field is converted into an array.
image

  1. If Things is sometimes completely absent, do either of the options shown offer any protection? (I’m guessing not , and that you’d need to check typeof for 'undefined' .)
  • Yes, you have to check and make the logic if some of those values are completely absent.
  1. Does the .get() provide any additional safety (or do anything else beyond what doc[“Something”] does)?
  • The methog .get() returns null on the output if the input data is not present. This is basically the same as Null-safe access on the Mapper snap.
  1. Let’s assume the arrays shown above are always arrays. Could someone just as easily (and more clearly) have written the following?
  • Yes if they are always arrays and exist in the input data.
6 Likes

Wow. Thank you very much for a very complete answer; this was a real education!

My takeaway from this (besides now knowing the details of how these aspects of JavaScript work) is that our offshore developers were generally on the right track and meant well, but failed to execute their intentions.

Which means, if the doc is always intact with all of its expected properties and they’re of the expected (and invariant) types, my code above is just fine (and much easier to read and maintain).

And if the docs vary greatly in their content, then either there’s a deficiency earlier in the pipelines that needs fixing, or else the code needs to protect itself like the following (based on your training I received above)…

execute: function() {
    this.log.info("Executing Transform Script");
    while ( this.input.hasNext() ) {
        var doc = this.input.next();
        var theWhatever = doc.get("Whatever");
        if ( theWhatever ) {
            for ( var i = 0; i < theWhatever.length; ++i ) {
                var theThings = theWhatever[i].Things.toArray();
                if ( theThings ) {
                    for ( var j = 0; j < theThings.length; ++j ) {
                    var foo = theThings[j].something;
                    // do some work
                }
            ...

[ The .toArray() above being used in lieu of Arrays.asList() – which I can get away with only because the code doesn’t emit that array in the output. Otherwise I’d be using java.util.ArrayList and java.util.Arrays as you mentioned. ]

Thanks again for your super-helpful post!
–JohnB, aka “Forbin”