35

Given a block like this:

var foo = {
  "regexp": /^http:\/\//,
  "fun": function() {},
}

What is a proper way to store it in JSON?

Ivar
  • 6,138
  • 12
  • 49
  • 61
Huang
  • 1,919
  • 3
  • 15
  • 11
  • You can't. JSON is for data only, not code. There's no way to transfer functions, unless you can transform it to a string first. – Evert Nov 30 '11 at 15:12

9 Answers9

49

You have to store the RegExp as a string in the JSON object. You can then construct a RegExp object from the string:

// JSON Object (can be an imported file, of course)
// Store RegExp pattern as a string
// Double backslashes are required to put literal \ characters in the string
var jsonObject = { "regex": "^http:\\/\\/" };

function fun(url) {
    var regexp = new RegExp(jsonObject.regex, 'i');

    var match;

    // You can do either:
    match = url.match(regexp);
    // Or (useful for capturing groups when doing global search):
    match = regexp.exec(url);

    // Logic to process match results
    // ...
    return 'ooga booga boo';
}

As for functions: they should not be represented in JSON or XML anyway. A function may be defined as an object in JS, but its primary purpose is still to encapsulate a sequence of commands, not serve as a wrapper for basic data.

Ankit Aggarwal
  • 1,546
  • 10
  • 11
7

You could do something like this...

Method

JSONEX = {

    stringify: function(obj){
        var jsonified = {}
        // loop through object and write string and type to newly stored data structure
        for(i in obj)
            jsonified[i] = {
                // some voodoo to determine the variable type
                type: Object.prototype.toString.call(obj[i]).split(/\W/)[2],
                value: obj[i].toString()
            }    
        return JSON.stringify(jsonified)
    },

    parse: function(json){
        objectified = {}
        obj = JSON.parse(json)
        // loop through object, and handle parsing of string according to type
        for(i in obj)
            if(obj[i].type == "RegExp"){
                var m = obj[i].value.match(/\/(.*)\/([a-z]+)?/)
                objectified[i] = new RegExp(m[1],m[2]);
            } else if(obj[i].type == "String"){
                objectified[i] = obj[i].value
            } else if(obj[i].type == "Function"){
                // WARNING: this is more or less like using eval
                // All the usual caveats apply - including jailtime
                objectified[i] = new Function("return ("+obj[i].value+")")();
            }
            // ADD MORE TYPE HANDLERS HERE ...

        return objectified

    }
}

Usage

myThing = {
    regex: new RegExp("123","g"),
    text: "good",
    func: function(x){
        return x * x
    }
}

json = JSONEX.stringify(myThing)
// "{"regex":{"type":"RegExp","value":"/123/g"},"text":{"type":"String","value":"good"},"func":{"type":"Function","value":"function (x) {\n    return x * x;\n}"}}"

obj = JSONEX.parse(json)
// native object representing original object

N.B.

Almost a good solution, but does not work with regex (for me anyway)

http://jsonplus.com/

// doing this: jsonPlus.stringify(myThing)
// just stores `regex` as an empty object
Billy Moon
  • 57,113
  • 24
  • 136
  • 237
6

TL;DR:

Storing both as objects seems the best to me:

{
    "regexp": {
        "body": "^http:\\/\\/",
        "flags": ""
    },
    "fun": {
        "args": [],
        "body": ""
    }
}

RegExps:

There are already a lot of great answers for storing regexps: store them as raw strings, so that one can use RegExp constructor to create an actual regexp from strings.

My addition would be to keep in mind flags. The OP might not need it, but it is definitely has to be addressed. RegExp constructor takes flags in the form of a string as its second parameter. So the contract for JSON-stored regexp would be:

interface JSONStoredRegExp {
    body: string; // "" (empty string) for empty regexp
    flags: string; // "" (empty string) for zero flags
}

... and, for example, this JSON:

{
    "regexp": {
        "body": "abc",
        "flags": "gi"
    }
}

... would produce this regexp:

RegExp(json.regexp.body, json.regexp.flags);
/abc/gi

Functions:

Unless a given function is pure, it seems weird to me to transfer it via JSON. Also, the code of such an algorithm is likely incompatible with any language, other than JavaScript.

Anyway, if that is still necessary to do, I would recommend the same approach: transfer functions as an object, rather than a string. One can also use Function constructor to create functions from serialized list of arguments and body.

interface JSONStoredFunction {
    args: string[]; // [] (empty array) for zero arguments
    body: string; // "" (empty string) for no-op function
}

Function constructor takes body as its last argument, and each of the parameters has to be passed separately; so this JSON:

{
    "fun": {
        "args": [ "x", "y" ],
        "body": "return x + y;"
    }
}

... will produce this function:

Function(...json.fun.args, json.fun.body);
function anonymous(x, y) { return x + y; }

It might be inconvenient to use ... the spread operator. In that case, using .apply might help:

