【问题标题】:Recursive function to expand json relations JS扩展json关系JS的递归函数
【发布时间】:2020-10-05 04:26:27
【问题描述】:

我有点被这个问题困扰:

我有这个 JSON:

  [
    {
        "id": 1,
        "name": "Sales",
        "superdepartment": null
    },
    {
        "id": 2,
        "name": "Engineering",
        "superdepartment": null
    },
    {
        "id": 3,
        "name": "Product",
        "superdepartment": null
    },
    {
        "id": 4,
        "name": "Design",
        "superdepartment": 3
    },
    {
        "id": 5,
        "name": "Inbound Sales",
        "superdepartment": 1
    },
    {
        "id": 6,
        "name": "Outbound Sales",
        "superdepartment": 1
    },
    {
        "id": 7,
        "name": "Application Security",
        "superdepartment": 2
    },
    {
        "id": 8,
        "name": "Front-End",
        "superdepartment": 2
    },
    {
        "id": 9,
        "name": "Sales Development",
        "superdepartment": 6
    },
    {
        "id": 10,
        "name": "Product Management",
        "superdepartment": 3
    }
]

因此,我需要根据所需的级别递归地扩展“超级部门”关系。例如:

  1. 如果我将 ?expand=superdeparment 传递给我的端点,我需要打开 1 级关系
  2. 如果我通过 ?expand=superdepartment.superdepartment 我需要打开 2 个级别,并且可以继续进行,所以我认为我需要一个递归解决方案。

实际上,我有满足第一级的这段代码,但是我在替换嵌套对象以打开第二级关系时遇到了几个问题。

departments.js --> 这里我获取数据(json)并调用“getRelations”方法。

module.exports.getAll = async function getAll(expand = null) {
    let response = await data;

    if (expand) {
        response = modelUtils.getRelations(response, expand, response);
    }

    return response;
}

modelUtils.js --> 在这里我编写了我的核心函数来实现嵌套对象:

const _ = require('lodash');

//targetEntity is de JSON that I will use to get the nested entities from my actual ID.
// In this case is the same json, but can be another different.
module.exports.getRelations = function getRelations(entity, expand, targetEntity) {
    let tmpEntity = _.cloneDeep(entity);
    let path = expand.split('.');

    for (let i=0; i < entity.length; i++) {
        tmpEntity[i] = fillRelations(entity[i], path, targetEntity);
    }

    return tmpEntity;
}

function fillRelations(entity, path, targetEntity, level = 0) {
    let current = _.cloneDeep(entity);
    const currentPath = path[level];

    if (!current[currentPath]) {
        return current;
    }

    let value = targetEntity.filter(target => target.id === current[currentPath]);
    if (value.length > 0) {
        current[currentPath] = value[0];
    }

    level++;
    return fillRelations(current, path, targetEntity, level);
}

所以实际上使用这段代码并将 ?expand=superdepartment.superdepartment 传递到我的端点,我得到了这个 JSON 响应:

[
    {
        "id": 1,
        "name": "Sales",
        "superdepartment": null
    },
    {
        "id": 2,
        "name": "Engineering",
        "superdepartment": null
    },
    {
        "id": 3,
        "name": "Product",
        "superdepartment": null
    },
    {
        "id": 4,
        "name": "Design",
        "superdepartment": {
            "id": 3,
            "name": "Product",
            "superdepartment": null
        }
    },
    {
        "id": 5,
        "name": "Inbound Sales",
        "superdepartment": {
            "id": 1,
            "name": "Sales",
            "superdepartment": null
        }
    },
    {
        "id": 6,
        "name": "Outbound Sales",
        "superdepartment": {
            "id": 1,
            "name": "Sales",
            "superdepartment": null
        }
    },
    {
        "id": 7,
        "name": "Application Security",
        "superdepartment": {
            "id": 2,
            "name": "Engineering",
            "superdepartment": null
        }
    },
    {
        "id": 8,
        "name": "Front-End",
        "superdepartment": {
            "id": 2,
            "name": "Engineering",
            "superdepartment": null
        }
    },
    {
        "id": 9,
        "name": "Sales Development",
        "superdepartment": {
            "id": 6,
            "name": "Outbound Sales",
            "superdepartment": 1
        }
    },
    {
        "id": 10,
        "name": "Product Management",
        "superdepartment": {
            "id": 3,
            "name": "Product",
            "superdepartment": null
        }
    }
]

如你所见,ID=9 的元素需要为 id=1 打开第二层嵌套关系,所以它必须是这样的:

{
    "id": 9,
    "name": "Sales Development",
    "superdepartment": {
        "id": 6,
        "name": "Outbound Sales",
        "superdepartment":  {
           "id": 1,
           "name": "Sales",
           "superdepartment": null
         }
    }
},

