2

I'm trying to index a variety of JSON objects in Mongo. Once in a while, the JSON object has a key somewhere that contains a . in it, which makes Mongo complain:

MongoError: The dotted field 'foo (e.g. bar)' in 'key.0.prop foo (e.g. bar)' is not valid for storage.

Leaving aside that this data shouldn't have this kind of key, I want to temporarily deal with the problem by adjusting the keys with dots in them by removing the text containing the dot. However, these keys can be anywhere in the object and this requires a simultaneous traversal and key modification. For example,

{
  "foo": {
    "foo (e.g. bar)": "baz" 
  },
  "a": "b"
}

would become

{
  "foo": {
    "foo": "baz" 
  },
  "a": "b"
}

based on some function transform(badKey) that I would specify.

What's the easiest/most robust way to iterate through a Javascript object and modify keys based on some criterion? Note that some of the keys may be nested several levels deep and so a straightforward key-value iteration would not work here. Pointers to a library that provides this functionality would be awesome.

Andrew Mao
  • 35,740
  • 23
  • 143
  • 224
  • 2
    If you are open to lodash you can try this deep walking https://stackoverflow.com/a/35056190/2103767 – bhantol Feb 05 '18 at 22:45

2 Answers2

5

Without using JSON.stringify which is an overkill

function deepTransformKeys(obj) {
    if (obj && typeof obj === 'object') {
        var allKeys = Object.keys(obj);
        for(var i = 0 ; i < allKeys.length ; i++){
            var k = allKeys[i];

            var value = obj[k];
           if (isBadKey(k)) {
                var goodKey = transform(k);
                obj[goodKey] = value;
                delete obj[k];
           }
           if ( typeof value === 'object') {
               deepTransformKeys(value);
           }
        }
    }
    return obj;
}

You can define how you want the keys and ow you want to transform them:

var GoodKeyRegex = /^\w+$/
function isBadKey(key) {
    return !GoodKeyRegex.test(key);
}

function transform(key) {
    return key.replace(/\W+/g, "");
}

Simple input and output

var x = {
    "foo": {
        "foo (e.g. bar)": {
            "foo (e.g. bar)2": {
                "foo (e.g. bar)3" : {
                    "foo1": "bar1"
                }
            }
        }
    },
    "a": "b"
};

console.log('INPUT',JSON.stringify(x, null, ' '));
var y = deepTransformKeys(x);
console.log('---------------------------');
console.log('OUTPUT', JSON.stringify(y, null, ' '));
bhantol
  • 9,368
  • 7
  • 44
  • 81
1

Though I prefer Paticks's solution, here's my take on it using recursivity. I still think the most robust and efficient way to do it would be using lexers and parsers and ignore the language itself.

var json = {
  "foo": {
    "foo (e.g. bar)": "baz" 
  },
  "a": "b"
}

console.log(json)

function replace (obj) {
  for ( var key in obj ) {
    
    if (!obj.hasOwnProperty(key)) {continue;}
    
    if ( typeof obj[key] === 'object' && obj[key] !== null ) {
      replace(obj[key])
      }
    if (key.indexOf('.')> -1) {
      var new_key = key.replace(/\./g,' ')
      obj[new_key] = obj[key];
      delete obj[key];
    }
  }
}

replace(json)

console.log(json)