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
CodePudding user response:
This version lets you configure your function like this:
const transform = nestedGroup ('country', 'customer', 'field')
transform (dataset)
It's not particularly generic, embedding 'value' and 'viewText' inside the code, and making the assumption that the original fields look like <something>Id and <something>Name. 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))
.as-console-wrapper {max-height: 100% !important; top: 0}
We start by creating, id and name values. At the first level they will be countryId and countryName, then customerId and customerName, and so on.
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] and [name] values, then for each group, we extract the common fields, as well as (if there's more nesting to do, the ids and, recursively, the children.
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)},
])
where the '?' on ids marks it as optional: used only if there are further nested levels. we could then write your transformation as an easy gloss that accepts ['country', 'customer', 'field'] and generates the above. This would let us group on as many fields as we like and then rename them as we chose. The first argument, , 'children', is just to allow us to name descendent nodes. 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.
CodePudding user response:
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
}]
}]
}))
