Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Renaming object keys recursively

I have a recursive function to rename the key names of an object but I'm unable to figure out how to rename 2 of the keys (problem keys are objects)

I think the problem is where I'm checking for object type but how can I rename the key at that point?

The actual arrays are very large but a cut down version is below.

Any help appreciated.

var keys_short = ['ch','d','u','tz'];
var keys_long = ['children','data','user_id','time_zone'];
function refit_keys(o){
    build = {};
    for (var i in o){
        if(typeof(o[i])=="object"){
            o[i] = refit_keys(o[i]);
            build = o;
        }else{
            var ix = keys_short.indexOf(i);
            if(ix!=-1){
                build[keys_long[ix]] = o[keys_short[ix]];
            }
        }
    }
    return build;
}

My input looks like this:

{
    "id":"1",
    "ch":[
        {
            "id":"3",
            "ch":[
            ],
            "d":{
                "u":"3",
                "tz":"8.00"
            }
        },
        {
            "id":"45",
            "ch":[
                {
                    "id":"70",
                    "ch":[
                        {
                            "id":"43",
                            "ch":[
                            ],
                            "d":{
                                "u":"43",
                                "tz":"-7.00"
                            }
                        }
                    ],
                    "d":{
                        "u":"70",
                        "tz":"-7.00"
                    }
                }
            ],
            "d":{
                "u":"45",
                "tz":"-7.00"
            }
        }
    ],
    "d":{
        "u":"1",
        "tz":"8.00"
    }
}

My output like this:

{
    "id":"1",
    "ch":[
        {
            "id":"3",
            "ch":[
            ],
            "d":{
                "user_id":"3",
                "time_zone":"8.00"
            }
        },
        {
            "id":"45",
            "ch":[
                {
                    "id":"70",
                    "ch":[
                        {
                            "id":"43",
                            "ch":[
                            ],
                            "d":{
                                "user_id":"43",
                                "time_zone":"-7.00"
                            }
                        }
                    ],
                    "d":{
                        "user_id":"70",
                        "time_zone":"-7.00"
                    }
                }
            ],
            "d":{
                "user_id":"45",
                "time_zone":"-7.00"
            }
        }
    ],
    "d":{
        "user_id":"1",
        "time_zone":"8.00"
    }
}
like image 658
Andy Gee Avatar asked Nov 03 '13 11:11

Andy Gee


3 Answers

There are a couple of problems there.

One is that you're falling prey to The Horror of Implicit Globals by failing to declare your build variable in the function.

But the logic has issues as well, here's a minimal reworking:

var keys_short = ["ch","d","u","tz"];
var keys_long = ["children","data","user_id","time_zone"];
function refit_keys(o){
    var build, key, destKey, ix, value;

    // Only handle non-null objects
    if (o === null || typeof o !== "object") {
        return o;
    }

    // Handle array just by handling their contents
    if (Array.isArray(o)) {
        return o.map(refit_keys);
    }

    // We have a non-array object
    build = {};
    for (key in o) {
        // Get the destination key
        ix = keys_short.indexOf(key);
        destKey = ix === -1 ? key : keys_long[ix];

        // Get the value
        value = o[key];

        // If this is an object, recurse
        if (typeof value === "object") {
            value = refit_keys(value);
        }

        // Set it on the result using the destination key
        build[destKey] = value;
    }
    return build;
}

Live Example:

"use strict";
var input = {
    "id":"1",
    "ch":[
        {
            "id":"3",
            "ch":[
            ],
            "d":{
                "u":"3",
                "tz":"8.00"
            }
        },
        {
            "id":"45",
            "ch":[
                {
                    "id":"70",
                    "ch":[
                        {
                            "id":"43",
                            "ch":[
                            ],
                            "d":{
                                "u":"43",
                                "tz":"-7.00"
                            }
                        }
                    ],
                    "d":{
                        "u":"70",
                        "tz":"-7.00"
                    }
                }
            ],
            "d":{
                "u":"45",
                "tz":"-7.00"
            }
        }
    ],
    "d":{
        "u":"1",
        "tz":"8.00"
    }
};

var keys_short = ["ch","d","u","tz"];
var keys_long = ["children","data","user_id","time_zone"];
function refit_keys(o){
    var build, key, destKey, ix, value;

    // Only handle non-null objects
    if (o === null || typeof o !== "object") {
        return o;
    }

    // Handle array just by handling their contents
    if (Array.isArray(o)) {
        return o.map(refit_keys);
    }

    // We have a non-array object
    build = {};
    for (key in o) {
        // Get the destination key
        ix = keys_short.indexOf(key);
        destKey = ix === -1 ? key : keys_long[ix];

        // Get the value
        value = o[key];

        // If this is an object, recurse
        if (typeof value === "object") {
            value = refit_keys(value);
        }

        // Set it on the result using the destination key
        build[destKey] = value;
    }
    return build;
}

console.log(refit_keys(input));
.as-console-wrapper {
    max-height: 100% !important;
}

But rather than parallel arrays, I'd suggest using a mapping, via an object or a Map:

// Object with no prototype to avoid false matches on `toString` and other built-ins
var mapShortToLong = Object.assign(Object.create(null), {
    "ch": "children",
    "d":  "data",
    "u":  "user_id",
    "tz": "time_zone"
});
function refit_keys(o){
    var build, key, destKey, value;

    // Only handle non-null objects
    if (o === null || typeof o !== "object") {
        return o;
    }

    // Handle array just by handling their contents
    if (Array.isArray(o)) {
        return o.map(refit_keys);
    }

    build = {};
    for (key in o) {
        // Get the destination key
        destKey = mapShortToLong[key] || key;

        // Get the value
        value = o[key];

        // If this is an object, recurse
        if (typeof value === "object") {
            value = refit_keys(value);
        }

        // Set it on the result using the destination key
        build[destKey] = value;
    }
    return build;
}