Function.apply(null, json.fun.args.concat(json.fun.body));
Parzh from Ukraine
  • 7,999
  • 3
  • 34
  • 65
2

In core JSON, there isn't; the JSON spec only allows for primitive values (string/numbers/boolean/null), and arrays and objects.

Jason S
  • 184,598
  • 164
  • 608
  • 970
1

There is good movement to use https://json5.org/ some new super-set of JSON that contain a lot of things that Illegal in JSON

{
  // comments
  unquoted: 'and you can quote me on that',
  singleQuotes: 'I can use "double quotes" here',
  lineBreaks: "Look, Mom! \
No \\n's!",
  hexadecimal: 0xdecaf,
  leadingDecimalPoint: .8675309, andTrailing: 8675309.,
  positiveSign: +1,
  trailingComma: 'in objects', andIn: ['arrays',],
  "backwardsCompatible": "with JSON",
}

github: https://github.com/json5/json5

pery mimon
  • 7,713
  • 6
  • 52
  • 57
0

I've used and would recommend serialize-javascript npm package from yahoo. It can serialize JSON with functions and regex specifically and handles other cases.

From their docs:

var serialize = require('serialize-javascript');

const serialized = serialize({
    str  : 'string',
    num  : 0,
    obj  : {foo: 'foo'},
    arr  : [1, 2, 3],
    bool : true,
    nil  : null,
    undef: undefined,

    fn: function echo(arg) { return arg; },
    re: /([^\s]+)/g
});

produces

'{"str":"string","num":0,"obj":{"foo":"foo"},"arr":[1,2,3],"bool":true,"nil":null,"fn":function echo(arg) { return arg; },"re":/([^\\s]+)/g}'

which can be hydrated with

const obj = JSON.parse(serialized)

This can be verified by looking at their unit tests.

cchamberlain
  • 17,444
  • 7
  • 59
  • 72
0

Saving regular expression patterns in JSON is a pain. A regular expression string such as /^\S+@\S+\.\S+$/ (used to check for email address format) needs to be escaped in JSON to /^\\S+@\\S+\\.\\S+$/ to be valid JSON.

In javascript, to avoid having to deal with escaping and un-escaping regex patterns I store in JSON, I encode the regex pattern using encodeURIComponent() before parsing the JSON. So if I want to save a regex pattern such as {"pattern": "/^\S+@\S+\.\S+$/"} in JSON (which will not validate as JSON), then the encoded pattern would be saved in the JSON as {"pattern": "%5E%5CS%2B%40%5CS%2B%5C.%5CS%2B%24"}. It is not ideal but is the least painful way I could find to avoid parsing issues.

To extract the regex pattern string from the JSON and convert to a regex literal value, I use decodeURIComponent() on the pattern and then use the new RegExp() constructor to create the regex literal.

// patternValue = /^\S+@\S+\.\S+$/
const json = JSON.parse(`{"pattern": "${encodeURIComponent(patternValue)}" }`);  // {"pattern": "%5E%5CS%2B%40%5CS%2B%5C.%5CS%2B%24" }
const dp = decodeURIComponent(json.pattern);
const regExString = new RegExp(dp); 
console.log(regExString);   // /^\S+@\S+\.\S+$/
w. Patrick Gale
  • 1,643
  • 13
  • 22
0

It is not JSON, but it is a form of serialization: foo.toSource() gives a string representation: "({regexp:/^http:\\/\\//, fun:(function () {})})". Using bar = eval(foo.toSource()); assigns a new object with a regex and a function to bar.

I don't know how well it is supported. Several website mention that it is gecko-only, although they are two years old. I currently only have access to Firefox, so you test whether it works in the browsers you want to support (probably IE, Chrome, Safari and Opera).

Daan Wilmer
  • 937
  • 4
  • 13
  • 1
    Don't use eval unless you have expressly matched a string against a "safe" pattern (whether through regexps or parsers or whatever), which is non-trivial -- that's one reason why JSON is so simple; it can be parsed safely. Regexps are probably sanitizable (but not easily so); functions are almost certainly probably not, and provably so via Godel's theorem or some such technique. – Jason S Nov 30 '11 at 19:24
-1

All answers above is right, instead of you can save this part of code as a string and then do eval()

var sCode = 'var oFoo = {"regexp": new RegExp("/^http:\/\//"), "fun": function() {}}';
eval(sCode);
console.log(oFoo);
balkon_smoke
  • 1,136
  • 2
  • 10
  • 25
  • Don't use eval in general (see my comment in Daan's answer) – Jason S Nov 30 '11 at 19:24
  • @JasonS I know about that, and this is just a way to do that Huang wants – balkon_smoke Dec 01 '11 at 13:08
  • This answer does not give any kind of warnings about the security implications of doing it this way. if you added the warning inside the answer itself, rather than in the comment, then it would be a correct albeit dangerous reply. – ftrotter Nov 10 '15 at 23:40