14

Has anyone implemented Mozilla's Object.toSource() method for Internet Explorer and other non-Gecko browsers? I'm looking for a lightweight way to serialize simple objects into strings.

nickf
  • 499,078
  • 194
  • 614
  • 709
JC Grubbs
  • 34,411
  • 27
  • 64
  • 74

8 Answers8

9

Consider the following: (when using FireFox 3.6)

javascript:
  x=function(){alert('caveat compter')};
  alert(['JSON:\t',JSON.stringify(x),'\n\ntoSource():\t',x.toSource()].join(''));

which displays:

JSON:

toSource(): (function () {alert("caveat compter");})

or even:

javascript:
x=[];x[3]=x;
alert('toSource():\t'+x.toSource());
alert('JSON can not handle this at all and goes "infinite".');
alert('JSON:\n'+JSON.stringify(x));

which displays:

toSource(): #1=[, , , #1#]

and the "going 'infinite'" message whence follows JSON's stackoverflow recursive digression.

The examples emphasize the subtleties of expression explicitly excluded from JSON representation that are rendered by toSource().

It is not easy to compose a program to replicate the same results, for ALL cases, as the Gecko toSource() primitive, which is exceptionally powerful.

Below are a few of the 'moving targets' that a program duplicating toSource() functionality MUST handle successfully:

javascript:
function render(title,src){ (function(objRA){
    alert([ title, src,
        '\ntoSource():',objRA.toSource(),
        '\nJSON:',JSON.stringify(objRA)     ].join('\n'));
    })(eval(src));
}
render('Simple Raw Object source code:',
    '[new Array, new Object, new Number, new String, ' +
        'new Boolean, new Date, new RegExp, new Function]'  );

render( 'Literal Instances source code:',
    '[ [], 1, true, {}, "", /./, new Date(), function(){} ]'    );

render( 'some predefined entities:',
    '[JSON, Math, null, Infinity, NaN, ' +
        'void(0), Function, Array, Object, undefined]'      );

which displays:

    Simple Raw Object source code:
    [new Array, new Object, new Number, new String, 
                new Boolean, new Date, new RegExp, new Function]

    toSource():
    [[], {}, (new Number(0)), (new String("")), 
                (new Boolean(false)), (new Date(1302637995772)), /(?:)/, 
                            (function anonymous() {})]

    JSON:
    [[],{},0,"",false,"2011-04-12T19:53:15.772Z",{},null]

and then displays:

    Literal Instances source code: 
    [ [], 1, true, {}, "", /./, new Date(), function(){} ]

    toSource():  
    [[], 1, true, {}, "", /./, (new Date(1302638514097)), (function () {})]

    JSON:  
    [[],1,true,{},"",{},"2011-04-12T20:01:54.097Z",null]

and lastly:

    some predefined entities:
    [JSON, Math, null, Infinity, NaN, void(0), 
                        Function, Array, Object, undefined]

    toSource():
    [JSON, Math, null, Infinity, NaN, (void 0), 
        function Function() {[native code]}, function Array() {[native code]}, 
            function Object() {[native code]}, (void 0)]

    JSON:
    [{},{},null,null,null,null,null,null,null,null]

The previous analysis is significant if the translations are 'to be used' or less stringent if the need is for simple benign human consumption to view an object's internals. A primary JSON feature, as a representation, is the transfer of some structured information 'to be used' between environments.

The quality of a toSource() function is a factor in the denotational semantics of a programme influencing, but not limited to:
round trip computations, least fixed point properties, and inverse functions.

  • Does repetition of code conversion quiesce to a static state?
  • Does obj.toSource() == eval(eval(eval(obj.toSource()).toSource()).toSource()).toSource()?
  • Does it make sense to consider whether obj == eval(obj.toSource())?
  • Does undoing a conversion result in, not just a similar object, but an IDENTICAL one?
    This is a loaded question with profound implications when cloning an operational object.

and many, many more ...

Note that the above questions take on added significance when obj contains an executed code object, such as (new Function ... )()!

Ekim
  • 353
  • 2
  • 4
5

If matching the exact serialization format of Firefox is not your aim, you could use one of the JavaScript JSON serialization/deserialization libraries listed at http://json.org. Using a standard scheme like JSON may be better than mimicking the proprietary Gecko format.

Ates Goral
  • 126,894
  • 24
  • 129
  • 188
  • 1
    Rather, if JSON is enough for the OP's needs, i.e. no need to decompile functions, allow circular references, etc. – Nickolay Oct 05 '08 at 15:08
5

If you need to serialise objects with circular references you can use the cycle.js extension to the JSON object by Douglas Crockford available at https://github.com/douglascrockford/JSON-js. This works very like toSource(), although it won't serialise functions (but could probably be adapted to using a function's toString method).

Daniel Lewis
  • 2,051
  • 2
  • 17
  • 6
3

You could do something like this:

