简体   繁体   English

"将平面对象数组转换为嵌套对象"

[英]Convert an array of flat objects to nested objects

I have the following array, which is flattened, so I want to convert it to the nested array.我有以下数组,它是展平的,所以我想将它转换为嵌套数组。

const data = [{
    "countryId": 9,
    "countryName": "Angola",
    "customerId": 516,
    "customerName": "CHEVRON (SASBU) - ANGOLA",
    "fieldId": 2000006854,
    "fieldName": "A12"
},
{
    "countryId": 9,
    "countryName": "Angola",
    "customerId": 516,
    "customerName": "CHEVRON (SASBU) - ANGOLA",
    "fieldId": 5037,
    "fieldName": "BANZALA"
}
]

I would like to have the following interface我想要如下界面

interface Option {
    value: number,
    viewText: string,
    ids?: Set<number>,
    children?: Option[]
}

So the output should look like this所以输出应该是这样的

const output = [{
    "value": 9,
    "viewText": "Angola",
    "ids": [516],
    "children": [{
        "value": 516,
        "viewText": "CHEVRON (SASBU) - ANGOLA",
        "ids": [2000006854],
        "children": [{
            "viewText": "A12",
            "value": 2000006854
        }]
    }]
}]

I have been struggling with the solution, I don't know how to approach this, do I need to use a recursive function or something else, any ideas?我一直在努力解决这个问题,我不知道如何解决这个问题,我是否需要使用递归函数或其他东西,有什么想法吗?

Update:更新:

Here is my naive solution.这是我天真的解决方案。 The problem with this approach is that I'm manually handling nested structures.这种方法的问题是我手动处理嵌套结构。

function mapFlatToNested(data: typeof dataset) {
    const map = new Map<number, Option>()

    for (const item of data) {
        const itemFromMap = map.get(item.countryId);

        if (!itemFromMap) {
            map.set(item.countryId, {
                viewText: item.countryName,
                value: item.countryId,
                ids: new Set([item.customerId]),
                childrens: [{
                    viewText: item.customerName,
                    value: item.customerId,
                    ids: new Set([item.fieldId]),
                    childrens: [{
                        value: item.fieldId,
                        viewText: item.fieldName
                    }]
                }]
            })
        } else {
            if (!itemFromMap?.ids?.has(item.customerId)) {

                itemFromMap?.ids?.add(item.customerId);
                itemFromMap?.childrens?.push({
                    value: item.customerId,
                    viewText: item.customerName,
                    ids: new Set([item.fieldId]),
                    childrens: [{
                        value: item.fieldId,
                        viewText: item.fieldName
                    }]
                })

            } else {
                const customer = itemFromMap?.childrens?.find((customer) => customer.value === item.customerId);

                if (customer) {
                    if (!customer.ids?.has(item.fieldId)) {
                        customer.ids?.add(item.fieldId)
                        customer.childrens?.push({
                            value: item.fieldId,
                            viewText: item.fieldName,
                            ids: new Set([customer.value])
                        })
                    }
                }
            }

        }
    }
    return map
}

console.log(mapFlatToNested(dataset));

Here is the typescript playground 这是打字稿游乐场

You need to map your array:您需要映射您的数组:

data.map(cur=>({
  value:cur.countryId,
    viewText:cur.countryName,
    ids:[cur.customerId],
    children:[{
        value:cur.customerId,
        viewText:cur.customerName,
        ids:[cur.fieldId],
        children:[{
            viewText:cur.fieldName,
            value:cur.fieldId
        }]
    }]
}))

Update<\/strong> : Added the generic version.更新<\/strong>:添加了通用版本。 It seems nicer overall, although not as targeted to the original problem.总体上看起来更好,尽管没有针对原始问题。 The original answer is below.原答案如下。

Generic Version通用版<\/h2>

A more generic version of this idea would allow you to choose what input fields are keys, how they are transformed into output fields, and then what to do with the children.这个想法的一个更通用的版本将允许您选择哪些输入字段是键,它们如何转换为输出字段,然后如何处理子项。

