Normalizing Nested Many To Many - javascript

I am looking to find a way to normalize nested many to many relationships.
However, it has been going when the model is a few levels deep, such as Categories in the example below. In the output the location id 27 only contains category ids 1 and 7 (which I assume is because that is what the last version of location 27 shows). What is the best approach in normalizing this type of data?
Would using shop id in the location be a good idea? i.e.
"locations": {
"1-27": {
"id": 27,
"label": "Birmingham",
"categories": [
1,
2,
7
]
},
"2-27": {
"id": 27,
"label": "Birmingham",
"categories": [
1,
7
]
}
},
Thanks for taking the time to read this!
Additional Data Details
Database
Shops
id
label
Locations
id
label
Location_Shop
location_id
shop_id
Categories
id
label
Category_Location
category_id
location_id
Example Data
[
{
id: 1,
label: 'First Shop',
locations: [
{
id: 27,
label: 'Birmingham',
categories: [
{
id: 1,
label: 'Car Park',
},
{
id: 2,
label: 'Petrol Station',
},
{
id: 7,
label: 'Bakery',
},
],
},
],
},
{
id: 2,
label: 'Second Shop',
locations: [
{
id: 27,
label: 'Birmingham',
categories: [
{
id: 1,
label: 'Car Park',
},
{
id: 7,
label: 'Bakery',
},
],
},
],
},
]
Code
const categorySchema = new schema.Entity('categories');
const locationSchema = new schema.Entity('locations', {
categories: [categorySchema],
});
const shopSchema = new schema.Entity('shops', {
locations: [locationSchema],
});
Output
{
"entities": {
"categories": {
"1": {
"id": 1,
"label": "Car Park"
},
"2": {
"id": 2,
"label": "Petrol Station"
},
"7": {
"id": 7,
"label": "Bakery"
}
},
"locations": {
"27": {
"id": 27,
"label": "Birmingham",
"categories": [
1,
7
]
}
},
"shops": {
"1": {
"id": 1,
"label": "First Shop",
"locations": [
27
]
},
"2": {
"id": 2,
"label": "Second Shop",
"locations": [
27
]
}
}
},
"result": [
1,
2
]
}

Related

how to extract and mapping object from array(object) with redundant key inside key

