I have implemented a recursive function that converts an object's 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 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:
Code available as a GitHub Gist
function recursiveConvertKeys(data_object, conversion_table, swap_conversion_table_key_value, __is_recursive_iteration, __current_object_level, __current_conversion_table_level)
{
// Do not pass in parameters for the double underscore arguments. These are private and only used for self recursive calling
//
// Demo: http://jsfiddle.net/MadLittleMods/g3g0g1L4/
// GitHub Gist: https://gist.github.com/MadLittleMods/7b9ec36879fd24938ad2
// Code Review: http://codereview.stackexchange.com/q/69651/40165
//
/* 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);
*/
// 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",
"dict": {
"_short": "d",
"_object": {
"one": "o",
"two": "t",
"three": "r"
}
},
"candidates": {
"_short": "c",
"_array_item": {
"ip": "i",
"port": "p"
}
}
}
And some accompanying test data:
var test_data = {
"psdf": "pcodereview",
"qwer": "qcodereview",
"dict": {
"one": "1",
"two": "2",
"three": "3"
},
"candidates": [
{
"ip": "0.0.0.0",
"port": 65000
},
{
"ip": "127.0.0.1",
"port": 65000
}
]
}