46

So, I was interested to find that JSON.stringify reduces a RegExp to an empty object-literal (fiddle):

JSON.stringify(/^[0-9]+$/) // "{}"

Is this behavior expected? I realize that a RegExp is an object with no properties to serialize. That said, dates are objects too; yet JSON.stringify() manages to produce a meaningful string:

JSON.stringify(new Date) // "2014-07-03T13:42:47.905Z"

I would have hoped that JSON would give RegExp the same consideration by using RegExp.prototype.toString().

  • 2
    You should convert it to string and use `RegExp` object later for unserialization – Shiplu Mokaddim Aug 22 '12 at 15:01
  • @shiplu.mokadd.im I'm not really looking for a work-around. It's easy enough to manually serialize a regex. –  Aug 22 '12 at 15:04
  • 1
    Yeah I know. thats why I didn't answer but comment. – Shiplu Mokaddim Aug 22 '12 at 15:09
  • 5
    @canon the real problem is that regular expression objects don't have as much universality as strings, numbers, and booleans do. JSON is a data interchange format, so it's important that JSON be usable from as many language contexts as possible. Deserializing a RegExp instance into a language with a significantly different regular expression mechanism - or no such mechanism - would be problematic. – Pointy Aug 22 '12 at 15:13

6 Answers6

53

If somebody would be interested, there is a nice workaround. I don't think, that current behaviour is correct. For example, Date instance is not serialized to empty object like RegExp, though it is an object and also has no JSON representation.

RegExp.prototype.toJSON = RegExp.prototype.toString;


// sample
var foo = { rgx: /qux$/ig, date: new Date }

JSON.stringify(foo);
//> {"rgx":"/qux$/gi","date":"2014-03-21T23:11:33.749Z"}"
tenbits
  • 7,568
  • 5
  • 34
  • 53
  • 3
    Nice, I hadn't known about [`toJSON()`](https://developer.mozilla.org/en-US/docs/JSON#toJSON()_method). – canon Apr 12 '14 at 16:09
  • When would this not be appropriate to use? – matthoiland May 12 '14 at 16:24
  • 5
    @matthoiland, I use this always, and I don't know any practical use-case, when this can't be used or leads to an undefined behaviour. But as you can see, it extends RegExp's prototypes _though you can extend it via `Object.defineProperty` to make it not enumerable (but have you ever iterated over regexp instances?)_; and it also changes defaults tojson behaviour, so this theme can be speculative, but with my experience, I find this approach and such behaviour correct. – tenbits May 12 '14 at 16:56
  • 3
    I think they should standartize this. It would make a lot of sense to have this as default behaviour of JSON parser/stringifier. – Capaj Nov 12 '15 at 23:44
  • What about if your regexp contains forward slash? e.g. (new RegExp( "/" )).toString() returns "/\\//" – goofballLogic Sep 26 '16 at 12:51
  • @goofballLogic, that's correct, the forward slash should be escaped. And both "/" and "\\/" for the RegExp constructor produce the same regexp pattern. – tenbits Sep 26 '16 at 14:27
  • @tenbits shouldn't the serialization allow round-tripping? e.g. https://gist.github.com/goofballLogic/61e4e7df3e7671b9ff66114a32c8ad9d – goofballLogic Sep 26 '16 at 15:58
  • 2
    @goofballLogic, you can't do this: `new RegExp(x.toString())`, as `toString` creates RegExp literal notation, it has trailing forward slash, and ends also with forward slash, optionally followed by regexp flages: `/REGEXP_STRING/FLAGS`. So `new RegExp("foo", "i").toString()` produces: "/foo/i" string. Consider my edits: https://gist.github.com/tenbits/ec7f0155b57b2d61a6cc90ef3d5f8b49 – tenbits Sep 26 '16 at 16:31
  • 2
    @tenbits, yeah. I'm going to avoid using this pattern because people will undoubtedly make the mistake of assuming that string to be consumable by the RegExp constructor. – goofballLogic Sep 27 '16 at 11:18
38

Both JSON.stringify and JSON.parse can be customized to do custom serialization and deserialization by using the replacer and reviver arguments.