i have following example array(object):
[
{
"id": 1,
"name": "selling",
"detail": [
{
"id": 11,
"name": "sale-report",
"detail": [
{ "id": 111, "name": "sale-report1", "detail": [] },
{ "id": 112, "name": "sale-report2", "detail": [] }
]
}
]
},
{
"id": 2,
"name": "webstore",
"detail": [
{
"id": 11,
"name": "sale-report",
"detail": [
{ "id": 111, "name": "webstore-report1", "detail": [] },
{ "id": 112, "name": "webstore-report2", "detail": [] }
]
}
]
},
{
"id": 2,
"name": "setting",
"detail": [
{
"id": 11,
"name": "general",
"detail": [
{ "id": 111, "name": "setting-general1", "detail": [] },
{ "id": 112, "name": "setting-general2", "detail": [] }
]
}
]
}
]
how to change the array with new format like this
[
{
"id": 1,
"name": "selling",
},
{
"id": 11,
"name": "sale-report"
},
{ "id": 111, "name": "sale-report1" },
{ "id": 112, "name": "sale-report2" },
{
"id": 2,
"name": "webstore",
},
{
"id": 11,
"name": "sale-report",
},
{ "id": 111, "name": "webstore-report1" },
{ "id": 112, "name": "webstore-report2" },
{
"id": 2,
"name": "setting",
},
{
"id": 11,
"name": "general",
},
{ "id": 111, "name": "setting-general1" },
{ "id": 112, "name": "setting-general2" }
]
with the condition that if there is a key "detail" inside object in the branch, it will be mapped as well (assuming unlimited key "detail" inside object inside array)
note: content of detail will be same as parent, but different value
thanks in advance
i tried mapping mannualy with foreach, but i cant figure out if detail key with array(object) has unlimited nesting
Simply, use recursion to do this.
Here is the working demo-
let input = [
{
id: 1,
name: "selling",
detail: [
{
id: 11,
name: "sale-report",
detail: [
{ id: 111, name: "sale-report1", detail: [] },
{ id: 112, name: "sale-report2", detail: [] }
]
}
]
},
{
id: 2,
name: "webstore",
detail: [
{
id: 11,
name: "sale-report",
detail: [
{ id: 111, name: "webstore-report1", detail: [] },
{ id: 112, name: "webstore-report2", detail: [] }
]
}
]
},
{
id: 2,
name: "setting",
detail: [
{
id: 11,
name: "general",
detail: [
{ id: 111, name: "setting-general1", detail: [] },
{ id: 112, name: "setting-general2", detail: [] }
]
}
]
}
];
let result = [];
function parseData(arr) {
arr.forEach((item) => {
result.push({
id: item.id || null,
name: item.name || null
});
if (item.detail && item.detail.length) {
parseData(item.detail);
}
});
return result;
}
let output = parseData(input);
console.log(output);
You could destructure the objects and take detail out of the object and map the rest object and the results of nested details array as flat array with a recursive function.
const
flat = (array = []) => array.flatMap(({ detail, ...rest }) => [
rest,
...flat(detail)
]),
data = [{ id: 1, name: "selling", detail: [{ id: 11, name: "sale-report", detail: [{ id: 111, name: "sale-report1", detail: [] }, { id: 112, name: "sale-report2", detail: [] }] }] }, { id: 2, name: "webstore", detail: [{ id: 11, name: "sale-report", detail: [{ id: 111, name: "webstore-report1", detail: [] }, { id: 112, name: "webstore-report2", detail: [] }] }] }, { id: 2, name: "setting", detail: [{ id: 11, name: "general", detail: [{ id: 111, name: "setting-general1", detail: [] }, { id: 112, name: "setting-general2", detail: [] }] }] }],
result = flat(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A non recursive method of doing this could look something like the following:
const data = [{"id":1,"name":"selling","detail":[{"id":11,"name":"sale-report","detail":[{"id":111,"name":"sale-report1","detail":[]},{"id":112,"name":"sale-report2","detail":[]}]}]},{"id":2,"name":"webstore","detail":[{"id":11,"name":"sale-report","detail":[{"id":111,"name":"webstore-report1","detail":[]},{"id":112,"name":"webstore-report2","detail":[]}]}]},{"id":2,"name":"setting","detail":[{"id":11,"name":"general","detail":[{"id":111,"name":"setting-general1","detail":[]},{"id":112,"name":"setting-general2","detail":[]}]}]}];
const extract = (data) => {
const result = [];
const queue = [...data];
while(queue.length > 0) {
const { detail, ...rest } = queue.shift();
result.push(rest);
queue.unshift(...detail);
}
return result;
};
console.log(extract(data));

Filter complex array in react native?

I have array .
const arr = [{
"status": "success",
"data": [{
"name": "user1",
"games": [{
"id": 1,
"gamename": "cricket"
}, {
"id": 2,
"gamename": "football"
}]
},
{
"name": "user1",
"games": [{
"id": 1,
"gamename": "videogames"
}, {
"id": 2,
"gamename": "volleyball"
}]
}
]
}]
I tried following the code to filter it. and no output show
arr.map((item,idx) => (
console.log(item.data.games.gamename)
)
))
I want to print all game name eg.
cricket
football
videogames
volleyball
We can use flatMap() to do it
const arr = [{
"status": "success",
"data": [{
"name": "user1",
"games": [{
"id": 1,
"gamename": "cricket"
}, {
"id": 2,
"gamename": "football"
}]
},
{
"name": "user1",
"games": [{
"id": 1,
"gamename": "videogames"
}, {
"id": 2,
"gamename": "volleyball"
}]
}
]
}]
// multiple flatMap chain invocation seems ugly,waiting for more elegant solution
let result = arr.flatMap(a => a.data).flatMap(a => a.games).flatMap(a => a.gamename)
console.log(result)
Data is a array and so is games:
const arr = [
{
status: "success",
data: [
{
name: "user1",
games: [
{
id: 1,
gamename: "cricket",
},
{
id: 2,
gamename: "football",
},
],
},
{
name: "user1",
games: [
{
id: 1,
gamename: "videogames",
},
{
id: 2,
gamename: "volleyball",
},
],
},
],
},
];
arr.map((item) => {
item.data.map((item) => {
item.games.map((item) => {
console.log(item.gamename);
});
});
});
Try out this code, it will return only game names, you can change the join if don't need comma (,)
Output :
"cricket,football,videogames,volleyball"
const arr = [{
"status": "success",
"data": [{
"name": "user1",
"games": [{
"id": 1,
"gamename": "cricket"
}, {
"id": 2,
"gamename": "football"
}]
},
{
"name": "user1",
"games": [{
"id": 1,
"gamename": "videogames"
}, {
"id": 2,
"gamename": "volleyball"
}]
}
]
}];
console.log(JSON.stringify(arr.filter(e=>e.status=="success").map(e=>e.data.map(f=>f.games.map(g=>g.gamename)).join(",")).join(",")));

