Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lodash to flatten JSON and show original path

I have a JSON object with nested children that I would like to flatten and modify using lodash. Ideally the revised JSON will have a value for the original level the nested children were at and show their original path.

Here is sample JSON:

var data = [
    {id: 0, name: 'Australia', children: [
        {id: 10, name: 'Melbourne', children: []},
        {id: 11, name: 'Sydney', children: [
            {id: 100, name: 'Surry Hills', children: []},
            {id: 102, name: 'Darlinghurst', children: []}
        ]},
        {id: 13, name: 'Kambalda', children: []}
    ]},
    {id: 1, name: 'Spain', children: [
        {id: 20, name: 'Barcelona', children: []},
        {id: 21, name: 'Madrid', children: []}
    ]},
    {id: 3, name: 'UK', children: [
        {id: 30, name: 'London', children: [
            {id: 302, name: 'Knightsbridge', children: []},
            {id: 309, name: 'West End', children: []}
        ]},
        {id: 33, name: 'Leeds', children: []},
        {id: 35, name: 'Manchester', children: []}
    ]}
];

And the transformed JSON I would like to generate is:

 [
    {id: 0, name: 'Australia', level: 0, pathname: 'Australia'},
    {id: 10, name: 'Melbourne', level: 1, pathname: 'Australia > Melbourne'},
    {id: 11, name: 'Sydney', level: 1, pathname: 'Australia > Sydney'},
    {id: 100, name: 'Surry Hills', level: 2, pathname: 'Australia > Sydney > Surry Hills'},
    {id: 102, name: 'Darlinghurst', level: 2, pathname: 'Australia > Sydney > Darlinghurst'},
    {id: 13, name: 'Kambalda', level: 1, pathname: 'Australia > Kambalda'},
    {id: 1, name: 'Spain', level: 0, pathname: 'Spain'},
    {id: 20, name: 'Barcelona', level: 1, pathname: 'Spain > Barcelona'},
    {id: 21, name: 'Madrid', level: 1, pathname: 'Spain > Madrid'},
    {id: 3, name: 'UK', level: 0, pathname: 'UK'},
    {id: 30, name: 'London', level: 1, pathname: 'UK > London'},
    {id: 302, name: 'Knightsbridge', level: 2, pathname: 'UK > London > Knightsbridge'},
    {id: 309, name: 'West End', level: 2, pathname: 'UK > London > West End'},
    {id: 33, name: 'Leeds', level: 1, pathname: 'UK > Leeds'},
    {id: 35, name: 'Manchester', level: 1, pathname: 'UK > Manchester'}
]

I have been playing with _.chain, _.flatten and _.pluck and been unable to get anything close.

like image 729
lukemcd Avatar asked Feb 10 '23 06:02

lukemcd


2 Answers

You can use a simple recursive helper function that would produce an array of arrays, and then use _.flattenDeep to flatten it. This does what you want:

function flattenMyTree(tree) {
    function recurse(nodes, path) {
        return _.map(nodes, function(node) {
            var newPath = _.union(path, [node.name]);
            return [
                _.assign({pathname: newPath.join(' > '), level: path.length}, _.omit(node, 'children')),
                recurse(node.children, newPath)
            ];
        });
    }
    return _.flattenDeep(recurse(tree, []));
}
like image 143
mik01aj Avatar answered Feb 11 '23 23:02

mik01aj


This one gives you the expected answer but it's not in ideal functional style; The idea is to start flattening the array from outside. On each iteration of the while() loop the array is flattened by one level. Items of that level are appended to the result and children of those items are appended to the children array. In next iteration we flatten those children. When there are no more items in the children array it means that we finished processing the last level. Finally we sort items by pathname which effectively groups parents and their children together.

var current = data;
var level = 0;
var result = [];
var children = [];

while(current.length > 0) {
    result = result.concat(_.map(current, function(item) {
        if (!_.isArray(item.path)) {
            item.path = [];
        }
        item.path.push(item.name);
        children = children.concat(_.map(item.children, function(child) {
            child.path = item.path.slice();
            return child;
        }));

        item.level = level;
        item.pathname = item.path.join(" > ");

        delete item.path;
        delete item.children;

        return item;
    }));

    current = children;
    children = [];
    level++;
}

result.sort(function(a, b) {
    return a.pathname.localeCompare(b.pathname);
});

console.log(result);
like image 31
Matjaž Drolc Avatar answered Feb 12 '23 01:02

Matjaž Drolc