3

For some reason, objects that have been returned from the server end of a Google Apps Script project have any member functions replaced by null. Here's some sample code demonstrating this:

server.gs

function A() {
  this.a = 'a string';
  this.toString = function() { return this.a; }
  this.innerObj = { b : "B", toString : function(){ return 'inner object'; } }
}

function getA() { return new A(); }

clientJS.html; /* or console, if you prefer... */

google.script.run.withSuccessHandler(console.log).getA();

Object, when printed raw, looks something like this:

{ "a": "a string", "toString": null, "innerObj": { "b": "B", "toString": null } }

Live demo of the problem

what can I do about this?!

Mike Warren
  • 3,796
  • 5
  • 47
  • 99

2 Answers2

3

This is by design, as noted in the documentation

Legal parameters and return values are JavaScript primitives like a Number, Boolean, String, or null, as well as JavaScript objects and arrays that are composed of primitives, objects and arrays. [...] Requests fail if you attempt to pass a Date, Function, DOM element besides a form, or other prohibited type, including prohibited types inside objects or arrays.

As a workaround, you can stringify an object with its methods:

JSON.stringify(obj, function(key, value) {
  if (typeof value === 'function') {
    return value.toString();
  } else {
    return value;
  }
});

and then reconstruct the functions from strings on the receiving side.

1

My answer extends Desire's answer. I was able to get this to work, by stringifying the member functions, but for reconstruction, instead of use eval(), I used these:

function shouldBeFunction(str)
{
    str = str.toString().trim();
    // str should *not* be function iff it doesn't start with 'function'
    if (str.indexOf('function') !== 0) return false;
    // str should *not* be function iff it doesn't have a '(' and a ')'
    if ((str.indexOf('(') === -1) || (str.indexOf(')') === -1)) return false;
    // str should *not* be function iff it doesn't have a '{' and a '}'
    if ((str.indexOf('{') === -1) || (str.indexOf('}') === -1)) return false;
    return true;
}

var myObjectWithFunctions = JSON.parse(objectWithStringsAsFunctions,
    function (key, value) {
        var DEBUG = false;
        if ((typeof(value) === 'string') && (shouldBeFunction(value))) {
            if (DEBUG) {
                console.log('function string detected on property named : ' + key);
                console.log('function text: " ' + value + '"');
            }
            // get arguments list, if there is one to get
            var argsList = value.substring(value.indexOf('(') + 1, value.indexOf(')')).trim();
            if (DEBUG) console.log('argsList == ' + argsList);
            // get function body
            var functionBody = value.substring(value.indexOf('{') + 1, value.lastIndexOf('}')).trim();
            if (DEBUG) console.log('functionBody == ' + functionBody);
            if (argsList) 
                return new Function(argsList, functionBody);    
            return new Function(functionBody);
        }
        return value;
    }
);

The reason being that I don't know if eval() is evil, or a sign of bad programming practice.

UPDATE: I learned that eval() may be OK if the strings came from the server and are being turned back into functions on the client-side

Mike Warren
  • 3,796
  • 5
  • 47
  • 99