What is the best way to group this js array items in this structure

how can i convert returned data from this structure:
[
{
"id": 91,
"name": "Art",
"division": {
"id": 4,
"name": "1st level",
"created_at": "2018-11-05T10:11:37+00:00",
},
"created_at": "2018-11-05T10:11:37+00:00",
},
{
"id": 188,
"name": "English",
"division": {
"id": 9,
"name": "3rd level",
"created_at": "2018-11-05T10:11:37+00:00",
},
"created_at": "2018-11-05T10:11:37+00:00",
},
{
"id": 218,
"name": "Art",
"division": {
"id": 11,
"name": "3rd level",
"created_at": "2018-11-05T10:11:37+00:00",
},
"created_at": "2018-11-05T10:11:37+00:00",
}
]
to this structure :
[
{
"id": 1,
"name": "Art",
"classes": [
{
"id": 91,
"name": "1st level",
},
{
"id": 218,
"name": "3rd level",
},
],
},
{
"id": 2,
"name": "English",
"classes": [
{
"id": 188,
"name": "3rd level",
},
],
},
]
note: class.id = parentSubject.id
I wrote some codes to solve the problem but I'm looking for the best way !!
i use .reduce() function
I will attach the codes in the comments box.
Here is how I would do it:
let ans = initialArray.reduce((cum,x) => {
let foundIndex = cum.findIndex((a) =>{
return a.name == x.name});
if(foundIndex!=-1){
cum[foundIndex].classes.push({
id : x.id,
name : x.division.name
})
}
else{
cum.push({
id : cum.length+1,
name : x.name,
classes : [{
id : x.id,
name : x.division.name
}]
})
}
return cum;
},[]);
I use reduce and findIndex methods.
You can use array map to create a new array of objects with the new id.
const oldDatas = [
{
id: 91,
name: 'Art',
division: {
id: 4,
name: '1st level',
created_at: '2018-11-05T10:11:37+00:00',
},
created_at: '2018-11-05T10:11:37+00:00',
},
{
id: 188,
name: 'English',
division: {
id: 9,
name: '3rd level',
created_at: '2018-11-05T10:11:37+00:00',
},
created_at: '2018-11-05T10:11:37+00:00',
},
{
id: 218,
name: 'Art',
division: {
id: 11,
name: '3rd level',
created_at: '2018-11-05T10:11:37+00:00',
},
created_at: '2018-11-05T10:11:37+00:00',
},
];
const newDatas = oldDatas.map((data, index) => {
return { ...data, id: index + 1 };
});
console.log(newDatas);
my solution :
let res = initialArray.reduce((acc, obj) => {
const exist = acc.findIndex((item) => item.name === obj.name);
if (exist >= 0) {
acc[exist] = {
id: exist,
name: obj.name,
classes: [
...acc[exist].classes,
{ id: obj.id, name: obj.division.name },
],
};
} else {
acc.push({
id: acc.length,
name: obj.name,
classes: [{ id: obj.id, name: obj.division.name }],
});
}
return acc;
}, []);

Nested Array Filter/Mapping