var o = {
  foo: "bar",
  re: /foo/gi
};

function replacer(key, value) {
  if (value instanceof RegExp)
    return ("__REGEXP " + value.toString());
  else
    return value;
}

function reviver(key, value) {
  if (value.toString().indexOf("__REGEXP ") == 0) {
    var m = value.split("__REGEXP ")[1].match(/\/(.*)\/(.*)?/);
    return new RegExp(m[1], m[2] || "");
  } else
    return value;
}

console.log(JSON.parse(JSON.stringify(o, replacer, 2), reviver));

You just have to come up with your own serialization format.

Jacob Brazeal
  • 654
  • 9
  • 16
Fabian Jakobs
  • 28,815
  • 8
  • 42
  • 39
  • 1
    Nice, I feel like there could be a js library just for serializing/deserializing a regex. – Joe Heyming Oct 01 '17 at 18:54
  • 1
    doing reClone = eval(re.toString()) is a lot shorter and easier to understand (and works). I'm not saying this is how you should do it, I'm asking a question. Anyone know if the `eval` method is slower compared to the method used here? – mathheadinclouds Dec 29 '19 at 22:46
  • there is npm lib that you can use https://www.npmjs.com/package/serialize-javascript – Dvir Arad Jan 19 '22 at 17:34
32

Yes, because there's no canonical representation for a RegExp object in JSON. Thus, it's just an empty object.

edit — well it's 2018 now; the answers suggesting solutions using .toJSON() etc are probably fine, though I'd add the method to the prototype with

Object.defineProperty(RegExp.prototype, "toJSON", {
  value: RegExp.prototype.toString
});

and so on. That ensures that the function name isn't enumerable, which makes the monkey-patch somewhat more hygienic.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 2
    I would think that since it already has a literal representation in javascript, that would translate well to JSON; I suppose not. – canon Aug 22 '12 at 15:00
  • 1
    Yes - it's an object with no properties, and that's all JSON can see. You could convert it to a string I guess, but then it's still not really a RegExp instance. – Pointy Aug 22 '12 at 15:01
  • Clearly that's a bug. @tenbits response should be the answer. – tgoneil Apr 21 '17 at 02:57
  • 2
    @tgoneil it's not a "bug"; the JSON spec simply does not include regular expressions as a primitive value type. It also doesn't include dates and various other things. – Pointy Apr 21 '17 at 12:20
7

Here's how I solved this issue:

Serialize it as a string:

var pattern = /foobar/i;
var serialized = JSON.stringify(pattern.toString());

Then rehydrate it using another regex:

var fragments = serialized.match(/\/(.*?)\/([a-z]*)?$/i);
var rehydrated = new RegExp(fragments[1], fragments[2] || '');

Preserves the pattern and flags - hope this helps someone!

probablyup
  • 7,636
  • 1
  • 27
  • 40
  • 1
    A bit more future-proof to use `[a-zA-Z]+` instead of `[gimy]?` (there are 6 flags at the current time; also there can be more than one flag... in fact I think using them all, e.g. `/x/gimsuy` is valid!) – Darren Cook Jun 06 '19 at 19:45
  • In 2019 yes, I think some of those weren't in use at the time of my OG answer :) – probablyup Jun 15 '19 at 15:59
5

I think a good approach would be something like this:

function stringifyFilter(key,value) {
    if (value instanceof RegExp) {
        return value.toString();
    }

    return value;
}

var myObj = {
    text : 'Howdy ho!',
    pattern : /[a-z]+/i
}

JSON.stringify(myObj,stringifyFilter); // output: {"text":"Howdy ho!","pattern":"/[a-z]+/i"}
tin
  • 834
  • 6
  • 16
4
RegExp.prototype.toJSON = RegExp.prototype.toString;

var regexp = /^[0-9]+$/;
var foo = { rgx: regexp.source, date: new Date };
var stringified = JSON.stringify(foo);
new RegExp(JSON.parse(stringified).rgx)
Sagi
  • 8,972
  • 3
  • 33
  • 41