Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any possibility to have JSON.stringify preserve functions?

Take this object:

x = {
 "key1": "xxx",
 "key2": function(){return this.key1}
}

If I do this:

y = JSON.parse( JSON.stringify(x) );

Then y will return { "key1": "xxx" }. Is there anything one could do to transfer functions via stringify? Creating an object with attached functions is possible with the "ye goode olde eval()", but whats with packing it?

like image 211
campino2k Avatar asked Oct 13 '11 19:10

campino2k


People also ask

Does JSON Stringify preserve functions?

To be clear, the output looks like JSON but in fact is just javascript. JSON. stringify works well in most cases, but "fails" with functions.

Can you Stringify functions?

The JSON. stringify() method converts a JavaScript object or value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.

Is JSON Stringify safe?

stringify() is fine. Using the output of JSON. stringify() in a JavaScript context will result in the expected behavior.

Does JSON Stringify remove undefined?

JSON. stringify will omit all object attributes that are undefined .


4 Answers

json-stringify-function is a similar post to this one.

A snippet discovered via that post may be useful to anyone stumbling across this answer. It works by making use of the replacer parameter in JSON.stringify and the reviver parameter in JSON.parse.

More specifically, when a value happens to be of type function, .toString() is called on it via the replacer. When it comes time to parse, eval() is performed via the reviver when a function is present in string form.

var JSONfn;
if (!JSONfn) {
    JSONfn = {};
}

(function () {
  JSONfn.stringify = function(obj) {
    return JSON.stringify(obj,function(key, value){
            return (typeof value === 'function' ) ? value.toString() : value;
        });
  }

  JSONfn.parse = function(str) {
    return JSON.parse(str,function(key, value){
        if(typeof value != 'string') return value;
        return ( value.substring(0,8) == 'function') ? eval('('+value+')') : value;
    });
  }
}());

Code Snippet taken from Vadim Kiryukhin's JSONfn.js or see documentation at Home Page

like image 131
Arthur Weborg Avatar answered Oct 06 '22 10:10

Arthur Weborg


I've had a similar requirement lately. To be clear, the output looks like JSON but in fact is just javascript.

JSON.stringify works well in most cases, but "fails" with functions.

I got it working with a few tricks:

  1. make use of replacer (2nd parameter of JSON.stringify())
  2. use func.toString() to get the JS code for a function
  3. remember which functions have been stringified and replace them directly in the result

And here's how it looks like:

// our source data
const source = {
    "aaa": 123,
    "bbb": function (c) {
        // do something
        return c + 1;
    }
};

// keep a list of serialized functions
const functions = [];

// json replacer - returns a placeholder for functions
const jsonReplacer = function (key, val) {
    if (typeof val === 'function') {
  	    functions.push(val.toString());
        
        return "{func_" + (functions.length - 1) + "}";
    }
        
    return val;
};

// regex replacer - replaces placeholders with functions
const funcReplacer = function (match, id) {
   return functions[id];
};

const result = JSON
    .stringify(source, jsonReplacer)               // generate json with placeholders
    .replace(/"\{func_(\d+)\}"/g, funcReplacer);   // replace placeholders with functions

// show the result
document.body.innerText = result;
body { white-space: pre-wrap; font-family: monospace; }

Important: Be careful about the placeholder format - make sure it's not too generic. If you change it, also change the regex as applicable.

like image 26
Christian Avatar answered Oct 06 '22 12:10

Christian


Technically this is not JSON, I can also hardly imagine why would you want to do this, but try the following hack:

x.key2 = x.key2.toString();
JSON.stringify(x)  //"{"key1":"xxx","key2":"function (){return this.key1}"}"

Of course the first line can be automated by iterating recursively over the object. Reverse operation is harder - function is only a string, eval will work, but you have to guess whether a given key contains a stringified function code or not.

like image 7
Tomasz Nurkiewicz Avatar answered Oct 06 '22 11:10

Tomasz Nurkiewicz


You can't pack functions since the data they close over is not visible to any serializer. Even Mozilla's uneval cannot pack closures properly.

Your best bet, is to use a reviver and a replacer.

https://yuilibrary.com/yui/docs/json/json-freeze-thaw.html

The reviver function passed to JSON.parse is applied to all key:value pairs in the raw parsed object from the deepest keys to the highest level. In our case, this means that the name and discovered properties will be passed through the reviver, and then the object containing those keys will be passed through.

like image 5
Mike Samuel Avatar answered Oct 06 '22 10:10

Mike Samuel