Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace all instances of a string within an object (and/or array) - JavaScript

What's the best way to search through a JavaScript object of unknown depth and properties and replace all instances of given string?

This works, but is it the best way?

var obj = {
   'a' : 'The fooman poured the drinks.',
   'b' : {
      'c' : 'Dogs say fook, but what does the fox say?'
   }
}

console.log (JSON.parse(JSON.stringify(obj).replace(/foo/g, 'bar')));

Fiddle: http://jsfiddle.net/93Uf4/3/

like image 669
Andrew Downes Avatar asked Apr 13 '14 19:04

Andrew Downes


1 Answers

Next to the way you proposed yourself, here is a classic loop approach. As mentioned by someone in a comment, it's more stable because you don't risk screwing the object up and throwing an error when trying to parse it back. On the other hand, some questions arise (see bottom).

Be careful, though, as the needle will be used as a regular expression. You may want to consider adding some sort of quoting to it.

I hope I didn't overlook anything, so test it and play around with it. Here you can find a fiddle.

/**
  * Replaces all occurrences of needle (interpreted as a regular expression with replacement and returns the new object.
  * 
  * @param entity The object on which the replacements should be applied to
  * @param needle The search phrase (as a regular expression)
  * @param replacement Replacement value
  * @param affectsKeys[optional=true] Whether keys should be replaced
  * @param affectsValues[optional=true] Whether values should be replaced
  */
Object.replaceAll = function (entity, needle, replacement, affectsKeys, affectsValues) {
    affectsKeys = typeof affectsKeys === "undefined" ? true : affectsKeys;
    affectsValues = typeof affectsValues === "undefined" ? true : affectsValues;

    var newEntity = {},
        regExp = new RegExp( needle, 'g' );
    for( var property in entity ) {
        if( !entity.hasOwnProperty( property ) ) {
            continue;
        }

        var value = entity[property],
            newProperty = property;

        if( affectsKeys ) {
            newProperty = property.replace( regExp, replacement );
        }

        if( affectsValues ) {
            if( typeof value === "object" ) {
                value = Object.replaceAll( value, needle, replacement, affectsKeys, affectsValues );
            } else if( typeof value === "string" ) {
                value = value.replace( regExp, replacement );
            }
        }

        newEntity[newProperty] = value;
    }

    return newEntity;
};

The last two parameters are optional, so it's perfectly fine to just call it like this:

var replaced = Object.replaceAll( { fooman: "The dog is fooking" }, "foo", "bar" );

However, there are still edge cases where it's unclear what should happen. For example:

// do you expect it to stay undefined or change type and become "undebazed"?
console.log( Object.replaceAll( { x: undefined }, "fin", "baz" ) );

// null or "nalala"?
console.log( Object.replaceAll( { x: null }, "ull", "alala" ) );

Or

// true or false?
console.log( Object.replaceAll( { x: true }, "true", "false" ) );

// true or "foo"?
console.log( Object.replaceAll( { x: true }, "true", "foo" ) );

And the same for numbers

// 1337 or 1007?
console.log( Object.replaceAll( { x: 1337 }, "33", "00" ) );

// 1337 or "1foo7"
console.log( Object.replaceAll( { x: 1337 }, "33", "foo" ) );

None of these cases are currently handled in my method – only objects (for nesting) and strings will be touched.

like image 160
Ingo Bürk Avatar answered Nov 10 '22 17:11

Ingo Bürk