Live Example:

"use strict";
var input = {
    "id":"1",
    "ch":[
        {
            "id":"3",
            "ch":[
            ],
            "d":{
                "u":"3",
                "tz":"8.00"
            }
        },
        {
            "id":"45",
            "ch":[
                {
                    "id":"70",
                    "ch":[
                        {
                            "id":"43",
                            "ch":[
                            ],
                            "d":{
                                "u":"43",
                                "tz":"-7.00"
                            }
                        }
                    ],
                    "d":{
                        "u":"70",
                        "tz":"-7.00"
                    }
                }
            ],
            "d":{
                "u":"45",
                "tz":"-7.00"
            }
        }
    ],
    "d":{
        "u":"1",
        "tz":"8.00"
    }
};

// Object with no prototype to avoid false matches on `toString` and other built-ins
var mapShortToLong = Object.assign(Object.create(null), {
    "ch": "children",
    "d":  "data",
    "u":  "user_id",
    "tz": "time_zone"
});
function refit_keys(o){
    var build, key, destKey, value;

    // Only handle non-null objects
    if (o === null || typeof o !== "object") {
        return o;
    }

    // Handle array just by handling their contents
    if (Array.isArray(o)) {
        return o.map(refit_keys);
    }

    build = {};
    for (key in o) {
        // Get the destination key
        destKey = mapShortToLong[key] || key;

        // Get the value
        value = o[key];

        // If this is an object, recurse
        if (typeof value === "object") {
            value = refit_keys(value);
        }

        // Set it on the result using the destination key
        build[destKey] = value;
    }
    return build;
}

console.log(refit_keys(input));
.as-console-wrapper {
    max-height: 100% !important;
}

Or in modern JavaScript:

// Using a `Map` here to provide a `Map` example, but you can ue an object as
// in the previous ones if you prefer if the keys are strings
const mapShortToLong = new Map([
    ["ch", "children"],
    ["d",  "data"],
    ["u",  "user_id"],
    ["tz", "time_zone"],
]);
function refit_keys(o){
    // Only handle non-null objects
    if (o === null || typeof o !== "object") {
        return o;
    }

    // Handle array just by handling their contents
    if (Array.isArray(o)) {
        return o.map(refit_keys);
    }

    const build = {};
    for (const key in o) {
        // Get the destination key
        const destKey = mapShortToLong.get(key) || key;

        // Get the value
        let value = o[key];

        // If this is an object, recurse
        if (typeof value === "object") {
            value = refit_keys(value);
        }

        // Set it on the result using the destination key
        build[destKey] = value;
    }
    return build;
}

Live Example:

"use strict";
var input = {
    "id":"1",
    "ch":[
        {
            "id":"3",
            "ch":[
            ],
            "d":{
                "u":"3",
                "tz":"8.00"
            }
        },
        {
            "id":"45",
            "ch":[
                {
                    "id":"70",
                    "ch":[
                        {
                            "id":"43",
                            "ch":[
                            ],
                            "d":{
                                "u":"43",
                                "tz":"-7.00"
                            }
                        }
                    ],
                    "d":{
                        "u":"70",
                        "tz":"-7.00"
                    }
                }
            ],
            "d":{
                "u":"45",
                "tz":"-7.00"
            }
        }
    ],
    "d":{
        "u":"1",
        "tz":"8.00"
    }
};

// Using a `Map` here to provide a `Map` example, but you can ue an object as
// in the previous ones if you prefer if the keys are strings
const mapShortToLong = new Map([
    ["ch", "children"],
    ["d",  "data"],
    ["u",  "user_id"],
    ["tz", "time_zone"],
]);
function refit_keys(o){
    // Only handle non-null objects
    if (o === null || typeof o !== "object") {
        return o;
    }

    // Handle array just by handling their contents
    if (Array.isArray(o)) {
        return o.map(refit_keys);
    }

    const build = {};
    for (const key in o) {
        // Get the destination key
        const destKey = mapShortToLong.get(key) || key;

        // Get the value
        let value = o[key];

        // If this is an object, recurse
        if (typeof value === "object") {
            value = refit_keys(value);
        }

        // Set it on the result using the destination key
        build[destKey] = value;
    }
    return build;
}

console.log(refit_keys(input));
.as-console-wrapper {
    max-height: 100% !important;
}
like image 167
T.J. Crowder Avatar answered Nov 16 '22 10:11

T.J. Crowder


A bit late, but I was looking for a nice short implementation that also deals with arrays (the previous answer doesn't), so I decided to post my generic ES6 implementation, as it may help some folks:

function deepMapKeys(originalObject, callback) {
  if (typeof originalObject !== 'object') {
    return originalObject
  }

  return Object.keys(originalObject || {}).reduce((newObject, key) => {
    const newKey = callback(key)
    const originalValue = originalObject[key]
    let newValue = originalValue
    if (Array.isArray(originalValue)) {
      newValue = originalValue.map(item => deepMapKeys(item, callback))
    } else if (typeof originalValue === 'object') {
      newValue = deepMapKeys(originalValue, callback)
    }
    return {
      ...newObject,
      [newKey]: newValue,
    }
  }, {})
}

For the case in question, the call would be:

deepMapKeys(inputObject, key => (keys_long[keys_short.indexOf(key)] || key))

That being said, if you can use npm, there are several packages out there (here's one, another..)

like image 6
Bogdan D Avatar answered Nov 16 '22 12:11

Bogdan D


One of the problem could be that the variable build is actually a global variable. Hence, it contains only the result of the first depth in the recursion.

Adding a var before build should solve part of the problem.

like image 2
musically_ut Avatar answered Nov 16 '22 11:11

musically_ut