I have an arbitrary (E)JSON that gets created and sent over the wire from client to server in my Meteor app. It uses RegExp
objects to zero-in on results:
# on the client
selector =
"roles.user": { "$ne": null }
"profile.email": /^admin@/gi
All is dandy fine on the client side, but if I pass this to the server via Meteor.call
or Meteor.subscribe
, the resulting (E)JSON takes this form:
# on the server
selector =
"roles.user": { "$ne": null }
"profile.email": {}
...and somewhere an engineer dies a little on the inside.
There are plenty of resources on the Web explaining why RegEx is unserializable via JSON.stringify
/JSON.parse
or the equivalent EJSON
methods.
I'm not convinced RegEx serialization is impossible. So how can it be done?
After reviewing this HowTo and the Meteor EJSON Docs, we may serialize RegEx using the EJSON.addType
method.
Extend RegExp - Provide RegExp with the methods EJSON.addType
requires for implementation.
RegExp::options = ->
opts = []
opts.push 'g' if @global
opts.push 'i' if @ignoreCase
opts.push 'm' if @multiline
return opts.join('')
RegExp::clone = ->
self = @
return new RegExp(self.source, self.options())
RegExp::equals = (other) ->
self = @
if other isnt instanceOf RegExp
return false
return EJSON.stringify(self) is EJSON.stringify(other)
RegExp::typeName = ->
return "RegExp"
RegExp::toJSONValue = ->
self = @
return {
'regex': self.source
'options': self.options()
}
Call EJSON.addType - Do this anywhere. It's best to make it available to client AND server though. This is going to deserialize the object defined in toJSONValue
above.
EJSON.addType "RegExp", (value) ->
return new RegExp(value['regex'], value['options'])
Test In Your Console - Don't take my word for it. See for yourself.
> o = EJSON.stringify(/^Mooo/ig)
"{"$type":"RegExp","$value":{"regex":"^Mooo","options":"ig"}}"
> EJSON.parse(o)
/^Mooo/gi
And there you have a RegExp being serialized and parsed on client and server, able to be passed in over the wire, saved in a Session, and even possibly stored in a Collection of queries!
EDIT to addess IE10+ Error: Assignment to read-only properties is not allowed in strict mode Courtesy of @Tim Fletcher in the comments
import { EJSON } from 'meteor/ejson';
function getOptions(self) {
const opts = [];
if (self.global) opts.push('g');
if (self.ignoreCase) opts.push('i');
if (self.multiline) opts.push('m');
return opts.join('');
}
RegExp.prototype.clone = function clone() {
return new RegExp(this.source, getOptions(this));
};
RegExp.prototype.equals = function equals(other) {
if (!(other instanceof RegExp)) return false;
return EJSON.stringify(this) === EJSON.stringify(other);
};
RegExp.prototype.typeName = function typeName() {
return 'RegExp';
};
RegExp.prototype.toJSONValue = function toJSONValue() {
return { regex: this.source, options: getOptions(this) };
};
EJSON.addType('RegExp', value => new RegExp(value.regex, value.options));
There is a far simpler solution:
stringify your RegExp via .toString()
, send it to the server and then parse it back to RegExp.