Renaming object keys recursively

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..)


More concise implementation of the recursive mapping function for object keys:

const objectRecursiveKeyMap = (obj, fn) =>
  Object.fromEntries(Object.entries(obj).map(([key, value]) => {
    const getValue = v =>
      (typeof v === 'object' && v !== null) ? objectRecursiveKeyMap(v, fn) : v

    return [fn(key), Array.isArray(value)
      ? value.map(val => getValue(val))
      : getValue(value)]
  }))

Using example:

objectRecursiveKeyMap(obj, key => key.toUpperCase())

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;
}

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.