递归函数扩展json关系JS

Recursive function to expand json relations JS

我有点被这个问题困住了:

我有这个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
    }
]

所以,我需要根据需要的层次递归展开"superdepartment"关系。例如:

  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
         }
    }
},

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

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 中给出 public API,它通过将输入字符串拆分为数组并将我们的初始对象作为查找列表和要展开的项目。这有你想要的签名:

fillRelations('superdepartment.superdepartment', departments)

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

这是针对您的问题的直接(递归)解决方案:

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'));