【问题讨论】:

    标签: javascript node.js rest recursion expand


    【解决方案1】:

    以下是解决问题的直接(递归)解决方案:

    const data = [{
            "id": 1,
            "name": "Sales",
            "superdepartment": null
        }, {
            "id": 2,
            "name": "Engineering",
            "superdepartment": null
        }, {
            "id": 3,
            "name": "Product",
            "superdepartment": null
        }, {
            "id": 4,
            "name": "Design",
            "superdepartment": 3
        }, {
            "id": 5,
            "name": "Inbound Sales",
            "superdepartment": 1
        }, {
            "id": 6,
            "name": "Outbound Sales",
            "superdepartment": 1
        }, {
            "id": 7,
            "name": "Application Security",
            "superdepartment": 2
        }, {
            "id": 8,
            "name": "Front-End",
            "superdepartment": 2
        }, {
            "id": 9,
            "name": "Sales Development",
            "superdepartment": 6
        }, {
            "id": 10,
            "name": "Product Management",
            "superdepartment": 3
        }
    ];
    
    function compute(data, expand) {
        const path = expand.split('.');
        return data.map(x => attachEntities(x, data, path));
    }
    
    function attachEntities(obj, data, [prop, ...props]) {
        return prop ? {
            ...obj,
            [prop]: obj[prop] && attachEntities(data.find(y => y.id === obj[prop]) || obj[prop], data, props)
        }
         : obj;
    }
    
    console.log('One level', compute(data, 'superdepartment'));
    console.log('Two levels', compute(data, 'superdepartment.superdepartment'));
    console.log('Three levels', compute(data, 'superdepartment.superdepartment.superdepartment'));

    【讨论】:

    • 这很好,比我的更简单,也很容易理解。一种建议是将path 在参数中或在return 之前解构为[node, ...nodes] 或类似的结构,以避免path[0] 到处重复。
    • 确实我按照你的建议做了,而且更清楚了,谢谢
    【解决方案2】:

    我不太确定这如何适合上面的代码库,但我认为它解决了您正在寻找的问题:

    const expand = (field, lookups, xs) => 
      xs.map (x => x[field] == null ? x : {...x, [field]: lookups.find(({id}) => id == x[field])})
      
    const expandAll = ([field, ...fields], lookups, xs) =>  
      field == undefined
        ? xs
      : fields .length > 0
        ? expandAll (fields, expand (field, lookups, xs), xs)
      : // else
        expand (field, lookups, xs)
    
    const fillRelations = (expansionStr, xs) => 
      expandAll (expansionStr .split ('.'), xs, xs)
    
    const departments = [{ id: 1, name: "Sales", superdepartment: null }, { id: 2, name: "Engineering", superdepartment: null }, { id: 3, name: "Product", superdepartment: null }, { id: 4, name: "Design", superdepartment: 3 }, { id: 5, name: "Inbound Sales", superdepartment: 1 }, { id: 6, name: "Outbound Sales", superdepartment: 1 }, { id: 7, name: "Application Security", superdepartment: 2 }, { id: 8, name: "Front-End", superdepartment: 2 }, { id: 9, name: "Sales Development", superdepartment: 6 }, { id: 10, name: "Product Management", superdepartment: 3}]
    
    console .log (
      JSON.stringify (
        fillRelations ('superdepartment.superdepartment', departments)
      , null, 4)
    )
    .as-console-wrapper {min-height: 100% !important; top: 0}

    我们定义expand,它接受一个字段名称、一个已展开的项目数组和一个要查找的项目数组,并通过在展开的列表 (lookups) 中查找来更新最后一个列表匹配给定的字段。我们可以这样使用:

    expand('superdepartment', departments, departments)
    

    然后我们使用expandAll 在此基础上进行构建,它接受一个字段名称数组并递归调用自身和expand 来填写缺失字段的详细信息。我们可以这样使用:

    expandAll(['superdepartment', 'superdepartment'], departments, departments)
    

    最后,我们在fillRelations 中提供了我们的公共 API,它通过将输入字符串拆分为一个数组并将我们的初始对象作为查找列表和要扩展的项目传入来启动该过程。这有你想要的签名:

    fillRelations('superdepartment.superdepartment', departments)
    

    我们添加了JSON.stringify 调用以跳过 SO 控制台的引用字符串化。但请注意,例如,第一个结果和第六个结果的superdepartement 属性和第九个结果的superdeparment.superdepartment 属性都引用同一个对象。我看到您正在代码中进行一些克隆,如果您不想要这些共享引用,您可以更新 expand 以返回您的对象的克隆

    【讨论】:

    • 谢谢你,Scott,我很快测试了它并且工作正常。它只需要一些修改以适合我的代码,但总的来说它工作得很好!
    猜你喜欢
    • 2020-11-09
    • 2013-12-02
    • 2019-10-18
    • 2023-04-06
    • 2015-11-27
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 1970-01-01
    相关资源
    最近更新 更多