1

I need to find and replace values in my object when they match a regular expression (e.g. **myVar**); The object that I need to loop through is user defined and structure varies.

Here is an example object, shortened for simplicity.

var testObject = {
    name: "/pricing-setups/{folderId}", 
    method: "POST", 
    endpoint: "/pricing-setups/:folderId", 
    functionName: "create",
    Consumes: null,
    filename: "apicontracts/pricingsetups/PricingSetupServiceProxy.java",
    pathParam: [
        {$$hashKey: "06S",
          key: "folderId",
          value: "**myVar**"}
    ],
    queryParam: [],
    request_payload: "{'title':'EnterAname'}",
    returnList: []
}

This object is passed into a master function that builds a angularjs resource object using the passed in object.

Here is the structure I am using:

function getTestResult(dataSource, options) {
      //input into the service should be api obj and selected environment obj

      //extend the passed object with options if passed
      var opts = $.extend({}, dataSource, options);
      //swap the {param} syntax for :param in opts.endpoint
      opts.endpoint = opts.endpoint.replace(/\}/g, "").replace(/\{/g, ":");

      //replace any defined vars passed in from scenario via scenario.userVar
      opts = replaceUserVars(opts, {"myVar": "C=1000=Corporate"});

    //MORE CODE STUFF
    // ...
    // ...
}

replaceUserVars() is based on the following questions/answers, but my case is different because the structure of the passed in object (var testObject) and the location of the found match will change.

SO... Here is my recursive solution to find the values that match the desired regex

function replaceUserVars(api, uvars) {
      if (!uvars) {
        return api;
      }
      var pattern =  new RegExp("\\*\\*\\w*\\*\\*", "g");//match **myVar**
      //check the api params for a match to regex
      // and if we find a match, replace the string with the userVar[regex match].value

      function warpSpeedAhead(collection) {
        _.find(collection, function (obj) { //find obj in api
          if (obj !== null && typeof(obj) === "object") {
            warpSpeedAhead(obj);
          }
          else {
            if (pattern.test(obj)) { //check the regex
              var sanitVar = obj.replace(/\*/g, ""); //remove the *
              if (uvars[sanitVar]) {
                console.log("found one");
                obj = uvars[sanitVar];
                //should be equivalent to 
                //api.pathParam[0][key] = uvars[sanitVar]; //works in this case ONLY
              }
            }
          }
        });
      }
      warpSpeedAhead(api);

      return api;
    }

This function successfully finds the values that match the regex, however, I can't seem to return the updated object without directly refrencing the structure of the testObject.

Here is a jsfiddle of the code above. http://jsfiddle.net/joshvito/2Lu4oexj/

My goal is to be able to search through the incoming object, find any values that match the regular expression, and change the value to the value defined in userVars (if the object value and userVar key match).

Community
  • 1
  • 1
joshvito
  • 1,498
  • 18
  • 23

5 Answers5

6

How about JSON.stringify and replace as string and back to JSON?

JSON.parse(JSON.stringify(testObject).replace(/\*\*([^*]+)\*\*/g,function($0,$1){return uvars[$1]||$0;}))
YOU
  • 120,166
  • 34
  • 186
  • 219
  • 1
    This is 4x faster than the recursion method: https://jsperf.com/recursion-vs-json-parse-json-stringify-x/1 – jeznag Aug 13 '18 at 00:34
  • Note: this won't work if you've got a circular json object, i recommend using `flatted` `import { parse, stringify} from 'flatted'` and just remove `JSON.` from your answer – Shannon Hochkins Feb 16 '21 at 23:58
  • JSON.stringify() will remove any properties that have a value of `undefined` - if that's okay for your use case then this solution is good. – Guy Aug 12 '21 at 22:08
4

I've made a solution based in your problem, a search and replacer in a complex object... 'll help you?

not change the object reference, only replace in strings...

can see a example in this fiddle... http://jsfiddle.net/Castrolol/gvpnxou0/

