Convert js Array to Dictionary/Hashmap - javascript

I'm trying to convert an array of objects into a hashmap. I only have some parts of ES6 available and I cannot use Map as well.
The objects in the array are quite simple, e.g. {nation: {name: string, iso: string, scoringPoints: number}. I need to sort them by scoringPoints.
I want now a "dictionary" holding the rank by iso -> {[iso:string]:number}.
I already tried (from here (SO))
const dict = sortedData.reduce((prev, curr, index, array) => (
{ ...array, [curr.nation.iso]: ++index }
), {});
But dict turns out to be an Object with indices starting with 0. Hopefully there is just a small thing I don't see. But currently my head is spinning how to convert a simple array into a hashmap-like object.
Maybe Array.map?
I should also note that I am using TypeScript which I also had some trouble before when not typed correctly.
const test = [
{ nation: { name: "Germany", iso: "DE", rankingPoints: 293949 } },
{ nation: { name: "Hungary", iso: "HU", rankingPoints: 564161 } },
{ nation: { name: "Serbia", iso: "SR", rankingPoints: 231651 } }
];
const sorted = test.sort((a, b) => a.nation.rankingPoints - b.nation.rankingPoints);
const dict = sorted.reduce((prev, curr, index, array) => ({ ...array, [curr.nation.iso]: ++index }), {});
console.log(JSON.stringify(dict));
is showing
{
"0": {
"nation": {
"name": "Serbia",
"iso": "RS",
"rankingPoints": 231651
}
},
"1": {
"nation": {
"name": "Germany",
"iso": "DE",
"rankingPoints": 293949
}
},
"2": {
"nation": {
"name": "Hungary",
"iso": "HU",
"rankingPoints": 564161
}
},
"HU": 3
}
in the console.
As per comments, what I want is a hashmap-like object like
{
"HU": 1,
"DE": 2,
"RS": 3
}
where the property-value is the rank (+1) in the sorted data so I can simply get the rank by accessing dict["DE"] which would return 2.

Capture the position of each key in your data using forEach or reduce:
const test = [
{ nation: { name: "Germany", iso: "DE", rankingPoints: 293949 } },
{ nation: { name: "Hungary", iso: "HU", rankingPoints: 564161 } },
{ nation: { name: "Serbia", iso: "SR", rankingPoints: 231651 } }
];
const sorted = test.sort((a, b) => a.nation.rankingPoints - b.nation.rankingPoints);
// Using forEach:
var dict = {}
sorted.forEach((el, index) => dict[el.nation.iso] = sorted.length - index);
// Using reduce:
dict = sorted.reduce(
(dict, el, index) => (dict[el.nation.iso] = sorted.length - index, dict),
{}
);
console.log(dict)
console.log("dict['DE'] = ", dict['DE'])
Output:
{
"SR": 3,
"DE": 2,
"HU": 1
}
dict['DE'] = 2
(Note the order of properties is not significant in an object used as a map - if you need a specific order use an array.)

It is also possible to achieve this using Array.map and Object.fromEntries:
const test = [
{ nation: { name: "Germany", iso: "DE", rankingPoints: 293949 } },
{ nation: { name: "Hungary", iso: "HU", rankingPoints: 564161 } },
{ nation: { name: "Serbia", iso: "SR", rankingPoints: 231651 } }
];
const sorted = test.sort((a, b) => a.nation.rankingPoints < b.nation.rankingPoints ? 1 : (a.nation.rankingPoints > b.nation.rankingPoints ? -1 : 0));
const dict = Object.fromEntries(sorted.map((c, index) => [c.nation.iso, index + 1]));
console.log(dict);

const test = [
{ nation: { name: "Germany", iso: "DE", rankingPoints: 293949 } },
{ nation: { name: "Hungary", iso: "HU", rankingPoints: 564161 } },
{ nation: { name: "Serbia", iso: "SR", rankingPoints: 231651 } }
];
const sorted = test.sort((a, b) => b.nation.rankingPoints - a.nation.rankingPoints);
const dict = sorted.reduce((result, curr, index, array) => ({ ...result, [curr.nation.iso]: ++index }), {});
console.log(JSON.stringify(dict));

Related

New Map from JSON data with unique values

I'm trying to create a new map of [key:value] as [trait_type]:[{values}]
So taking the below data it should look like:
[Hair -> {"Brown", "White-Blue"},
Eyes -> {"Green", "Red"}
]
Heres my json obj
[
{
"name":"Charlie",
"lastname":"Gareth",
"date":1645462396133,
"attributes":[
{
"trait_type":"Hair",
"value":"Brown"
},
{
"trait_type":"Eyes",
"value":"Red"
}
]
},
{
"name":"Bob",
"lastname":"James",
"date":1645462396131,
"attributes":[
{
"trait_type":"Hair",
"value":"White-Blue"
},
{
"trait_type":"Eyes",
"value":"green"
}
]
}
];
To note: I'm also trying to make it the values unique so If I have 2 people with red eyes the value would only ever appear once.
This is what I have so far, kind of works but in the console window the values come out as a char array.
let newData = data.map((item) =>
item.attributes.map((ats) => {
return { [ats.trait_type]: [...ats.value] };
})
);
newData.map((newItem) => console.log(newItem));
You could take an object and iterate the nested data with a check for seen values.
const
data = [{ name: "Charlie", lastname: "Gareth", date: 1645462396133, attributes: [{ trait_type: "Hair", value: "Brown" }, { trait_type: "Eyes", value: "Red" }] }, { name: "Bob", lastname: "James", date: 1645462396131, attributes: [{ trait_type: "Hair", value: "White-Blue" }, { trait_type: "Eyes", value: "green" }] }],
result = data.reduce((r, { attributes }) => {
attributes.forEach(({ trait_type, value }) => {
r[trait_type] ??= [];
if (!r[trait_type].includes(value)) r[trait_type].push(value);
})
return r;
}, {});
console.log(result);
try this
const extractTrait = data =>
data.flatMap(d => d.attributes)
.reduce((res, {
trait_type,
value
}) => {
return {
...res,
[trait_type]: [...new Set([...(res[trait_type] || []), value])]
}
}, {})
const data = [{
"name": "Charlie",
"lastname": "Gareth",
"date": 1645462396133,
"attributes": [{
"trait_type": "Hair",
"value": "Brown"
},
{
"trait_type": "Eyes",
"value": "Red"
}
]
},
{
"name": "Bob",
"lastname": "James",
"date": 1645462396131,
"attributes": [{
"trait_type": "Hair",
"value": "White-Blue"
},
{
"trait_type": "Eyes",
"value": "green"
}
]
}
];
console.log(extractTrait(data))

How to create array of objects from object of objects?

I have such object:
const countriesList = {
NAC: {
name: 'NAC'
},
LEVANT: {
name: 'Levant'
},
GCC: {
name: 'Gulf Cooperation Council',
iso2: 'GC',
code: '96'
},
AF: {
name: "Afghanistan",
iso2: "AF",
code: "93"
},
AL: {
name: "Albania",
iso2: "AL",
code: "355"
},
}
It's object, not array and it's important. I want to create new array, which is gonna look like that:
const result = [
{NAC: 'NAC'},
{LEVANT: 'Levant'},
{GCC: 'Gulf Cooperation Council'},
{AF: "Afghanistan"},
{AL: "Albania"}
]
I was trying to do something like that:
for (let value in countriesList) {
let temp = {
value: countriesList[value]['name']
}
this.countries.push(temp)
temp = {}
}
But instead of keys in array of objects I got value. How can I do that?
Thanks for answers!
You can map over Object.entries.
const countriesList = {
NAC: {
name: 'NAC'
},
LEVANT: {
name: 'Levant'
},
GCC: {
name: 'Gulf Cooperation Council',
iso2: 'GC',
code: '96'
},
AF: {
name: "Afghanistan",
iso2: "AF",
code: "93"
},
AL: {
name: "Albania",
iso2: "AL",
code: "355"
},
}
const res = Object.entries(countriesList).map(([key, {name}])=>({[key]: name}));
console.log(res);
Just use Object.keys then map to the name property:
Object.keys(countriesList).map(x => ({[x]: countriesList[x].name}))
You can Create and similar Array of Object with all the Keys.
const countryList = Object.entries(countriesList).map((e) => ( { [e[0]]: e[1] } ));
This Will Return Like This
[
{
NAC: {
name: 'NAC'
},
LEVANT: {
name: 'Levant'
},
GCC: {
name: 'Gulf Cooperation Council',
iso2: 'GC',
code: '96'
},
AF: {
name: "Afghanistan",
iso2: "AF",
code: "93"
},
AL: {
name: "Albania",
iso2: "AL",
code: "355"
},
}
]

How to filter deeply nested json by multiple attributes with vue/javascript

I have some JSON with the following structure:
{
"root": {
"Europe": {
"children": [
{
"name": "Germany"
},
{
"name": "England",
"children": [
{
"name": "London",
"search_words": ["city", "capital"],
"children": [
{
"name": "Westminster",
"search_words": ["borough"]
}
]
},
{
"name": "Manchester",
"search_words": ["city"]
}
]
},
{
"name": "France",
"children": [
{
"name": "Paris",
"search_words": ["city", "capital"]
}
]
}
]
},
"North America": {
"children": [
{
"name": "Canada",
"children": [
{
"name": "Toronto"
},
{
"name": "Ottawa"
}
]
},
{
"name": "United States"
}
]
}
}
}
I want to filter the JSON based on a text search. I should be able to search by both the name and any search_words. The final problem is that the JSON can be arbitrarily deep so it needs to be able to search through all levels.
I also need to loop through and print out the JSON in HTML (using Vue) and for it to update based on the search. Currently unclear how I can do this without knowing how many levels deep the JSON will go?
Any help will be greatly appreciated!
I answered a similar question recently. I'm sharing it here because I think it provides a relevant foundation for this post. Before we begin though, we must first address the irregular shape of your input data -
const data2 =
{ name:"root"
, children:
Array.from
( Object.entries(data.root)
, ([ country, _ ]) =>
Object.assign({ name:country }, _)
)
}
console.log(JSON.stringify(data2, null, 2))
Now we can see data2 is a uniform { name, children: [ ... ]} shape -
{
"name": "root",
"children": [
{
"name": "Europe",
"children": [
{ "name": "Germany" },
{
"name": "England",
"children": [
{
"name": "London",
"search_words": [ "city", "capital" ],
"children": [
{
"name": "Westminster",
"search_words": [ "borough" ]
}
]
},
{
"name": "Manchester",
"search_words": [ "city" ]
}
]
},
{
"name": "France",
"children": [
{
"name": "Paris",
"search_words": [ "city", "capital" ]
}
]
}
]
},
{
"name": "North America",
"children": [
{
"name": "Canada",
"children": [
{ "name": "Toronto" },
{ "name": "Ottawa" }
]
},
{ "name": "United States" }
]
}
]
}
Now we write a generic depth-first traversal function, dft -
function* dft (t, path = [])
{ for (const _ of t.children ?? [])
yield* dft(_, [...path, t.name ])
yield [path, t]
}
Our dft function gives us a path to each element, e, in our input tree, t -
["root","Europe"]
{"name":"Germany"}
["root","Europe","England","London"]
{name:"Westminster", search_words:["borough"]}
["root","Europe","England"]
{name:"London", search_words:["city","capital"], children:[...]}
["root","Europe","England"]
{name:"Manchester", search_words:["city"]}
["root","Europe"]
{name:"England", children:[...]}
["root","Europe","France"]
{name:"Paris", search_words:["city","capital"]}
["root","Europe"]
{name:"France", children:[...]}
["root"]
{name:"Europe", children:[...]}
["root","North America","Canada"]
{name:"Toronto"}
Now that we know that path to each of the nodes, we can create an index which uses the path and any search_words to link back to the node -
const index = t =>
Array.from
( dft(t)
, ([path, e]) =>
[ [...path, e.name, ...e.search_words ?? [] ] // all words to link to e
, e // e
]
)
.reduce
( (m, [ words, e ]) =>
insertAll(m, words, e) // update the index using generic helper
, new Map
)
This depends on a generic helper insertAll -
const insertAll = (m, keys, value) =>
keys.reduce
( (m, k) =>
m.set(k, [ ...m.get(k) ?? [], value ])
, m
)
With index finished, we have a way to create a fast lookup for any search term -
const myIndex =
index(data2)
console.log(myIndex)
Map
{ "Europe" =>
[{"name":"Germany"},{"name":"Westminster",...},{"name":"London",...},{"name":"Manchester",...},{"name":"England"...},{"name":"Manchester",...}]},{"name":"Paris",...},{"name":"France"...},{"name":"Europe"...},{"name":"Manchester",...}]},{"name":"France"...}]}]
, "Germany" =>
[{"name":"Germany"}]
, "England" =>
[{"name":"Westminster",...},{"name":"London",...},{"name":"Manchester",...},{"name":"England"...},{"name":"Manchester",...}]}]
, "London" =>
[{"name":"Westminster",...},{"name":"London",...}]
, "Westminster" =>
[{"name":"Westminster",...}]
, "borough" =>
[{"name":"Westminster",...}]
, "city" =>
[{"name":"London",...},{"name":"Manchester",...},{"name":"Paris",...}]
, "capital" =>
[{"name":"London",...},{"name":"Paris",...}]
, "Manchester" =>
[{"name":"Manchester",...}]
, "France" =>
[{"name":"Paris",...},{"name":"France"...}]
, "Paris" =>
[{"name":"Paris",...}]
, "North America" =>
[{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...},{"name":"United States"},{"name":"North America"...},
{"name":"United States"}]}]
, "Canada" =>
[{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...}]
, "Toronto" =>
[{"name":"Toronto"}]
, "Ottawa" =>
[{"name":"Ottawa"}]
, "United States" =>
[{"name":"United States"}]
}
This should highlight the remaining inconsistencies in your data. For example, you have some nodes nested under city, capital, or borough. Also worth noting that we should probably use s.toLowerCase() on all of the index keys so that lookups can be case-insensitive. This is an exercise left for the reader.
Creating the index is easy and you only need to do it once -
const myIndex =
index(data2)
Your index can be reused for as many lookups as you need -
console.log(myIndex.get("Toronto") ?? [])
console.log(myIndex.get("France") ?? [])
console.log(myIndex.get("Paris") ?? [])
console.log(myIndex.get("Canada") ?? [])
console.log(myIndex.get("Zorp") ?? [])
[{"name":"Toronto"}]
[{"name":"Paris",...},{"name":"France"...}]
[{"name":"Paris",...}]
[{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...}]
[]
Inserting the results in you Vue application is left for you.
As Thankyou points out, your inconsistent data format makes it harder to write nice code for this. My approach is slightly different. Instead of transforming your data, I write a wrapper to my generic function to handle this output in a more useful manner.
We start with a function collect, that will work recursively with {name?, search_words?, children?, ...rest} objects, returning the nodes that match a given predicate and recurring on the children. We call this with a function, search, that takes a search term and makes a predicate from it. (Here we test if name or any search_term matches the term; this would be easy to modify for partial matches, for case insensitivity, and so on.)
Then we write the wrapper I mentioned, searchLocations. It descends into the .root node and then maps and combines the results of calling search on each of the root's values.
const collect = (pred) => ({children = [], ...rest}) => [
... (pred (rest) ? [rest] : []),
... children .flatMap (collect (pred))
]
const search = (term) =>
collect (({name = '', search_words = []}) => name == term || search_words .includes (term))
const searchLocations = (locations, term) =>
Object.values (locations .root) .flatMap (search (term))
const locations = {root: {Europe: {children: [{name: "Germany"}, {name: "England", children: [{name: "London", search_words: ["city", "capital"], children: [{name: "Westminster", search_words: ["borough"]}]}, {name: "Manchester", search_words: ["city"]}]}, {name: "France", children: [{name: "Paris", search_words: ["city", "capital"]}]}]}, "North America": {children: [{name: "Canada", children: [{name: "Toronto"}, {name: "Ottawa"}]}, {name: "United States"}]}}}
console .log ('Toronto', searchLocations (locations, 'Toronto'))
console .log ('borough', searchLocations (locations, 'borough'))
console .log ('capital', searchLocations (locations, 'capital'))
.as-console-wrapper {max-height: 100% !important; top: 0}
If what you want, as it sounds like you might, is the same structure as the input, keeping only the nodes necessary to include the matches, then we should be able to do something similar starting with a tree filtering function. I will try to look at that after the holidays.
Update
I did look at this again, looking to filter the tree as a tree. The code wasn't much harder. But this time, I did use a convert function to turn your data into a more consistent recursive structure. Thus the whole object becomes an array with two elements at the root, one with name "Europe" and the other with name "North America", each with their existing children nodes. This makes all further processing easier.
There are two key functions here:
The first is a general-purpose deepFilter function, which takes a predicate and an array of items that may have children nodes structured like their parents, and returns a new version containing anything which matches the predicate and their entirely ancestry. It looks like this:
const deepFilter = (pred) => (xs) =>
xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>
pred (rest) || kids.length
? [{...rest, ...(kids.length ? {children: kids} : {})}]
: []
)
The second is specific to this problem: searchLocation. It calls deepFilter using a predicate constructed from a search term and the converted structure already discussed. It uses a convert helper for the structure, and a search helper to turn the search term into a predicate that looks for (case-insensitive) partial matches on the name and all search terms.
const searchLocations = (loc, locations = convert(loc)) => (term) =>
term.length ? deepFilter (search (term)) (locations) : locations
This is demonstrated by a user interface which shows the locations in nested <UL>s, with a search box that filters the locations in real time.
If, for instance, you enter just "w" in the search box, you will get
Europe
England
London (city, capital)
Westminster (borough)
North America
Canada
Ottawa
as "Westminster" and "Ottawa" are the only matches.
If you enter "city" you will get
Europe
England
London (city, capital)
Manchester (city)
France
Paris (city, capital)
You can see this in action in this snippet:
// utility function
const deepFilter = (pred) => (xs) =>
xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>
pred (rest) || kids.length
? [{...rest, ...(kids.length ? {children: kids} : {})}]
: []
)
// helper functions
const search = (t = '', term = t.toLowerCase()) => ({name = '', search_words = []}) =>
term.length && (
name .toLowerCase () .includes (term) ||
search_words .some (word => word .toLowerCase() .includes (term))
)
const convert = ({root}) =>
Object.entries (root) .map (([name, v]) => ({name, ...v}))
// main function
const searchLocations = (loc, locations = convert(loc)) => (term) =>
term.length ? deepFilter (search (term)) (locations) : locations
// sample data
const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };
// main function specialized to given data
const mySearch = searchLocations(myData)
// UI demo
const format = (locations, searchTerm) => `<ul>${
locations.map(loc => `<li>${
loc.name +
(loc.search_words ? ` (${loc.search_words. join(', ')})` : ``) +
(loc.children ? format(loc.children, searchTerm) : '')
}</li>`)
.join('')
}</ul>`
const render = (locations, searchTerm) =>
document .getElementById ('main') .innerHTML = format (locations, searchTerm)
document .getElementById ('search') .addEventListener (
'keyup',
(e) => render (mySearch (e.target.value))
)
// show demo
render (mySearch (''))
<div style="float: right" id="control">
<label>Search: <input type="text" id="search"/></label>
</div>
<div style="margin-top: -1em" id="main"></div>
Obviously, this does not use Vue to generate the tree, only some string manipulation and innerHTML. I leave that part to you. But it should show another way to filter a nested structure.
Not entirely clear what you are looking for from your questions, but I'm guessing you need to modify the data to ensure that when it gets rendered, the matched data is correctly highlighted?
Here is a solution to find the matched objects using object-scan
// const objectScan = require('object-scan');
const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };
// eslint-disable-next-line camelcase
const mySearchFn = (term) => ({ name, search_words = [] }) => name === term || search_words.includes(term);
const search = (input, searchFn) => objectScan(['**[*]'], {
filterFn: ({ value, context }) => {
if (searchFn(value)) {
const { children, ...match } = value;
context.push(match);
}
}
})(input, []);
console.log(search(myData, mySearchFn('Toronto')));
// => [ { name: 'Toronto' } ]
console.log(search(myData, mySearchFn('borough')));
// => [ { name: 'Westminster', search_words: [ 'borough' ] } ]
console.log(search(myData, mySearchFn('capital')));
// => [ { name: 'Paris', search_words: [ 'city', 'capital' ] }, { name: 'London', search_words: [ 'city', 'capital' ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
And this is how you could inject information that can then be picked up by your rendering pipeline
// const objectScan = require('object-scan');
const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };
// eslint-disable-next-line camelcase
const mySearchFn = (term) => ({ name, search_words = [] }) => name === term || search_words.includes(term);
const search = (input, searchFn) => objectScan(['**[*]'], {
filterFn: ({ value }) => {
if (searchFn(value)) {
value.css = { highlight: true };
return true;
} else {
delete value.css;
return false;
}
},
rtn: 'count' // return number of matches
})(input);
console.log(search(myData, mySearchFn('Toronto')));
// => 1
console.log(myData);
// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ] } ] }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ] } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto', css: { highlight: true } }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }
console.log(search(myData, mySearchFn('borough')));
// => 1
console.log(myData);
// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ], css: { highlight: true } } ] }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ] } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto' }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }
console.log(search(myData, mySearchFn('capital')));
// => 2
console.log(myData);
// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ] } ], css: { highlight: true } }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ], css: { highlight: true } } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto' }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Using a library might not be worth it for you, it's a trade-off. Let me know if you have questions / thoughts on this answer.

