String.prototype.replace() problems with JSON.stringify() and serialization of Objects

# Christoph Martens (11 years ago)

I wanted to ask if there's a plan to offer something like String.prototype.replace(search, replace, stringmode) as an alternative API for String.prototype.replace()?

The problem I'm refering here is that the ECMA spec section 15.5.4.11 is using the replaceValue as a string that gets parsed (and may contain a $ character). www.ecma-international.org/ecma-262/5.1/#sec-15.5.4.11

So, if you are serializing an Object via toJSON() and want to attach the Function.prototype.toString(), the resulting string may contain a $ character, even if it is written as a statement. As the String.prototype.replace API doesn't offer an option to turn off the "parsing" of the replaceValue, I think this is a conceptional flaw in the API design.

Example:

var func = function() {

   var y = [ 52, '$'];
   return null;

};

var data = func.toString();
var x = 'This is a simple {{blob}}';
var y = x.replace('{{blob}}', data); // Not the expected behaviour  because of $ character

Suggestion:

If you want to replace the string "much simplier" by calling it this way, it would save performance and allow you to replace raw strings using the simplest way, which is a simple indexOf() and substr().

var data = func.toString(); // Inside an engine, this would be a 
JSON.stringify(environment); or similar
var x = 'This is a simple {{blob}}';
var y = x.replace('{{blob}}', data, true); // note the suggested  optional flag

An example implementation I made for showing the usage I would prefer:

String.prototype.replace = function(key, value, raw) {

     if (raw === true) {

         var keyl = key.length;
         var keyi = this.indexOf(key);

         return '' + this.substr(0, keyi) + value + this.substr(keyi + keyl, this.length - keyl);

     } else {
         // Old behaviour
     }


     return this;

};

What do you guys think about the idea?

PS:

Currently, I have to polyfill a custom method for my build templates in my game engine because that API is missing and I think it is pretty important to offer.

If you are curious about the use case, it's used for a remote debugger and environment snapshots that allows restoring savegames on a different computer (NodeJS-sdl or Browser or Server or Android or iOS... etc.). Currently the development-0.8 branch, but may merge into master soon: LazerUnicorns/lycheeJS

# Alex Kit (11 years ago)

I am definitely for this, to make this special replacement patterns optional. As I also consider replace function to be dangerous when speaking about (string, string) replacement, and I also use custom util function for doing this. But for the (regexp, string) interface those replacement patterns are more often useful. But it would be nice, when there were the possibility to switch this option off, as it sometimes could lead to some unexpected replacements, that could cause the unexpected behaviour.

# Rick Waldron (11 years ago)

No extension is necessary here, just better familiarity with the existing capabilities:

  var func = function() {
    var y = [ 52, '$'];
    return null;
  };

  var data = func.toString();
  var x = 'This is a simple {{blob}}';
  var y = x.replace('{{blob}}', function() {
    return data;
  });
# Boris Zbarsky (11 years ago)

On 7/28/14, 11:09 AM, Rick Waldron wrote:

   var y = x.replace('{{blob}}', function() {
     return data;
   });

In fairness, that's the sort of thing that gives off a "WAT smell". Code like this without a comment that explains the indirection is just asking someone to "simplify" it, breaking it in the process...

# Rick Waldron (11 years ago)

I don't disagree with your feedback, but JS has had special semantics for "$" (with ’ or n) character in the replaceValue since ES3 (just short of 15 years). I didn't say that the solution was obvious, just that it required familiarity ;)

# Christoph Martens (11 years ago)

To be honest, I didn't know the trick with the dummy filter function returning the plain data. I expected that it was executed each time a match was found (similar to Array filter functions).

I think for external guys writing JavaScript, the WAT effect is pretty huge. Either we should change the behaviour of the parameters (which is bad for legacy implementations) or the flags to disable it optionally.

As I wasn't familiar with the replace behaviour before, I would expect the method to behave differently, depending on the arguments:

  • replace(regexp, string) -> parse string and see if $ is in there
  • replace(regexp, callback) -> callback is called each time regexp is matched
  • replace(string, callback) -> only called once
  • replace(string, string) -> called one time, using the first indexOf and replacing it
# Andrea Giammarchi (11 years ago)

