Lodash-如何从平面数组创建树

时间:2018-12-17 16:06:37

标签: javascript json lodash

每次加载应用程序时,都会收到以下json

[
  {
    id: 'mALRRY93jASr',
    identifier: '100',
    text: 'Text A'
  },
  {
    id: '7S3xHZEdNcfV',
    identifier: '200',
    text: 'Text B'
  },
  {
    id: '2ZA5xSJeukU6',
    identifier: '300',
    text: 'Text C',
  },
  {
    id: 'bhg3GnLEvw2k',
    identifier: '300.100',
    text: 'Text C - A'
  },
  {
    id: 'bhg3GnLEvw2k',
    identifier: '300.100.100',
    text: 'Text C - A - A'
  },
  {
    id: '2AcXNr4HT388',
    identifier: '300.200',
    text: 'Text C - B'
  }
]

树级别由identifier属性标识。

这棵树可以有数千个孩子,所以它需要递归。

如何使用Lodash排列json使其看起来像下面的json

[
  {
    id: 'mALRRY93jASr',
    identifier: '100',
    text: 'Text A'
  },
  {
    id: '7S3xHZEdNcfV',
    identifier: '200',
    text: 'Text B'
  },
  {
    id: '2ZA5xSJeukU6',
    identifier: '300',
    text: 'Text C',
    children: [
      {
        id: 'bhg3GnLEvw2k',
        identifier: '300.100',
        text: 'Text C - A',
        children: [
          {
            id: 'bhg3GnLEvw2k',
            identifier: '300.100.100',
            text: 'Text C - A - A'
          }
        ]
      },
      {
        id: '2AcXNr4HT388',
        identifier: '300.200',
        text: 'Text C - B'
      }
    ]
  }
]

3 个答案:

答案 0 :(得分:1)

您可以采用迭代方法,在identifier的同一路径中查找对象并构建嵌套结构。

这种方法也适用于未排序的数据。

var data = [{ id: 'mALRRY93jASr', identifier: '100', text: 'Text A' }, { id: '7S3xHZEdNcfV', identifier: '200', text: 'Text B' }, { id: '2ZA5xSJeukU6', identifier: '300', text: 'Text C' }, { id: 'bhg3GnLEvw2k', identifier: '300.100', text: 'Text C - A' }, { id: 'bhg3GnLEvw2k', identifier: '300.100.100', text: 'Text C - A - A' }, { id: '2AcXNr4HT388', identifier: '300.200', text: 'Text C - B' }],
    tree = [];

data.reduce((r, o) => {
    o.identifier
        .split('.')
        .map((_, i, a) => a.slice(0, i + 1).join('.'))
        .reduce((q, identifier, i, { length }) => {
            var temp = (q.children = q.children || []).find(p => p.identifier === identifier);
            if (!temp) {
                q.children.push(temp = { identifier });
            }
            if (i + 1 === length) {
                Object.assign(temp, o);
            }
            return temp;
        }, r);
    return r;
}, { children: tree });

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:0)

我以previous answer of mine为基础。有很多相似之处,但是您的“路径”语法有点不同,因此我不得不调整一些解析。

const data = [
  {
    id: 'mALRRY93jASr',
    identifier: '100',
    text: 'Text A'
  },
  {
    id: '7S3xHZEdNcfV',
    identifier: '200',
    text: 'Text B'
  },
  {
    id: '2ZA5xSJeukU6',
    identifier: '300',
    text: 'Text C',
  },
  {
    id: 'bhg3GnLEvw2k',
    identifier: '300.100',
    text: 'Text C - A'
  },
  {
    id: 'bhg3GnLEvw2k',
    identifier: '300.100.100',
    text: 'Text C - A - A'
  },
  {
    id: '2AcXNr4HT388',
    identifier: '300.200',
    text: 'Text C - B'
  }
];

const pathPartRegex = /.*?\./g;
const tree = _.reduce(data, (result, value) => {
    // We use the . character as a "path part" terminator,
    // but it is not there at the end of the string, so we add it
    let identifier = value.identifier;
    if (!identifier.endsWith(".")) {
        identifier = identifier + ".";
    }
    const pathParts = identifier.match(pathPartRegex);
    let node = result;
    let path = "";

    // Go down through tree until last path part
    const notLastPart = pathParts.splice(0, pathParts.length - 1);
    for (const pathPart of notLastPart) {
        path += pathPart;
        const existingNode = node.children 
                                ? node.children.find(item => path.startsWith(item.identifier) )
                                : node.find(item => path.startsWith(item.identifier));
        if (existingNode) {
            node = existingNode
        } else {
            // If we need to traverse over a path that doesn't exist, just create it
            // See notes 
            const newNode = {
                identifier: path,
                children: []
            };

            // The root element is just an array, and doesn't have a children property
            if (node.children) {
                node.children.push(newNode);
            } else {
                node.push(newNode);
            }
            node = newNode;
        }
    }

    // Add new node
    const newNode = {
        id: value.id,
        text: value.text,
        identifier: value.identifier,
        children: []
    };

    // The root element is just an array, and doesn't have a children property
    if (node.children) {
        node.children.push(newNode);
    } else {
        node.push(newNode);
    }

    return result;
}, []);

通过RunKit(https://npm.runkit.com/lodash)进行了测试


注意:

与原始答案相同的警告也适用于此。

答案 2 :(得分:0)

您可以使用Array.reduce()_.setWith()通过路径(身份)创建对象树。然后,您可以对_.transform()使用递归函数,并使用_.values()select order_id, a, b, c from summary where a > 0 and (a + b + c + d > 2 * b) 转换为数组:

children
const createTree = (arr) => {
  // reduce to a tree of objects
  const oTree = arr.reduce((r, o) => {
    const key = o.identifier.replace(/\./g, '.children.');

    // creates the path and adds the object value
    return _.setWith(r, key, o, Object)
  }, {});
  
  // transforms the children to an array recursivly
  const transformChildren = (tree) =>    
    _.transform(tree, (acc, v, k) => {
      const value = _.isObject(v) ? transformChildren(v) : v;

      acc[k] = _.eq(k, 'children') ? _.values(value) : value;
    });
    
  return transformChildren(_.values(oTree));
};

const data = [{"id":"mALRRY93jASr","identifier":"100","text":"Text A"},{"id":"7S3xHZEdNcfV","identifier":"200","text":"Text B"},{"id":"2ZA5xSJeukU6","identifier":"300","text":"Text C"},{"id":"bhg3GnLEvw2k","identifier":"300.100","text":"Text C - A"},{"id":"bhg3GnLEvw2k","identifier":"300.100.100","text":"Text C - A - A"},{"id":"2AcXNr4HT388","identifier":"300.200","text":"Text C - B"}];

const result = createTree(data);

console.log(result);