0

JSFiddle: http://jsfiddle.net/3WdzL/1/

I need to convert locale JS object files to flattened versions and back again:

Orig locale object:

var localeObj = {
    toolbar: {
        link: {
            back: 'Back',
            menu: 'Menu',
        },
        flatTest: 'something'
    },
    countries: [
        ["AF", "Afghanistan"],
        ["AX", "Åland Islands"],
        ['nested', [1, 2, 3, 4]],
        ["AL", "Albania"]
    ]
};

Using the following function:

function flattenObj(obj) {
    var flattenedObj = {};

    var walk = function(obj, stringMap) {

        for(k in obj) {
            var computedKey = stringMap + (stringMap ? '.' + k : k);

            if(typeof obj[k] !== 'object') {
                flattenedObj[computedKey] = obj[k];
            } else {
                walk(obj[k], computedKey);
            }
        }
    };

    walk(obj, '');
    return flattenedObj;
}

Would produce a flattened object:

{
    toolbar.link.back: Back
    toolbar.link.menu: Menu
    toolbar.flatTest: something
    countries.0.0: AF
    countries.0.1: Afghanistan
    countries.1.0: AX
    countries.1.1: Åland Islands
    countries.2.0: nested
    countries.2.1.0: 1
    countries.2.1.1: 2
    countries.2.1.2: 3
    countries.2.1.3: 4
    countries.3.0: AL
    countries.3.1: Albania 
}

Converting back with the following func works fine for objects:

function deepenObj(obj) {
  var deepenedObj = {}, tmp, parts, part;

  for (var k in obj) {
    tmp = deepenedObj;
    parts = k.split('.');

    var computedKey = parts.pop();

    while (parts.length) {
      part = parts.shift();
      tmp = tmp[part] = tmp[part] || {};
    }

    tmp[computedKey] = obj[k];
  }

  return deepenedObj;
}

But produces a structure like this for the arrays:

region: {
    country: {
        0: {
            0: 'AF',
            1: 'Afghanistan'
        },
        ...
        2: {
            0: 'nested',
            1: {
                0: 1,
                1: 2,
                3: 4,
                4: 5
            }
        }
    }
}

Obviously this isn't the desired results for the arrays and I haven't been able to come up with a safe, elegant or even working solution yet. PS I am happy to save the arrays to strings differently if it makes converting back easier. Thanks!

1
  • 2
    You could test whether the key is numeric and then decide to create an array instead. This would not allow numeric keys in properties though. Commented Jun 10, 2013 at 14:37

2 Answers 2

2

You should either keep track if an object is actually an array:

var walk = function(obj, stringMap) {
    if (Array.isArray(obj) {
        for (var k = 0; k < obj.length; k++)
            var computedKey = stringMap ? stringMap + ',' + k : k;
    } else {
        for (var k in obj) {
            var computedKey = stringMap ? stringMap + '.' + k : k;
        ...

Then, when deepening:

for (var k in obj) {
    tmp = deepenedObj;
    parts = ["."].concat(k.split(/([\.,])/));

    var computedKey = parts.pop(), sign;

    while (parts.length) {
        sign = parts.shift();
        part = !parts.length ? computedKey : parts.shift();
        tmp = tmp[part] = tmp[part] || (sign === "," ? [] : {});
    }

    tmp[computedKey] = obj[k];
}

Note that Array.isArray could be undefined. You can use obj instanceof Array instead.

This solution works if localeObj is an object literal and not an array, because the first point/comma isn't saved in the computed key. You can modify the function if you need to.

The trick here is to use an unusual behaviour of split that pushes captured groups in the splitted array when used with regular expressions, so before every key part there's the proper separator.

Sign up to request clarification or add additional context in comments.

2 Comments

Very nice good idea, thanks for your time. The initial conversion looks ok (commas for arrays) but something is going wrong when converting it back into an array I'll try and find out why: jsfiddle.net/3EFuA
@infensus Yeah, right, I didn't have the chance to test my answer :) Modified accordingly.
0

Use JSON.stringify() and JSON.parse():

var flattenedObj = JSON.stringify(localeObj);

vat deepenedObj = JSON.parse(flattenedObj);

Demo

1 Comment

The problem with this is that I need: 'toolbar.link.back' not "toolbar": {"link": "back"}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.