/* definition */

function replaceVars(objSource, objReplacer){

    var pattern = replaceVars.pattern;

    if(typeof objSource === "object" ){     
        if(objSource === null) return null;

        if(objSource instanceof Array){
            for(var i = 0; i < objSource.length; i++){
             objSource[i] =  replaceVars(objSource[i], objReplacer); 
            }           
        }else{        
            for(var property in objSource){         
                objSource[property] = replaceVars(objSource[property], objReplacer);            
            }
        }

        return objSource;

    }

    if(typeof objSource === "string"){

        return objSource.replace(pattern, function(finded, varName){
            return varName in objReplacer ? objReplacer[varName] : finded;
        });

    }

    return objSource;

}


 replaceVars.pattern = /\*\*([0-9a-z_$]{1,})\*\*/gi;

you can implement your solution with calling internally this function

Luan Castro
  • 1,184
  • 7
  • 14
  • This is 4x slower than the JSON.parse(JSON.stringify()) method https://jsperf.com/recursion-vs-json-parse-json-stringify-x/1 – jeznag Aug 13 '18 at 00:34
3

I modified Luan Castros solution to my needs. Note that for(key in myObject) is discouraged by linters, because of it also traverses the prototype attributes, which may be unwanted. At the same time, the Object.keys(myObject) will nicely work also on arrays.

function recursiveSubStringReplace (source, pattern, replacement) {

    function recursiveReplace (objSource) {
        if (typeof objSource === 'string') {
            return objSource.replace(pattern, replacement);
        }

        if (typeof objSource === 'object') {
            if (objSource === null) {
                return null;
            }

            Object.keys(objSource).forEach(function (property) {
                objSource[property] = recursiveReplace(objSource[property]);
            });

            return objSource;
        }

    }

    return recursiveReplace(source);
}
Markko Paas
  • 789
  • 7
  • 8
0
  • First: solve the problem of recursively renaming a key. You can use map keys deep
  • Then: write your iteratee so that you return the new key name

const yourObject = { 'a': 1, 'b': 2 }; _.mapKeys(yourObject, function(value, key) { const pattern = /.*/; // whatever you want to match if (key.match(pattern)){ return key + "1234"; // return the new key name } return key; });

Flavien Volken
  • 19,196
  • 12
  • 100
  • 133
0

For basic data processing we now use object-scan. It's very powerful and makes things a lot cleaner, but it takes a moment to wrap your head around it. Here is how you'd solve your questions

Note that the function mutates the object and returns the count of replaces. If you wanted to just replace the first occurrence, you could set abort to true

// const objectScan = require('object-scan');

const replace = (p, n, data) => objectScan(['**'], {
  rtn: 'count',
  filterFn: ({ value, parent, property }) => {
    if (p.test(value)) {
      parent[property] = n;
      return true;
    }
    return false;
  }
})(data);

const testObject = { name: '/pricing-setups/{folderId}', method: 'POST', endpoint: '/pricing-setups/:folderId', functionName: 'create', Consumes: null, filename: 'apicontracts/pricingsetups/PricingSetupServiceProxy.java', pathParam: [{ $$hashKey: '06S', key: 'folderId', value: '**myVar**' }], queryParam: [], request_payload: "{'title':'EnterAname'}", returnList: [] };

const r = replace(new RegExp('\\*\\*\\w*\\*\\*', 'g'), 'newValue', testObject);
console.log(r);
// => 1

console.log(testObject);
// => { name: '/pricing-setups/{folderId}', method: 'POST', endpoint: '/pricing-setups/:folderId', functionName: 'create', Consumes: null, filename: 'apicontracts/pricingsetups/PricingSetupServiceProxy.java', pathParam: [ { '$$hashKey': '06S', key: 'folderId', value: 'newValue' } ], queryParam: [], request_payload: "{'title':'EnterAname'}", returnList: [] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24