Group an array of object having nested level

I have a tabular mode array object as shown below upto n level. it can be any parent children's records
var records = [
{ country: "USA", state: "FLORIDA", city: "city1" },
{ country: "USA", state: "FLORIDA", city: "city2" },
{ country: "USA", state: "FLORIDA", city:"city3" },
{ country: "USA", state: "ALASKA" },
{ country: "USA", state: "ALBAMA" },
]
var columns = ["country","state","city"] // upto n column
I need to group in below format for nth level as there can be n level of relations, group records in below format
{
sequencer: 1, value: 'USA', loop: [
{ sequencer: 1, value: 'FLORIDA', loop: [
{ sequencer: 1, value: 'city1' },
{ sequencer: 2, value: 'city2' },
{ sequencer: 3, value: 'city3' },
], },
{ sequencer: 2, value: 'ALASKA' },
{ sequencer: 3, value: 'ALBAMA' },
],
}
Can someone write an recursive function to group for n level of column object.
Thanks
You could group the data by avoiding unwanted loop properties.
const
data = [{ country: "USA", state: "FLORIDA", city: "city1" }, { country: "USA", state: "FLORIDA", city: "city2" }, { country: "USA", state: "FLORIDA", city: "city3" }, { country: "USA", state: "ALASKA" }, { country: "USA", state: "ALBAMA" }],
keys = ['country', 'state', 'city'],
result = data.reduce((loop, o) => {
keys
.map(k => o[k])
.filter(Boolean)
.reduce((r, value) => {
let temp = (r.loop ??= []).find(q => q.value === value);
if (!temp) r.loop.push(temp = { sequence: r.loop.length + 1, value });
return temp;
}, { loop });
return loop;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I answered a similar question recently. You could write your transform using flatMap and recursion -
const transform = ([ name, ...more ], { value, loop = [{}] }, r = {}) =>
name === undefined
? [ r ]
: loop.flatMap(t => transform(more, t, { [name]: value, ...r }))
const records =
{sequencer:1,value:'USA',loop:[{sequencer:1,value:'FLORIDA',loop:[{sequencer:1,value:'city1'},{sequencer:2,value:'city2'},{sequencer:3,value:'city3'}]},{sequencer:2,value:'ALASKA'},{sequencer:3,value:'ALBAMA'}]}
const columns =
['country', 'state', 'city']
const result =
transform(["country", "state", "city"], records)
console.log(JSON.stringify(result, null, 2))
A function statement equivalent of the => expression above -
function transform
( [ name, ...more ]
, { value, loop = [{}] }
, r = {}
)
{ if (name === undefined)
return [ r ]
else
return loop.flatMap
( t =>
transform
( more
, t
, { [name]: value, ...r }
)
)
}
Read the original Q&A for more insight on this technique.

Manipulate Object to group based on Array Object List

I'm trying to find a way to convert this list of objects based on the group array. The tricky part I've found is iterating through the group Array and applying the object to more than one place if there are multiple groups.
I'm also trying to ignore any group that does not belong to anything. I've tried using the reduce function but I cannot get the iteration through the group array.
let cars =
[
{
"group":[],
"name": "All Makes",
"code": ""
},
{
"group":["Group A"],
"name": "BMW",
"code": "X821"
},
{
"group":["Group B"],
"name": "Audi",
"code": "B216"
},
{
"group":["Group B"],
"name": "Ford",
"code": "P385"
},
{
"group":["Group B", "Group C"],
"name": "Mercedes",
"code": "H801"
},
{
"group":["Group C"],
"name": "Honda",
"code": "C213"
}
]
To become this:
[
{
"group": "Group A",
"cars": [
{
name: "BMW",
code: "X821"
}
]
},
{
"group": "Group B",
"cars": [
{
name: "Audi",
code: "B216"
},
{
name: "Ford",
code: "P385"
},
{
name: "Mercedes",
code: "H801"
}
]
},
{
"group": "Group C",
"cars": [
{
name: "Mercedes",
code: "H801"
},
{
name: "Honda",
code: "C213"
}
]
}
]
I've already tried using reduce to accomplish this but it doesn't quite get the desired affect.
const res = cars.reduce((acc, {group, ...r}) => {
group.forEach(key => {
acc[key] = (acc[key] || []).concat({...r});
});
return acc;
}, []);
console.log(res);
Using string keys on arrays doesn't work quite well, that's what objects are for, then use Object.values to get the array out of it. Also you want to put objects and not arrays in there:
const res = Object.values(cars.reduce((acc, {group, ...r}) => {
group.forEach(key => {
(acc[key] = (acc[key] || { group, cars: [] })).cars.push(r);
});
return acc;
}, {}));
I'd write it this way:
const byGroup = new Map();
for(const { group, ...rest } of cars) {
if(!byGroup.has(group))
byGroup.set(group, { group, cars: [] });
byGroup.get(group).cars.push(rest);
}
const result = [...byGroup.values()];
You need to take an object as accumulator and then map the entries fro getting the wanted result.
let cars = [{ group: [], name: "All Makes", code: "" }, { group: ["Group A"], name: "BMW", code: "X821" }, { group: ["Group B"], name: "Audi", code: "B216" }, { group: ["Group B"], name: "Ford", code: "P385" }, { group: ["Group B", "Group C"], name: "Mercedes", code: "H801" }, { group: ["Group C"], name: "Honda", code: "C213" }],
result = Object
.entries(cars.reduce((acc, { group, ...r }) => {
group.forEach(key => acc[key] = (acc[key] || []).concat({...r}));
return acc;
}, {}))
.map(([group, cars]) => ({ group, cars }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources