-1

Really struggling to find the right wording for this issue, I am sure the answer is posted somewhere and has to do with not understanding fully how RegExp.prototype.exec() works.

I have a large object with keys that are strings and follow this pattern. "foo.bar.baz": 'test' where each segment is of arbitrary length, and all the keys start with foo, but the rest vary. I need to construct an array of all of the middle segments, ie. 'bar' in this example. To do this I am using a simple regex grouping.

const myRegex = /foo\.([^\.]*)\./g

String starts with foo, followed by a ".", and then capture everything until the next ".".

Iterating over the object with the keys described above, I want to append the middle section (bar) to an array each time, excluding duplicates.

for(const key in myObject){           //loop over each key in the object
  const matches = []
  match = myRegex.exec(key)
  if (match) {
    if (matches.indexOf(match[1]) === -1) {    //exclude duplicates
      matches.push(match[1])
    }
  }

The strange behavior is the fact that this only works for all of the second segments that have more than one occurrence. For example given the folowing object.

{
    'foo.bar.bar': 'test',
    'foo.bar.baz': 'test',
    'foo.baz.bar': 'test',
    'foo.baz.baz': 'test',
    'foo.bam.bar': 'test'
}

Only bar, and baz get pushed to matches. If foo.bam.baz: 'test' is added to the object, bam is matched and pushed to the array. If one of the foo.bar.x or foo.baz.x is removed, then that key is not pushed. It appears to only be matching on the second attempt, and I cannot figure out why.

Any insight is greatly appreciated.

Justin Olson
  • 126
  • 2
  • 13
  • 2
    Wouldn't a simpler approach be to split the string on period to get the different segments? https://jsfiddle.net/g8acfL88/ – jas7457 Sep 20 '17 at 23:47
  • 1
    dude i found it... im confused too: https://stackoverflow.com/questions/11477415/why-does-javascripts-regex-exec-not-always-return-the-same-value – Isaac Sep 20 '17 at 23:51
  • 2
    Don't use the `g` modifier. – Barmar Sep 21 '17 at 00:11

2 Answers2

1

Looks like regex objects are stateful:

var myRegex = /foo\.([^\.]*)\./g;

console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))
console.log(myRegex.exec("foo.bar.bar"))

(2) ["foo.bar.", "bar", index: 0, input: "foo.bar.bar"]
null
(2) ["foo.bar.", "bar", index: 0, input: "foo.bar.bar"]
null
(2) ["foo.bar.", "bar", index: 0, input: "foo.bar.bar"]
null
(2) ["foo.bar.", "bar", index: 0, input: "foo.bar.bar"]
null
(2) ["foo.bar.", "bar", index: 0, input: "foo.bar.bar"]
Isaac
  • 11,409
  • 5
  • 33
  • 45
0

You can use Object.keys() to get property values of object, .map() to iterate properties, RegExp /^foo\.|\.\w+$/g to match "foo" and "." followed by one or more word characters followed by end of string, Set to remove duplicates from resulting collection, and then convert Set to array

const o = {
    'foo.bar.bar': 'test',
    'foo.bar.baz': 'test',
    'foo.baz.bar': 'test',
    'foo.baz.baz': 'test',
    'foo.bam.bar': 'test'
}

let matches = [...new Set(Object.keys(o).map(prop => 
                prop.replace(/^foo\.|\.\w+$/g, "")))];

console.log(matches);
guest271314
  • 1
  • 15
  • 104
  • 177