15

I'm using range.getValues() to load an array in Google Apps Script.

var array = SpreadsheetApp.getActive().getActivesheet().getRange('E10:E30').getValues();
> array = [["val1"],["val2"],["val3"],["val4"]]

I then want to loop through a separate list to see whether its elements exist in the array, using array.prototype.includes():

if(array.includes("val4")) {doSomething;}

Using array.prototype.includes() like this does not work for two reasons: 1) since each element in array is an array in itself and 2) because the method isn't available in Google Apps Script:

TypeError: Cannot find function includes in object

Then wanted to use array.prototype.flat() to solve 1) but it seems it isn't available in Google Apps Script either. I get the following error:

TypeError: Cannot find function flat in object

This is something I could do using brute force, but surely there's a neat way of doing it?

a-burge
  • 1,535
  • 1
  • 13
  • 25

3 Answers3

16

EDIT(2019-04-07): Please be advised. With the expected V8 upgrade (ECMAScript 2017) for App Script, the language will natively support Array.prototype.includes and many other modern Javascript features in the near future.


The simplest solution for array.prototype.includes is to use the following polyfill from MDN in your apps script project. Just create a script file and paste the following code - the code/polyfill will add the function directly to the Array prototype object:

// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
  Object.defineProperty(Array.prototype, 'includes', {
    value: function(searchElement, fromIndex) {

      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      // 1. Let O be ? ToObject(this value).
      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If len is 0, return false.
      if (len === 0) {
        return false;
      }

      // 4. Let n be ? ToInteger(fromIndex).
      //    (If fromIndex is undefined, this step produces the value 0.)
      var n = fromIndex | 0;

      // 5. If n ≥ 0, then
      //  a. Let k be n.
      // 6. Else n < 0,
      //  a. Let k be len + n.
      //  b. If k < 0, let k be 0.
      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

      function sameValueZero(x, y) {
        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
      }

      // 7. Repeat, while k < len
      while (k < len) {
        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
        // b. If SameValueZero(searchElement, elementK) is true, return true.
        if (sameValueZero(o[k], searchElement)) {
          return true;
        }
        // c. Increase k by 1. 
        k++;
      }

      // 8. Return false
      return false;
    }
  });
}

For array.prototype.flat the MDN site also provides alternative solutions. One of which leverages array.prototype.reduce and array.prototype.concat:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat#Alternative

TheAddonDepot
  • 8,408
  • 2
  • 20
  • 30
  • I didn't know about the polyfill. I have a feeling I'll be a frequent visitor there. Thanks – a-burge Aug 10 '18 at 08:16
  • 2
    @a-burge Not for long (at least where polyfills are concerned)...Google has plans to upgrade Apps Script to use Chrome's V8 engine which fully supports EcmaScript 2017 :) – TheAddonDepot Aug 16 '18 at 14:14
  • 2
    Also, you can easily stick the polyfill code in a separate file without having to worry about importing it into your main code. I often have a "polyfill.gs" file in my GAS projects. – Dustin Michels Nov 16 '18 at 23:07
  • 1
    @TheMaster Nothing concrete. But [this google groups post](https://groups.google.com/forum/#!topic/google-apps-script-community/1aGhw0rZBts) purports that the upgrade has been in beta for about a year. I have my fingers crossed for a release during Google Next 2019. – TheAddonDepot Apr 07 '19 at 23:40
  • I see... So probably in the next few days – TheMaster Apr 07 '19 at 23:47
  • October 2019 now, and I still don't see v8 :)) – 0xh8h Oct 15 '19 at 04:02
6

Replace the .includes() with .indexOf()+1 (it will yield 0 if the element is not present, else it will yield an integer between 1 and the length of your array). It works in Google Script.

if(array.indexOf("val4")+1) {doSomething;}
JPR
  • 572
  • 5
  • 12
  • `includes` and `indexOf` are not trivially interchangeable--one returns a boolean. The other returns an integer on the interval [-1, inf). You must also alter consuming logic at the call site. – tehhowch Dec 24 '18 at 14:53
  • 1
    In the presented case, it is in fact interchangeable (trivially so!). The consuming logic does not have to be altered in the slightest. Nevertheless, thank you for explaining the reason for your -1 – JPR Dec 24 '18 at 17:20
  • Consider the difference in behavior of your snippet with the following: `["val4", "foo"]` vs `["foo", "val4"]` vs `["foo", "bar"]` – tehhowch Dec 24 '18 at 20:20
  • I see, the result of `indexOf()` needed to be shifted one unit higher to account for the fact that `(0==false) == true`. Corrected. – JPR Dec 24 '18 at 21:27
3

Went with if(array.indexOf("val4") > -1) {doSomething}; as suggested in the comments


I first decided to go with array.filter():

var test = array.filter(element => element == "val4");
if(test != null) {doSomething};

But as noted below, the arrow functions don't work in Google Apps Script


But while looking for an answer I found this to solve 1):

function flatten(arrayOfArrays){
  return [].concat.apply([], arrayOfArrays);
}

Definitely better than I would have been able to come up with.

a-burge
  • 1,535
  • 1
  • 13
  • 25
  • Nope it doesn't. I just found out but thought I had it :( – a-burge Aug 09 '18 at 11:15
  • Thanks for the help! array.some is one of those things that exist but someone like me wouldn't think of. – a-burge Aug 09 '18 at 11:34
  • While arrow functions aren't supported, you can just use normal function definition... `arr.filter(function (e, i, a) {...` – tehhowch Aug 09 '18 at 13:29
  • True @tehhowch, just one step too steep for me now to translate between arrow and normal (i.e. not enough time to google and understand) – a-burge Aug 10 '18 at 08:19