Object.prototype.getSource = function() {
    var output = [], temp;
    for (var i in this) {
        if (this.hasOwnProperty(i)) {
            temp = i + ":";
            switch (typeof this[i]) {
                case "object" :
                    temp += this[i].getSource();
                    break;
                case "string" :
                    temp += "\"" + this[i] + "\"";    // add in some code to escape quotes
                    break;
                default :
                    temp += this[i];
            }
            output.push(temp);
        }
    }
    return "{" + output.join() + "}";
}
nickf
  • 499,078
  • 194
  • 614
  • 709
  • 1
    I'm not a JavaScript expert, but Object.prototype is verboten! See: http://erik.eae.net/archives/2005/06/06/22.13.54/ Maybe it's better to implement this as a free function. – jk. Oct 05 '08 at 07:33
  • 2
    Its not a good idea to modify the Object prototype. Also a string type will need more than just \" escaping. It will need escaping of \t\n\r etc. – AnthonyWJones Oct 05 '08 at 08:11
  • 4
    It's a perfectly fine idea to modify the Object prototype, provided that you know how to code in JavaScript and aren't using a library (like jQuery) that has explicitly chosen be broken when you do so (for the benefit of increased speed). – Phrogz Feb 14 '11 at 19:29
1

In order to take this a little further: when you send something - to work on - a receiver must get it and be able to work on it. So this next bit of code will do the trick - adapted from the previous answer by Eliran Malka.

// SENDER IS WRAPPING OBJECT TO BE SENT AS STRING
// object to serialize
var s1 = function (str) {
    return {
        n: 8,
        o: null,
        b: true,
        s: 'text',
        a: ['a', 'b', 'c'],
        f: function () {
            alert(str)
        }
    }
};
// test
s1("this function call works!").f();
// serialized object; for newbies: object is now a string and can be sent ;)
var code = s1.toString();

// RECEIVER KNOWS A WRAPPED OBJECT IS COMING IN
// you have to assign your wrapped object to somevar
eval('var s2  = ' + code);
// and then you can test somevar again
s2("this also works!").f();

Be aware of the use of eval. If you own all code being transferred: feel free to use it (although it can also have disadvantages). If you don't know where the source is coming from: it's a no-no.

Community
  • 1
  • 1
dragon
  • 11
  • 3
0

You don't have to use toSource(); wrap the code to be serialized in a function that returns the JSON struct, and use function#toString() instead:

var serialized = function () {
    return {
        n: 8,
        o: null,
        b: true,
        s: 'text',
        a: ['a', 'b', 'c'],
        f: function () {
            alert('!')
        }
    }
};
serialized.toString();

See a live demo on jsFiddle.

Eliran Malka
  • 14,498
  • 5
  • 72
  • 96
0

See also JavaScript data formatting/pretty printer. I think the routine exports in valid JS format, so it can be eval to get it back.

[EDIT] Actually, not! It is OK for quick dump but not for real serialization. I improved it, result below:

function SerializeObject(obj, indentValue)
{
  var hexDigits = "0123456789ABCDEF";
  function ToHex(d)
  {
    return hexDigits[d >> 8] + hexDigits[d & 0x0F];
  }
  function Escape(string)
  {
    return string.replace(/[\x00-\x1F'\\]/g,
        function (x)
        {
          if (x == "'" || x == "\\") return "\\" + x;
          return "\\x" + ToHex(String.charCodeAt(x, 0));
        })
  }

  var indent;
  if (indentValue == null)
  {
    indentValue = "";
    indent = ""; // or " "
  }
  else
  {
    indent = "\n";
  }
  return GetObject(obj, indent).replace(/,$/, "");

  function GetObject(obj, indent)
  {
    if (typeof obj == 'string')
    {
      return "'" + Escape(obj) + "',";
    }
    if (obj instanceof Array)
    {
      result = indent + "[";
      for (var i = 0; i < obj.length; i++)
      {
        result += indent + indentValue +
            GetObject(obj[i], indent + indentValue);
      }
      result += indent + "],";
      return result;
    }
    var result = "";
    if (typeof obj == 'object')
    {
      result += indent + "{";
      for (var property in obj)
      {
        result += indent + indentValue + "'" +
            Escape(property) + "' : " +
            GetObject(obj[property], indent + indentValue);
      }
      result += indent + "},";
    }
    else
    {
      result += obj + ",";
    }
    return result.replace(/,(\n?\s*)([\]}])/g, "$1$2");
  }
}

indentValue can be null, "", " ", "\t" or whatever. If null, no indentation, output a rather compact result (could use less spaces...).

I could use an array to stack the results then join them, but unless you have giant objects, string concatenation should be good enough...
Also doesn't handle cyclic references...

Community
  • 1
  • 1
PhiLho
  • 38,673
  • 6
  • 89
  • 128
0

Nobody has mentioned it yet, so I'll point out there is a polyfill available for Mozilla's Object.toSource at https://github.com/oliver-moran/toSource.js

max pleaner
  • 23,403
  • 8
  • 50
  • 99