Probably a basic question, but I've been blocked for a day now on this.
I am trying to get the correct map/filter from the following array:
[{
"id": 1,
"description": "Electric",
"subUtilityTypes": [{
"id": 5,
"description": "Grid"
},
{
"id": 6,
"description": "Solar"
}
]
},
{
"id": 2,
"description": "Gas",
"subUtilityTypes": [{
"id": 7,
"description": "Heating Oil"
},
{
"id": 8,
"description": "Natural Gas"
},
{
"id": 11,
"description": "Propane"
}
]
}
]
I want to get the id and description inside all subUtilityTypes.
This is what I've been trying:
this.options = arr1.map((parent) => {
return {
id: parent.id,
name: parent.subUtilityTypes.flatMap((child) => {
return child.description;
})
};
});
My problem with what I am trying is that instead of creating separate objects I am getting parent id and list of child names like this:
[{
"id": 1,
"name": [
"Grid",
"Solar"
]
},
{
"id": 2,
"name": [
"Heating Oil",
"Natural Gas",
"Propane"
]
}
]
Expected result should look like this:
[{
"id": 5,
"name": [
"Grid"
]
},
{
"id": 6,
"name": [
"Solar"
]
},
{
"id": 7,
"name": [
"Heating Oil"
]
}
]
Firstly use flatMap to get subUtilityTypes, then map entries:
const input = [{id:1,description:"Electric",subUtilityTypes:[{id:5,description:"Grid"},{id:6,description:"Solar"}]},{id:2,description:"Gas",subUtilityTypes:[{id:7,description:"Heating Oil"},{id:8,description:"Natural Gas"},{id:11,description:"Propane"}]}];
const res = input.flatMap(e => e.subUtilityTypes)
.map(e => ({ id: e.id, name: [e.description] }))
console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; } /* ignore this */
Map only arr1 by returning only subUtilityTypes and then map it to get the desired result :
arr1.flatMap(item=>item.subUtilityTypes)
.map(item=>({id:item.id,name:[item.description]}))
let arr1=[
{
"id": 1,
"description": "Electric",
"subUtilityTypes": [
{
"id": 5,
"description": "Grid"
},
{
"id": 6,
"description": "Solar"
}
]
},
{
"id": 2,
"description": "Gas",
"subUtilityTypes": [
{
"id": 7,
"description": "Heating Oil"
},
{
"id": 8,
"description": "Natural Gas"
},
{
"id": 11,
"description": "Propane"
}
]
}
]
let options=arr1.flatMap(item=>item.subUtilityTypes).map(item=>({id:item.id,name:[item.description]}))
console.log(options)
You can also use reduce to achieve the same result.
arr.reduce((acc, curr) => [...acc,...curr.subUtilityTypes.map((o) => ({ id: o.id, name: [o.description] })),],[])
const arr = [
{
id: 1,
description: "Electric",
subUtilityTypes: [
{
id: 5,
description: "Grid",
},
{
id: 6,
description: "Solar",
},
],
},
{
id: 2,
description: "Gas",
subUtilityTypes: [
{
id: 7,
description: "Heating Oil",
},
{
id: 8,
description: "Natural Gas",
},
{
id: 11,
description: "Propane",
},
],
},
];
const result = arr.reduce((acc, curr) => [...acc,...curr.subUtilityTypes.map((o) => ({ id: o.id, name: [o.description] })),],[]);
console.log(result);

Reorganization and cleaning of a 'treeview' JSON object

