How to get the object type
Asked Answered
B

5

10

In my Google Apps Script App I am seeing the following error:

The script completed but the returned value is not a supported return type

How can I find out the type of the value which I am returning? I tried:

typeof(obj)

But all I get told is that it is an Object.

This error only occurs when the cache is empty and the data is loaded from a spreadsheet. E.g. User hits the page, the get the error, a refresh now loads the page as the cache was populated on the first hit. Now users can continue using the page until the cache is invalidated, then the next hit will cause this error again.

This error started happening today after I read and followed this document. Previously my script was loading data in the template, I have now refactored to load asynchronously using:

google.script.run

I took a look at what was happening in my page as to why it worked when read from cache and the only thing I could see different was that data in the cache had been parsed from a JSON string. When the cache is empty I have to stringify the object in order to save into the cache, so rather than returning my original object, I then parse the string I just created and return that and now there is no error.

I'm not sure where the error is coming from either, is it Apps Script or is it Caja?

Parsing the JSON string back in order to return is not the ideal solution either, what is this stringifying / parsing doing to my original object that removes the error?

When I use the built in debugger to inspect the objects (before and after the stringifying / parsing process) the values and types all seem to be identical to the best of my ability to check.

Is this a bug in Apps Script? Am I doing something wrong? What is the best solution to remove this error?

Bromine answered 18/12, 2013 at 17:24 Comment(0)
F
8

We can extend JavaScript objects to get them to tell us their object type, as shown in How do I get the name of an object's type in JavaScript?. This doesn't work for Google Apps Script objects, though.

Here's a hack I've put together that appears to work for GAS objects. I haven't tried every one, but a number of the primary objects do work.

function getObjType(obj) {
  var type = typeof(obj);
  if (type === "object") {
    try {
      // Try a dummy method, catch the error
      type = obj.getObjTypeXYZZY();
    } catch (error) {
      // Should be a TypeError - parse the object type from error message
      type = error.message.split(" object ")[1].replace('.','');
    }
  }
  return type;
}

Here's a test routine:

function test_getObjType() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  Logger.log("Spreadsheet: " + getObjType(spreadsheet));
  var sheet = spreadsheet.getActiveSheet();
  Logger.log("Sheet: " + getObjType(sheet));
  var range = sheet.getActiveCell();
  Logger.log("Range: " + getObjType(range));
  var string = "Hello";
  Logger.log("String: " + getObjType(string));
};

And the results:

[13-12-18 23:23:47:379 EST] Spreadsheet: Spreadsheet
[13-12-18 23:23:47:609 EST] Sheet: Sheet
[13-12-18 23:23:47:626 EST] Range: Range
[13-12-18 23:23:47:626 EST] String: string
Five answered 19/12, 2013 at 4:26 Comment(2)
getObjType() doesn't seem to work anymore as of 22nd Feb 2021. The error message string returned from "catch" is as shown below. 5:58:02 PM Info Spreadsheet: obj.getObjTypeXYZZY is not a function 5:58:02 PM Info Sheet: obj.getObjTypeXYZZY is not a function 5:58:02 PM Info Range: obj.getObjTypeXYZZY is not a function 5:58:02 PM Info String: stringArson
Error TypeError: Cannot read property 'replace' of undefinedMartine
M
2

Here's my helper function to get the object type in Google Apps Script:

/**
 *  Returns the object type.
 *
 *  @param {*} obj
 *  @returns {string}
 */
function getObjectType(obj) {
  let type = typeof obj;

  if (type === 'object') {
    if (Array.isArray(obj)) {
      type = 'Array';
    } else if (obj === null) {
      type = 'null';
    } else if (Object.prototype.toString.call(obj.getA1Notation) === '[object Function]') {
      // JSDoc usage: @param {SpreadsheetApp.Range}
      type = 'Range';
    } else if (Object.prototype.toString.call(obj.getActiveCell) === '[object Function]') {
      // JSDoc usage: @param {SpreadsheetApp.Sheet}
      type = 'Sheet';
    } else if (Object.prototype.toString.call(obj) === '[object Object]') {
      // Some other object (this block should come last)
      type = 'Object';
    }
  }

  // Note that NaN (Not a Number) returns type `number`
  return type;
}

Feel free to use it and extend it to support other SpreadsheetApp classes as your needs arise. If you do extend it, try to pick a method that is unique to that class.

Maid answered 23/8, 2023 at 22:13 Comment(1)
Thanks - this is the magic incantation that is currently working. This can be combined with a try to do a conditional: try { object.prototype.toString.call(obj.getTimezoneOffset()); console.log("This is a Date"); } catch (error) { console.log("Not Date"); }Wilderness
O
1

I know this is a VERY old post, but wanted to let everybody, who find themselves here following Google recommendation, know that the solution no longer works.

I was struggling with the same issue and tried the suggested solution. Looks like Google has (again) decided to change a functionality of their product without telling anybody or updating documentation.

The error message no longer contains the object type, so instead of getting "TypeError: Cannot find function foobar in object " we get "TypeError: Cannot find function foobar in object <insert output of toString() here>".

It's quite possible that the error message always used toString() method and since Google removed the object type from toString() method, they also broke this way of getting the object type.

I only needed to find out if the object was an array or a string and found a hack to solve this.

function getObjType(obj) {
  
  // If the object is an array, this will return the stored value,
  // if the object is a string, this will return only one letter of the string
  var type = obj[0];
  if (type.length == 1) {
    return 'string';
  }
  try {
    type = obj.foobar();
  } catch (error) {
    
    // TypeError no longer contains object type, just return 'array'
    Logger.log(error);
    return 'array';
  }
}
Ogata answered 27/8, 2020 at 18:45 Comment(1)
Why would you do any of this? You can test for String and Array with typeof obj == "string" and obj instanceof Array respectivelyIq
I
1

I went down a whole rabbit hole and was about ready to write a "guess type" function when I remembered that the problem began when the Logger console just spat out the "type" name of the object, rather than an object view with members. Turns out, the default toString() behavior of scripting types is to return their implicit type name:

  const filter = range.createFilter();
  Logger.log(filter.toString() == "Filter"); // true
  Logger.log(filter + "" == "Filter"); // true
  Logger.log(filter == "Filter"); // true

So you can check for scripting types by simply comparing it to the implicit type name. I don't know yet if there are types that have different toString() behavior, so maybe using something like object + "" is more robust, if there's a difference behind the scene(?)

Iq answered 29/3, 2023 at 23:8 Comment(0)
S
-1

As the top answer doesn't work anymore, I came up with a hacky solution for my own use case before later discovering that Google has built in functionality for finding types.

To give an example context, if you are iterating through paragraphs from a Google Doc and want to know if the paragraph represents text or an image, you can do:

const childType = paragraph.getChild(0).getType();
if (childType == DocumentApp.ElementType.TEXT) {
  // ...
}
else if (childType == DocumentApp.ElementType.INLINE_IMAGE) {
  // ...
}

See the documentation for more information.

Stokehole answered 4/2, 2023 at 1:21 Comment(1)
This is simply getting and comparing the type, as exposed, of the first child element. That has nothing to do with the OP problemIq

© 2022 - 2024 — McMap. All rights reserved.