递归函数扩展json关系JS

问题描述 投票:0回答:1

我被这个问题卡住了。

我有这个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传给我的终端,我需要打开一个级别的关系。
  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个回答
1
投票

我不太确定这是否适合上面的代码库,但我认为它解决了你正在寻找的问题。

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)

最后,我们给我们的公共API fillRelations它通过将输入的字符串分割成一个数组,并将我们的初始对象作为查找列表和要展开的项目传入,从而启动了这个过程。 这就是你想要的签名。

fillRelations('superdepartment.superdepartment', departments)

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


2
投票

这里有一个直接的(和递归的)解决方案来解决你的问题。

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'));
© www.soinside.com 2019 - 2024. All rights reserved.