'0101'.replace('0', function(){return 1}) == '1101'

so your last two are the same:

replace(string, callback) -> called one time, using the first indexOf and

replacing it invoking the callback replace(string, string) -> called one time, using the first indexOf and

replacing it via provided string and checking $

# Frankie Bagnardi (11 years ago)

You can also do a split-join to get a literal replace.

"I'll sell this for {cost}.".split("{cost}").join("$5.00"); "I'll sell this for $5.00."

# Alex Kit (11 years ago)

split-join to get a literal replace

You really think this is a good design, when for simple string-replace one should use string-split to create an array, and afterwards join it? This is a hacky workaround.

better familiarity with the existing capabilities

Rick has pointed for a function replacer. It is definitely better, but lets assume, you perform this in a loop. I am sure you don’t want to create the function on each iteration (especially when performance matters). for(let line of lines){ yield line.replace('foo', () => x); }

OK, we can initialize it before and pass it as a delegate. But do anybody consider this as a clean and correct way to do simple string replacement? And yes, this replacement patterns exist already for ages. But does this mean we should not move towards better APIs?

# Rick Waldron (11 years ago)

On Mon, Jul 28, 2014 at 10:29 PM, Christoph Martens <cmartensms at gmail.com> wrote:

I think for external guys writing JavaScript, the WAT effect is pretty huge. Either we should change the behaviour of the parameters (which is bad for legacy implementations)

The progress of JS is limited only to non-web-breaking changes.

# Frankie Bagnardi (11 years ago)

Of course it's a hack :-)

So, moving forward, it'd have to be an extra method on strings, that only provides a small change in behavior. It's probably not going to happen because it's such a small change.

Potentially a S.p.replaceAll which has the same behavior as split-join in that: "a b a c a d".replaceAll("a", "X") => "X b X c X d".

But then you end up with similar concerns to those expressed in the current .contains thread (it's inconsistent).

# Alex Kit (11 years ago)

Would be nice to use something similar that mozilla now does: replace(string, mix, ?flags), but so that flags are also applied for strings. It wont break nothing as when (string, string) is used, then usual behaviour is applied, but with flags the behaviour is extendible, it would be possible then:

// replace global
'foo x foo'.replace('foo', 'baz', 'g') //> 'baz x baz'

// global and case insensitive
'Foo x foo'.replace('foo', 'baz', 'gi') //> 'baz x baz'

// and now the subject, e.g. `r`(raw) flag
'foo bar`'.replace('foo', '$`', 'rg') //> '$` bar`'
# Rick Waldron (11 years ago)

ES6 template string already solve the problem that this thread states, without changing replace: gul.ly/149g

# Andrea Giammarchi (11 years ago)

that's the side effect of having fat arrow in the house, people need to pray the parser/engine is smart enough to do not create and trash it every time. all the time, during the loop or everywhere else is abused ... or, you simply do what you said already, you reuse code and create the callback outside the loop once which is the preferred choice since quite ever.

I actually recycle functions in .forEach and Array extras too, I'v never found it inconvenient or less elegant when the operation is not a one off that needs closure but a complete, non closure dependent, operation similar to what generic replacers are.

My .02

# Rick Waldron (11 years ago)

On Tue, Jul 29, 2014 at 4:24 PM, Rick Waldron <waldron.rick at gmail.com> wrote:

ES6 template string already solve the problem that this thread states, without changing replace: gul.ly/149g

Sorry, I should've just included this in-line:

  var func = function() {
    var y = [ 52, '$'];
    return null;
  };

  var data = func.toString();
  var x = `This is a simple ${data}`;

  console.log(x);

Result:

  This is a simple function () {
    var y = [52, '$'];
    return null;
  }
# Andrea Giammarchi (11 years ago)

curious to know if This is a simple ${func} would have invoked func.toString() for us implicitly ... will check specs

# Alex Kit (11 years ago)

ES6 template string already solve the problem that this thread states, without changing replace

with the link it was alright The problem here is that a template string is the literal, that means you have to hardcode it. But more interesting and more usual are dynamic strings(variables): var x = title.replace('foo', data);. And one more - template strings are just sugar for the concatenation, and the subject is about the replacements.