Skip to main content
1 of 8
MLM
  • 173
  • 9

Convert Object Keys according to Table/Map object

I have implemented a recursive function that converts an objects keys according to another lookup table/map. You are able to convert back and forth using the 3rd swap_conversion_table_key_value boolean argument.

My use case is to convert object keys to single characters to slim down the amount of characters generated from JSON.stringify. Then be able to convert it back to full keys on another client.

Along with the usual code review criteria(mainly clarity), I am wondering if I just reinvented the wheel or overthought the whole problem. The code does seem a bit long for the functionality and I am not keen how how it directly modifies the object(wish it would return a new object). This means that you have to clone the data before every use to keep all the references to the original data happy.

Demo: jsFiddle

Usage:

recursiveConvertKeys(resultant_data, conversion_table, false);

// 3rd parameter defines whether we should swap the key-value in the table/map (good for converting back to the original data)
recursiveConvertKeys(resultant_data, conversion_table, true);

Code:

function recursiveConvertKeys(data_object, conversion_table, swap_conversion_table_key_value, __is_recursive_iteration, __current_object_level, __current_conversion_table_level)
{
    /* Usage: 
        var data = {asdf: 1, qwer: 2};
        var conversion_table = {asdf: 'a', qwer: 'q'};

        // Clone the data so we don't overwrite it
        var resultant_data = $.extend(true, {}, data);
        // Now execute the key converting process
        recursiveConvertKeys(resultant_data, conversion_table, false);
        console.log("Reversed Data:", resultant_data);
        
        // If you want to reverse the process simply pass true for the `swap_conversion_table_key_value` argument
        recursiveConvertKeys(resultant_data, conversion_table, true);
        console.log("Back to normal Data:", resultant_data);
    */
    //
    // Do not pass in parameters for the double underscore arguments. These are private and only used for self recursive calling


    // Start at the root of the objects when we invoke this method
    __current_object_level = __is_recursive_iteration ? __current_object_level : data_object;
    __current_conversion_table_level = __is_recursive_iteration ? __current_conversion_table_level : conversion_table;

    
    if(typeof __current_object_level == "object")
    {
        // Make the iterate object
        var iterate_object = Object.keys(__current_object_level);
        //console.log('iter', iterate_object);

        iterate_object.map(function(key, index, array) {
            // Check to make sure this is part of the object itself
            if (__current_object_level.hasOwnProperty(key))
            {
                if(__current_conversion_table_level)
                {
                    var new_key = null;
                    if(!swap_conversion_table_key_value)
                    {
                        if(typeof __current_conversion_table_level[key] == "object")
                            new_key = __current_conversion_table_level[key]['_short'];
                        else
                            new_key = __current_conversion_table_level[key];
                    }
                    else
                    {
                        // We have to search through all of the current level to match the value to curernt object key since we swapped
                        var table_level_keys = Object.keys(__current_conversion_table_level);
                        for(var i = 0; i < table_level_keys.length; i++)
                        {
                            var curr_level_table_key = table_level_keys[i];

                            var key_to_compare = null;
                            var curr_level_table_value = __current_conversion_table_level[curr_level_table_key];
                            if(typeof curr_level_table_value == "object")
                                key_to_compare = curr_level_table_value['_short'];
                            else
                                key_to_compare = curr_level_table_value;

                            // If it is a match, we found it :)
                            if(key_to_compare == key)
                            {
                                // Now use the key from the conversion table instead of the value
                                new_key = curr_level_table_key;

                                // Break out of the for loop after we found it
                                break;
                            }
                        }
                    }
                
                    // If there is actually a new key, replace it in our object
                    if(new_key)
                    {
                        renameProperty(__current_object_level, key, new_key);
                    }

                    //console.log('key', key, new_key);

                    // Only keep going if there actually was a new_key
                    // Or there is a array to look through the items on
                    var is_current_key_array_index = key%1 == 0; // If the current key is a positive integer, we assume it is an array key
                    if(new_key || is_current_key_array_index)
                    {
                        // Use the new key if it was available
                        // Because that is what the object property is changed to from above
                        var value = __current_object_level[new_key ? new_key : key];

                        //console.log('current', value, __current_conversion_level);

                        // If we are swapping then the `key` will not be found in the table as it is ass-backwards.
                        var table_key = swap_conversion_table_key_value ? new_key : key;

                        // If the current key is a array, maintain the `_array_item` conversion level we set the level prior
                        // Otherwise continue down the tree
                        var next_conversion_level = is_current_key_array_index ? __current_conversion_table_level : __current_conversion_table_level[table_key];
                        if(typeof __current_conversion_table_level[table_key] == "object")
                        {
                            // If the current value is an array set up the conversion level for the items
                            if(Object.prototype.toString.call(value) === '[object Array]')
                            {
                                next_conversion_level = __current_conversion_table_level[table_key]['_array_item'];
                            }
                            else
                            {
                                next_conversion_level = __current_conversion_table_level[table_key]['_object'];
                            }
                        }
                        //console.log('next', next_conversion_level);
                        recursiveConvertKeys(data_object, conversion_table, swap_conversion_table_key_value, true, value, next_conversion_level);
                    }
                }
            }
        });
    }
}

The format for the conversion table is below. I am not set on this format so feel free to suggest something better for the table/map.

var conversion_table = {
    "psdf": "p", 
    "qwer": "q",
    "array": {
        "_short": "a"
    },
    "candidates": {
        "_short": " ",

        "_array_item": {
            "ip": "i",
            "port": "p"
        }
    }
}

And some accompanying test data:

var test_data = {
    "psdf": "pcodereview",
    "qwer": "qcodereview",
    "array": [
        "1",
        "2",
        "3"
    ],
    "candidates": [
        {
            "ip": "0.0.0.0",
            "port": 65000
        },
        {
            "ip": "127.0.0.1",
            "port": 65000
        }
    ]
}
MLM
  • 173
  • 9