Here is one implementation:这是一种实现:

 const omit = (names) => (o) => Object .fromEntries (Object .entries (o) .filter (([k, v]) => !names .includes (k))) const group = (fn) => (xs) => Object .values (xs .reduce ( (a, x, _, __, k = fn (x)) => ((a[k] = [... (a[k] || []), x]), a), {} )) const nesting = (fields, descend) => (xs) => { const select = Object .entries (fields) const outers = select .map (([_, v]) => v) const descendents = Object .entries (descend) const makeKey = (x) => outers .map ((v) => x [v]) .join ('\') return group (makeKey) (xs) .map ((group) => Object .assign ( ... select .map (([k, v]) => ({[k]: group [0] [v]})), ... descendents .map (([k, v]) => ({[k]: v (group .map (omit (outers)))})) )) } const transform = nesting ({ value: 'countryId', viewText: 'countryName' }, { ids: (xs) => xs .map (x => x.customerId), children: nesting ({ value: 'customerId', viewText: 'customerName', }, { ids: (xs) => xs .map (x => x.fieldId), \/\/ or: `children: (xs) => xs` to get all remaining fields intact. children: nesting ({ value: 'fieldId', viewText: 'fieldName' }, {}) }) }) const dataset = [{countryId: 59, countryName: "Algeria", customerId: 6959, customerName: "ABERDEEN DRILLING SCHOOL LTD", fieldId: 8627, fieldName: " BAGAN"}, {countryId: 59, countryName: "Algeria", customerId: 2730, customerName: "ABU DHABI COMPANY FOR ONSHORE OIL OPERATIONS", fieldId: 6158, fieldName: "BAB"}, {countryId: 59, countryName: "Algeria", customerId: 3457, customerName: "AGIP - ALGIERS", fieldId: 9562, fieldName: "LESS"}, {countryId: 9, countryName: "Angola", customerId: 516, customerName: "CHEVRON (SASBU) - ANGOLA", fieldId: 2000006854, fieldName: "A12"}, {countryId: 9, countryName: "Angola", customerId: 516, customerName: "CHEVRON (SASBU) - ANGOLA", fieldId: 5037, fieldName: "BANZALA"}] console .log (transform (dataset))<\/code><\/pre>
 .as-console-wrapper {max-height: 100% !important; top: 0}<\/code><\/pre>

We have two generic helper functions.我们有两个通用的辅助函数。 omit<\/code> takes a list of keys and returns a function that takes an object and returns a clone that does not include those keys. omit<\/code>接受一个键列表并返回一个函数,该函数接受一个对象并返回一个不包含这些键的克隆。 group<\/code> takes a key-generation function and returns a function that takes an array of values and groups them into arrays by the value returned by that key. group<\/code>接受一个键生成函数并返回一个函数,该函数接受一个值数组并按该键返回的值将它们分组到数组中。

Our main function is nesting<\/code> , and it takes two parameters:我们的主要功能是nesting<\/code> ,它有两个参数:

  • An object describing the fields that are used for grouping.描述用于分组的字段的对象。 Its keys become the fields in the output, and its values are the properties from the input object;它的键成为输出中的字段,它的值是输入对象的属性; they are combined together to create the grouping key.它们组合在一起以创建分组键。 The output value is taken from the first element of the group.输出值取自组的第一个元素。

    <\/li>

  • An object describing other fields that will be added.描述将要添加的其他字段的对象。 Its keys again are the fields in the output, but its values are functions from the group to some output field.它的键同样是输出中的字段,但它的值是从组到某个输出字段的函数。 Here is where we handle recursion, simply making another call to nested<\/code> .这是我们处理递归的地方,只需再次调用nested<\/code> 。 But we can also do other things, such as pulling out all the child id<\/code> into a single field as done here.但我们也可以做其他事情,例如将所有子id<\/code>提取到单个字段中,就像这里所做的那样。

    <\/li><\/ul>

    In this version, the omission of outer nodes is important, because most uses I've seen for something like this would involve grouping\/nesting on a few levels, and then returning the remaining values intact.在这个版本中,外部节点的省略很重要,因为我见过的大多数类似这样的用途都涉及在几个级别上进行分组\/嵌套,然后完整地返回剩余的值。 We could do that by replacing our innermost children<\/code> with an identity function.我们可以通过用一个身份函数替换我们最里面的children<\/code>来做到这一点。

    Original Version原版<\/h2>

    This version lets you configure your function like this:此版本允许您像这样配置您的功能:

     const transform = nestedGroup ('country', 'customer', 'field') transform (dataset)<\/code><\/pre>

    It's not particularly generic, embedding 'value'<\/code> and 'viewText'<\/code> inside the code, and making the assumption that the original fields look like <something>Id<\/code> and <something>Name<\/code> .它不是特别通用,在代码中嵌入'value'<\/code>和'viewText'<\/code> ,并假设原始字段看起来像<something>Id<\/code>和<something>Name<\/code> 。 But it does the job, and subject to those restrictions, is configurable.但它完成了这项工作,并且受到这些限制,是可配置的。

     const nestedGroup = (level, ...levels) => (xs, id = level + 'Id', name = level + 'Name') => level == undefined ? xs : Object .values (xs .reduce ( (a, x, _, __, k = x [id] + '~' + x [name]) => ((a [k] = [... (a [k] || []), x]), a), {} )) .map (xs => ({ value: xs [0] [id], viewText: xs [0] [name], ... (levels .length > 0 ? { ids: xs .map (x => x [`${levels [0]}Id`]), children: nestedGroup (...levels)(xs) } : {}), })) const transform = nestedGroup ('country', 'customer', 'field') const dataset = [{countryId: 59, countryName: "Algeria", customerId: 6959, customerName: "ABERDEEN DRILLING SCHOOL LTD", fieldId: 8627, fieldName: " BAGAN"}, {countryId: 59, countryName: "Algeria", customerId: 2730, customerName: "ABU DHABI COMPANY FOR ONSHORE OIL OPERATIONS", fieldId: 6158, fieldName: "BAB"}, {countryId: 59, countryName: "Algeria", customerId: 3457, customerName: "AGIP - ALGIERS", fieldId: 9562, fieldName: "LESS"}, {countryId: 9, countryName: "Angola", customerId: 516, customerName: "CHEVRON (SASBU) - ANGOLA", fieldId: 2000006854, fieldName: "A12"}, {countryId: 9, countryName: "Angola", customerId: 516, customerName: "CHEVRON (SASBU) - ANGOLA", fieldId: 5037, fieldName: "BANZALA"}] console .log (transform (dataset))<\/code><\/pre>
     .as-console-wrapper {max-height: 100% !important; top: 0}<\/code><\/pre>

    We start by creating, id<\/code> and name<\/code> values.我们首先创建id<\/code>和name<\/code>值。 At the first level they will be countryId<\/code> and countryName<\/code> , then customerId<\/code> and customerName<\/code> , and so on.在第一级,它们将是countryId<\/code>和countryName<\/code> ,然后是customerId<\/code>和customerName<\/code> ,依此类推。

    Then, if we're at the bottom layer (no more levels to nest) we simply return our input.然后,如果我们在底层(没有更多的层次可以嵌套),我们只需返回我们的输入。 Otherwise, we group together those values with the same [id]<\/code> and [name]<\/code> values, then for each group, we extract the common fields, as well as (if there's more nesting to do, the ids<\/code> and, recursively, the children<\/code> .否则,我们将具有相同[id]<\/code>和[name]<\/code>值的这些值组合在一起,然后对于每个组,我们提取公共字段,以及(如果有更多嵌套要做, ids<\/code>和递归的children<\/code> 。

    It would be interesting to make this much more generic.让这个更通用会很有趣。 I think it would be interesting to try to come up with a function that took this sort of configuration:我认为尝试提出一个采用这种配置的函数会很有趣:

     const transform = nestedGroup ('children', [ {value: 'countryId', viewText: 'countryName', ['ids?']: (xs) => xs .map (x => x.id)}, {value: 'customerId', viewText: 'customerName', ['ids?']: (xs) => xs .map (x => x.id)}, {value: 'fieldId', viewText: 'fieldName', ['ids?']: (xs) => xs .map (x => x.id)}, ])<\/code><\/pre>

    where the '?'<\/code> '?'<\/code> on ids<\/code> marks it as optional: used only if there are further nested levels.ids<\/code>上将其标记为可选:仅在有更多嵌套级别时使用。 we could then write your transformation as an easy gloss that accepts ['country', 'customer', 'field']<\/code> and generates the above.然后,我们可以将您的转换写成一个简单的注释,接受['country', 'customer', 'field']<\/code>并生成上述内容。 This would let us group on as many fields as we like and then rename them as we chose.这将使我们可以根据需要对任意数量的字段进行分组,然后根据我们的选择重命名它们。 The first argument, , 'children'<\/code> , is just to allow us to name descendent nodes.第一个参数'children'<\/code>只是允许我们命名后代节点。 It would probably be better to include that per-level as well, but I don't have that quite figured out.将每个级别也包括在内可能会更好,但我还没有完全弄清楚。

    I think that would be an interesting and useful function, but I haven't worked out the details yet.我认为这将是一个有趣且有用的功能,但我还没有弄清楚细节。 Perhaps I'll come back to it soon.也许我很快就会回来。

    "

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM