8

I have an array of objects. Each object has a lot of keys (more than 100) and some of these keys can have special chars that I would like to remove.

I try to do what I want in this way:

const result = data.map(datum => {
  const keys = Object.keys(datum)
  const replacedKeys = keys.map(key => {
    const newKey = key.replace(/[.|&;$%@%"<>+]/g, '')
  })
  // ??
})

But I'm sure it's not the right way..

John Conde
  • 217,595
  • 99
  • 455
  • 496

6 Answers6

5

You could map new object with a new key and create a single object with Object.assign.

const result = data.map(datum => Object.assign(...Object
    .keys(datum)
    .map(key => ({ [key.replace(/[.|&;$%@%"<>+]/g, '')]: datum[key] }))
));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Just so others understand what is being given as the input: ` data = [ { someKey: 1 }, { some0Key: 1 }, { some1Key: 1, some2Key: 1, }, { some3Key: 1, some4Key: 1, some5Key: 1, some6Key: 1, some7Key: 1, some8Key: 1, some9Key: 1, some10Key: 1, }, { some11Key: 1, some12Key: 1, some13Key: 1, some14Key: 1, some15Key: 1, }, ]; ` – Emmanuel Mahuni Aug 29 '20 at 13:01
3

With the ES8 Object.fromEntries method that has already found its way in FireFox, you can do:

const sanitiseKeys = o => Object.fromEntries(Object.entries(o).map(([k,v]) => 
                                            [k.replace(/[.|&;$%@%"<>+]/g,""), v]));

// Example use:
var data = [{ "name#": "John" }, { "@key": 2 }];

data = data.map(sanitiseKeys);

console.log(data);

If not yet implemented, here is a polyfill:

Object.fromEntries = arr => Object.assign({}, ...arr.map( ([k, v]) => ({[k]: v}) ));
guest271314
  • 1
  • 15
  • 104
  • 177
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Wouldn't your polyfill cause problems with large objects with lots of keys? Normally there's a limit of how many arguments a function can take. –  Feb 09 '19 at 11:08
  • @DoMiNeLa10 _"Normally there's a limit of how many arguments a function can take"_? – guest271314 Feb 09 '19 at 11:09
  • @guest271314: https://stackoverflow.com/questions/22747068/is-there-a-max-number-of-arguments-javascript-functions-can-accept –  Feb 09 '19 at 11:10
  • @DoMiNeLa10 https://stackoverflow.com/a/22747268/, https://tc39.github.io/ecma262/#sec-list-and-record-specification-type – guest271314 Feb 09 '19 at 11:14
  • @guest271314 that's the specification which has nothing to do with how implementations treat it. Most of them have limits, with jscore being especially low. If we were going by the spec, I could say ECMAScript has TCO, but almost no implementations actually have it. –  Feb 09 '19 at 11:30
  • @DoMiNeLa10 Then what is the current "limit" given X amount of RAM and N amount of disk space for each implementation? The specification is clear _"These sequences may be of any length."_ – guest271314 Feb 09 '19 at 11:32
  • @guest271314: https://stackoverflow.com/a/22747272/6019272 (this one lacks safari and other environments with jscore) –  Feb 09 '19 at 11:42
  • @DoMiNeLa10 The previous comment asked for _current_ implementations, with X amount of RAM and N amount of available disk space. Chromium is at version 70+, Firefox is at version 65+ now. Is your only source for a "limit" on the amount of arguments a function can have that SO answer? Does the same apply to arrow functions? Have you performed the tests yourself, in 2019? What yes, what are the OS's you have tested at? What was the available RAM and disk space available? Can you post a link to the tests that you have performed with the details of the architectures and environments listed? – guest271314 Feb 09 '19 at 11:48
1

You can convert the plain JavaScript object to JSON using JSON.stringify() and match the property of valid JSON using String.prototype.replace(), then convert back to a plain JavaScript object using JSON.parse().

Removed " from character class as valid JSON property is surrounded by double quotes ".

The RegExp

([.|&;$%@%<>+]+)(?=([^\1]+|)":)

creates a capture group containing the character class and matches the character class followed by one or more characters not in the character class followed by closing double quote of the property name " followed by colon character or double quotes followed by colon character.

The matched character class can be replaced with an empty string '' or any other character.

let o = {"a.B|c&D;0$_%@q%<Z>5+":1};

console.log(o);

o = JSON.parse(JSON.stringify(o).replace(/([.|&;$%@%<>+]+)(?=([^\1]+|)":)/g, ''));

console.log(
  JSON.stringify(o)
, /[.|&;$%@%<>+]+/.test(Object.keys(o)[0]) // false
);
guest271314
  • 1
  • 15
  • 104
  • 177
1

This solution relies on String.prototype.replace(), so it can take Strings or RegExps as the source, and allows for subsitution. Keep in mind that it's not very performant, but it uses only pure functions:

const data = {
  someKey:   1,
  some0Key:  1,
  some1Key:  1,
  some2Key:  1,
  some3Key:  1,
  some4Key:  1,
  some5Key:  1,
  some6Key:  1,
  some7Key:  1,
  some8Key:  1,
  some9Key:  1,
  some10Key: 1,
  some11Key: 1,
  some12Key: 1,
  some13Key: 1,
  some14Key: 1,
  some15Key: 1,
};

// simple equivalent of proposed Object.fromEntries()
const fromEntries = (entries) =>
      entries.reduce((obj, [key, value]) => ({
        [key]: value,
        ...obj
      }), {});

const replaceObjectKeys = (obj, from, to) =>
      fromEntries(
        Object.entries(obj)
          .map(([key, value]) => [key.replace(from, to), value]));

console.log(replaceObjectKeys(data, /Key$/, 'prop'));

fromEntries could be easily rewritten into a faster implementation at the cost of introducing mutable variables.

0

Consider using Array#reduce() to aggregate values for keys of your input object. The reason for this is that your key sanitization (ie removing unwanted characters from keys) may cause distinct key/value pairs of your input object to "reduce", so that a sanitized key effectively relates to multiple values. For instance an input object like:

const data = {
  'key' : 'value0',
  'key&&&' : 'value1',
  'key$%<>' : 'value2'
}

would (based on your sanitation) yield an output object with a single key relating to multiple values:

const data = {
  'key' : 'value0', // what about value1, value2 ?
}

To address this, you might consider aggregating values with common sanitized keys to an array as shown:

const data = {
  'key' : 'value0',
  'key&&&' : 'value1',
  'key$%<>' : 'value2',
  'foo' : 'value3'
}

const result = Object.entries(data).reduce((obj, [ key, value ]) => {
  
  const sanitizedKey = key.replace(/[.|&;$%@%"<>+]/g, '');
  const objValue = obj[ sanitizedKey ]
  
  /* 
  Account for conflicting keys after santizing by grouping
  values in a nested array
  */
  if(objValue) {
    obj[ sanitizedKey ] = [value].concat(objValue)
  }
  else {
    obj[ sanitizedKey ] = value
  }
  
  return obj;
  
}, {});

console.log(result)
Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
0

To extend @Nina's answer, here is the full representation:

data = [
  {someKey:   1},
  {some0Key:  1},
  {some1Key:  1,
    some2Key:  1},
  {some3Key:  1,
    some4Key:  1,
    some5Key:  1,
    some6Key:  1,
    some7Key:  1,
    some8Key:  1,
    some9Key:  1,
    some10Key: 1,
  },
  {some11Key: 1,
    some12Key: 1,
    some13Key: 1,
    some14Key: 1,
    some15Key: 1,}
];
result = data.map(datum => Object.assign(...Object
    .keys(datum)
    .map(key => ({ [key.replace(/some/g, 'bum')]: datum[key] }))
));

results:

result === [
  {bumKey:   1},
  {bum0Key:  1},
  {bum1Key:  1,
    bum2Key:  1},
  {bum3Key:  1,
    bum4Key:  1,
    bum5Key:  1,
    bum6Key:  1,
    bum7Key:  1,
    bum8Key:  1,
    bum9Key:  1,
    bum10Key: 1,
  },
  {bum11Key: 1,
    bum12Key: 1,
    bum13Key: 1,
    bum14Key: 1,
    bum15Key: 1,}
];
Emmanuel Mahuni
  • 1,766
  • 16
  • 16