I want to create a JavaScript method that transforms a Json ‘treeView’ object. My starting object has a structure with duplicated branches:
Example of basic data:
- ROOT
- 01
- 011
- 0111
- 01
- 011
- 0112
- 01
- 011
- 0113
- 01
- 012
- 0121
- 01
- 012
- 0122
- 01
- 012
- 0123
- 01
- 013
- 0131
- 01
- 013
- 0132
- 01
- 013
- 0133
I would like the method to group all the levels with an identical code.
And sort the items alphabetically.
Example of what the method should return:
- ROOT
- 01
- 011
- 0111
- 0112
- 0113
- 012
- 0121
- 0122
- 0123
- 013
- 0131
- 0132
- 0133
Example of basic data (Json):
[
{
"id": 0,
"name": "ROOT",
"children": [
{
"name": "01",
"id": 3,
"children": [
{
"name": "013",
"id": 2,
"children": [
{
"name": "0131",
"id": 1
}
]
}
]
},
{
"name": "01",
"id": 6,
"children": [
{
"name": "011",
"id": 5,
"children": [
{
"name": "0112",
"id": 4
}
]
}
]
},
{
"name": "01",
"id": 9,
"children": [
{
"name": "011",
"id": 8,
"children": [
{
"name": "0111",
"id": 7
}
]
}
]
},
{
"name": "01",
"id": 12,
"children": [
{
"name": "013",
"id": 11,
"children": [
{
"name": "0132",
"id": 10
}
]
}
]
},
{
"name": "01",
"id": 15,
"children": [
{
"name": "013",
"id": 14,
"children": [
{
"name": "0133",
"id": 13
}
]
}
]
},
{
"name": "01",
"id": 18,
"children": [
{
"name": "011",
"id": 17,
"children": [
{
"name": "0113",
"id": 16
}
]
}
]
},
{
"name": "01",
"id": 21,
"children": [
{
"name": "012",
"id": 20,
"children": [
{
"name": "0121",
"id": 19,
}
]
}
]
},
{
"name": "01",
"id": 24,
"children": [
{
"name": "012",
"id": 23,
"children": [
{
"name": "0122",
"id": 22
}
]
}
]
},
{
"name": "01",
"id": 27,
"children": [
{
"name": "012",
"id": 26,
"children": [
{
"name": "0123",
"id": 25
}
]
}
]
}
]
}
]
Example of what the method should return (json):
[
{
"id": 0,
"name": "ROOT",
"children": [
{
"name": "01",
"id": 3,
"children": [
{
"name": "011",
"id": 5,
"children": [
{
"name": "0111",
"id": 7
},
{
"name": "0112",
"id": 4
},
{
"name": "0113",
"id": 16
}
]
},
{
"name": "012",
"id": 20,
"children": [
{
"name": "0121",
"id": 19,
},
{
"name": "0122",
"id": 22
},
{
"name": "0123",
"id": 25
}
]
},
{
"name": "013",
"id": 2,
"children": [
{
"name": "0131",
"id": 1
},
{
"name": "0132",
"id": 10
},
{
"name": "0133",
"id": 13
}
]
}
]
},
]
}
]
I think a recursive method would be the most appropriate, but I don't have too many ideas.
The keyword you are looking for might be trie
But without much theory we can just write some code already
The idea is to consider your output as a tree.
Whenever you access a child of your data, you create a node corresponding to that child in the tree if it does not exists yet
Then your recurse, and in-fine you just have to get back your tree-like structure
const data = [{"id":0,"name":"ROOT","children":[{"name":"01","id":3,"children":[{"name":"013","id":2,"children":[{"name":"0131","id":1}]}]},{"name":"01","id":6,"children":[{"name":"011","id":5,"children":[{"name":"0112","id":4}]}]},{"name":"01","id":9,"children":[{"name":"011","id":8,"children":[{"name":"0111","id":7}]}]},{"name":"01","id":12,"children":[{"name":"013","id":11,"children":[{"name":"0132","id":10}]}]},{"name":"01","id":15,"children":[{"name":"013","id":14,"children":[{"name":"0133","id":13}]}]},{"name":"01","id":18,"children":[{"name":"011","id":17,"children":[{"name":"0113","id":16}]}]},{"name":"01","id":21,"children":[{"name":"012","id":20,"children":[{"name":"0121","id":19}]}]},{"name":"01","id":24,"children":[{"name":"012","id":23,"children":[{"name":"0122","id":22}]}]},{"name":"01","id":27,"children":[{"name":"012","id":26,"children":[{"name":"0123","id":25}]}]}]}]
const makeTree = root => {
const toTree = (trieNode, node) => {
trieNode.children = trieNode.children || []
let trieNodeChild = trieNode.children.find(child => child.name === node.name)
if (!trieNodeChild) {
trieNodeChild = { name: node.name, id: node.id }
trieNode.children.push(trieNodeChild)
}
if (!node.children) return
node.children.forEach(child => toTree(trieNodeChild, child))
return trieNode
}
return toTree({}, root)
}
console.time('a')
console.log(JSON.stringify(makeTree(data[0]), null, 2))
console.timeEnd('a')
Note that it is not very efficient because searching in array is slow
A slightly improved version is to store the trieNodes in a map(name => trieNode)
const makeTree2 = root => {
const cache = new Map
const toTree = (trieNode, node) => {
trieNode.children = trieNode.children || []
let trieNodeChild = cache.get(node.name)
if (!trieNodeChild) {
trieNodeChild = { name: node.name, id: node.id }
trieNode.children.push(trieNodeChild)
cache.set(node.name, trieNodeChild)
}
if (!node.children) return
node.children.forEach(child => toTree(trieNodeChild, child))
return trieNode
}
return toTree({}, root)
}
console.time('a2')
console.log(JSON.stringify(makeTree2(data[0]), null, 2))
console.timeEnd('a2')
On my potatoe, first code takes 5ms, second takes 0.3ms. Does not matter much for this data, but order of complexity is apparent.
Here is an interative solution using object-scan
We traverse the tree and remember the nodes that we have seen. If we encounter a node we have seen previously, we merge its children into the previously seen node and delete the node itself.
Note that different ids are picked, since we have to traverse the tree in "delete-safe" order.
// const objectScan = require('object-scan');
const myTree = [{ id: 0, name: 'ROOT', children: [{ name: '01', id: 3, children: [{ name: '013', id: 2, children: [{ name: '0131', id: 1 }] }] }, { name: '01', id: 6, children: [{ name: '011', id: 5, children: [{ name: '0112', id: 4 }] }] }, { name: '01', id: 9, children: [{ name: '011', id: 8, children: [{ name: '0111', id: 7 }] }] }, { name: '01', id: 12, children: [{ name: '013', id: 11, children: [{ name: '0132', id: 10 }] }] }, { name: '01', id: 15, children: [{ name: '013', id: 14, children: [{ name: '0133', id: 13 }] }] }, { name: '01', id: 18, children: [{ name: '011', id: 17, children: [{ name: '0113', id: 16 }] }] }, { name: '01', id: 21, children: [{ name: '012', id: 20, children: [{ name: '0121', id: 19 }] }] }, { name: '01', id: 24, children: [{ name: '012', id: 23, children: [{ name: '0122', id: 22 }] }] }, { name: '01', id: 27, children: [{ name: '012', id: 26, children: [{ name: '0123', id: 25 }] }] }] }];
const rewrite = (tree) => {
const lookup = objectScan(['**(^children$)'], {
useArraySelector: false,
filterFn: ({ parent, property, value, context }) => {
if (value.name in context) {
if (Array.isArray(value.children)) {
if (!Array.isArray(context[value.name].children)) {
context[value.name].children = [];
}
context[value.name].children.push(...value.children);
}
parent.splice(property, 1);
} else {
context[value.name] = value;
}
}
})(tree, {});
const cmpFn = (a, b) => a.name.localeCompare(b.name);
Object.values(lookup)
.filter((v) => Array.isArray(v.children))
.forEach((v) => v.children.sort());
tree.sort(cmpFn);
};
console.log(rewrite(myTree));
// => undefined
console.log(myTree);
// => [ { id: 0, name: 'ROOT', children: [ { name: '01', id: 27, children: [ { name: '012', id: 26, children: [ { name: '0123', id: 25 }, { name: '0122', id: 22 }, { name: '0121', id: 19 } ] }, { name: '011', id: 17, children: [ { name: '0113', id: 16 }, { name: '0111', id: 7 }, { name: '0112', id: 4 } ] }, { name: '013', id: 14, children: [ { name: '0133', id: 13 }, { name: '0132', id: 10 }, { name: '0131', id: 1 } ] } ] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#16.0.0"></script>
Disclaimer: I'm the author of object-scan

Categories