How to define schema for recursive model with Normalizr - javascript

Having a bit of an issue trying to normalise a payload, that contains a nested schema of the same type as the parent using Normalizr
For example I have the initial object (menu) which has a child (sections) which is an array of objects (section), which can go n deep.
{
id: 123,
sections: [{
id: 1,
sections:[{ id: 4, sections: [ id: 5, sections: [] ] }]
}, {
id: 2,
sections:[]
}, {
id: 3,
sections:[]
}]
}
I started by creating a menu schema, that had sections in the definition that linked to a sections schema, that worked for the first pass, but then wouldn't handle children of sections, so I added a subsequent definition within the section schema with the same name (was worth a shot) but it didn't work.
const section = new schema.Entity('sections')
const sections = new schema.Entity('sections', {
sections: section
})
const menu = new schema.Entity('menu', {
sections: [ sections ]
})
section.define({ sections })
I'm hoping to end up with the object below:
{
entities: {
menu: {
sections: [1, 2, 3]
},
sections: [{
1: { id: 1, sections: [4] },
2: { id: 2, sections: [] },
3: { id: 3, sections: [] },
4: { id: 4, sections: [5] },
5: { id: 5, sections: [] },
}]
}
}

Your sections schema should be an Array.
const section = new schema.Entity('sections')
const sections = new schema.Array(section);
section.define({ sections });
const menu = new schema.Entity('menu', { sections });
Then, in using it...
const data = {
id: 123,
sections: [{
id: 1,
sections:[{ id: 4, sections: [ { id: 5, sections: [] } ] }]
}, {
id: 2,
sections:[]
}, {
id: 3,
sections:[]
}]
};
normalize(data, menu)
Will return:
{
"entities": {
"sections": {
"1": { "id": 1, "sections": [ 4 ] },
"2": { "id": 2, "sections": [] },
"3": { "id": 3, "sections": [] },
"4": { "id": 4, "sections": [ 5 ] },
"5": { "id": 5, "sections": [] }
},
"menu": {
"123": { "id": 123, "sections": [ 1, 2, 3 ] }
}
},
"result": 123
}

If someone has the case of nested objects of same "type", for example "sections" and top level structure is array of "sections" too, like this:
const data = [
{
id: 1,
sections:[{ id: 4, sections: [ { id: 5, sections: [] } ] }]
},
{
id: 2,
sections:[]
},
{
id: 3,
sections:[]
}
]
here one way to "unnest" them:
import {schema, normalize} from "normalizr";
const child = new schema.Entity("sections");
const sections = new schema.Array(child);
child.define({sections});
const topLevel = new schema.Entity("sections", {
sections
});
const customSchema = [topLevel];
console.log(normalize(data, customSchema));
What you will get is:
{
"entities":{
"sections":{
"1":{
"id":1,
"sections":[
4
]
},
"2":{
"id":2,
"sections":[
]
},
"3":{
"id":3,
"sections":[
]
},
"4":{
"id":4,
"sections":[
5
]
},
"5":{
"id":5,
"sections":[
]
}
}
},
"result":[
1,
2,
3
]
}

Related

Why object is getting returned when applied map and filter to match the string search in available nested json data

I am returning the objects which have the card titles matches with the searched value from available data.
Data JSON :
let state = {rawData : [
{
"id": 1,
"title": "To Do",
"cards": [
{
"id": 111,
"title": "Team Meeting"
},
{
"id": 112,
"title": "DB Design"
}
]
},
{
"id": 2,
"title": "Doing",
"cards": [
{
"id": 221,
"title": "Raman's Review"
},
{
"id": 222,
"title": "Sequence Diagram"
}
]
},{
"id": 3,
"title": "Done",
"cards": [
{
"id": 331,
"title": "Karan's Review"
},{
"id": 332,
"title": "Karan"
}
]
}
]}
Here, As you can see rawData consist 3 objects each have cards array object.
Now, filter out the cards data which had a consist the search value. Please refer below code. To know how I am trying filter it and actual output I am getting Also the output I want.
var searchVal = "K"
let val = state.rawData.map(list => {
let out = list.cards.filter(card => card.title.toLowerCase().includes(searchVal.toLowerCase()))
let x = {...list}
x.cards =[...out]
console.log(x)
return x;
})
console.log(val)
As you can see above. I have a search str as "K". I am mapping over the each rawData object and using filter checking if the value is entered search string is available in card.title or not.
console result of x:
{ id: 1, title: 'To Do', cards: [] }
{ id: 2, title: 'Doing', cards: [] }
{
id: 3,
title: 'Done',
cards: [ { id: 331, title: "Karan's Review" }, { id: 332, title: 'Karan' } ]
}
Here, I am getting the result I want. But when I returns the x. The output is below:
[
{ id: 1, title: 'To Do', cards: [] },
{ id: 2, title: 'Doing', cards: [] },
{ id: 3, title: 'Done', cards: [ [Object], [Object] ] }
]
Please not here the cards are returned as objects. Not sure why this is happening.
The result I want :
[
{ id: 1, title: 'To Do', cards: [] }
{ id: 2, title: 'Doing', cards: [] }
{
id: 3,
title: 'Done',
cards: [ { id: 331, title: "Karan's Review" }, { id: 332, title: 'Karan' } ]
}
]
I need some help in understanding the reason behind/way around to resolve the issue.

how to group objects in array that are pointing at eact other

I have an array of objects, each object is pointing at another object in the "related" field.
I need to group the object with the related field so I can output them in groups on my website.
how can I group something like this
[
{
id: 1,
name: "dog",
related: "cat",
},
{
id: 2,
name: "cat",
related: "dog",
},
{
id: 3,
name: "shark",
related: "whale",
},
];
to this ?
[
[
{
id: 1,
name: "dog",
related: "cat",
},
{
id: 2,
name: "cat",
related: "dog",
},
],
[
{
id: 3,
name: "shark",
related: "whale",
},
],
];
Here i've tried this. It'll only work for two way binding relations.
const data = [
{
id: 1,
name: "dog",
related: "cat",
},
{
id: 2,
name: "cat",
related: "dog",
},
{
id: 3,
name: "shark",
related: "whale",
},
{
id: 4,
name: "whale",
related: "shark",
},
{
id: 5,
name: "test",
related: "none",
},
];
const categoriesData = (data) => {
const dataObj = data.reduce((obj, item) => {
if (!obj[item.name]) obj[item.related] = [item];
else if (obj[item.name] && obj[item.name][0].name === item.related)
obj[item.name].push(item);
return obj;
}, {});
return Object.values(dataObj);
};
console.log(categoriesData(data));
You can reduce it:
var data=[ { id: 1, name: "dog", related: "cat", }, { id: 2, name: "cat", related: "dog", }, { id: 3, name: "shark", related: "whale", }];
var result = Object.values(data.reduce((acc, elem)=>{
const key = [elem.name, elem.related].sort((a,b)=>a.localeCompare(b)).join('|');
acc[key] = [...(acc[key] || []), elem];
return acc;
},{}));
console.log(result);
this doesn't exactly answer your question, but is more of an "outside the box" solution. your data would be much simpler, straight forward and easy to manage if you simply add a "category" field.
[
{
id: 1,
name: "dog",
related: "cat",
category: 1,
},
{
id: 2,
name: "cat",
related: "dog",
category: 1,
},
{
id: 3,
name: "shark",
related: "whale",
category: 2,
},
{
id: 3,
name: "whale",
related: "shark",
category: 2,
},
];
One way of doing this would be to create a Map holding the animals. Then for each animal in this collection (that is not removed) create a group and remove it from the collection. Find related animals and add them to the same group, also removing them from the collection.
const animals = [
{ id: 1, name: "dog", related: "cat" },
{ id: 2, name: "cat", related: "dog" },
{ id: 3, name: "shark", related: "whale" },
];
const animalsMap = new Map(animals.map(animal => [animal.name, animal]));
const animalGroups = [];
for (let [,animal] of animalsMap) {
const group = Array.of(animal);
while (
animalsMap.delete(animal.name),
animal = animalsMap.get(animal.related)
) {
group.push(animal);
}
animalGroups.push(group);
}
console.log(animalGroups);
You could gather all relations first, then build groups of related values and then map the groups by filtering the data.
This approach works with longer chains of related objects.
function getGroups(k, group) {
if (seen.has(k)) return;
seen.add(k);
group.push(k);
relations[k].forEach(v => seen.has(v) || getGroups(v, group));
}
var data = [{ id: 1, name: "dog", related: "cat" }, { id: 2, name: "cat", related: "dog" }, { id: 3, name: "shark", related: "whale" }],
relations = {},
groups = [],
seen = new Set,
result;
data.forEach(({ name, related }) => {
if (!relations[name]) relations[name] = [];
relations[name].push(related);
if (!relations[related]) relations[related] = [];
relations[related].push(name);
});
Object.keys(relations).forEach(k => {
let group = [];
getGroups(k, group);
if (group.length) groups.push(group);
});
result = groups.map(group => data.filter(({ name }) => group.includes(name)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Constructing Tree View Object

I'd like to construct an Array Object for tree view in React Native.
Realm DB returns following rows as they have parent-child relation:
{
"0":{
"ID":3,
"name":"KITCHEN",
"parentID":2,
"createdBY":null,
"createdAT":null
},
"1":{
"ID":4,
"name":"BATHROOM",
"parentID":2,
"createdBY":null,
"createdAT":null
},
"2":{
"ID":5,
"name":"OIL",
"parentID":3,
"createdBY":null,
"createdAT":null
},
"3":{
"ID":6,
"name":"LIQUID",
"parentID":5,
"createdBY":null,
"createdAT":null
},
"4":{
"ID":7,
"name":"SOLID",
"parentID":5,
"createdBY":null,
"createdAT":null
}
}
Object should be look like this:
const treeData = [
{
key: 3,
label: 'KITCHEN',
nodes: [
{
key: '5',
label: 'OIL',
nodes: [
{
key: '6',
label: 'LIQUID',
},
{
key: '7',
label: 'SOLID',
},
],
},
],
},
{
key: 4,
label: 'BATHROOM',
},
];
My attempt was looping over all rows and get their IDs then in a nested loop checking the parentID with the ID and if any match occurs then adding that node to another object.
This only gives me the child/s of any parent.
for (var i = 0; i < rows.length; i++) {
let tempID = rows[i].ID
treeData = treeData.concat(rows[i])
for (var j = 0; j < rows.length; j++) {
let tempParentID = rows[j].parentID
if (tempID == tempParentID) {
subCategoryJson = subCategoryJson.concat(rows[j])
}
}
}
Problem is I am really not sure how to construct exactly the above Array Object.
PS. I'm trying to use following node module: https://www.npmjs.com/package/react-simple-tree-menu
You could store the keys and filter the parentt nodes for the result.
var data = { 0: { ID: 3, name: "KITCHEN", parentID: 2, createdBY: null, createdAT: null }, 1: { ID: 4, name: "BATHROOM", parentID: 2, createdBY: null, createdAT: null }, 2: { ID: 5, name: "OIL", parentID: 3, createdBY: null, createdAT: null }, 3: { ID: 6, name: "LIQUID", parentID: 5, createdBY: null, createdAT: null }, 4: { ID: 7, name: "SOLID", parentID: 5, createdBY: null, createdAT: null } },
tree = function (data) {
var t = {},
parents = {};
Object.values(data).forEach(({ ID: key, name: label, parentID }) => {
Object.assign(t[key] = t[key] || {}, { key, label });
t[parentID] = t[parentID] || { };
t[parentID].nodes = t[parentID].nodes || [];
t[parentID].nodes.push(t[key]);
parents[key] = true;
});
return Object
.keys(t)
.filter(k => !parents[k])
.flatMap(k => t[k].nodes);
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I would first loop and create a look up object so it is easy to reference parents and check if a parent exists.
After that I would loop over the data again and check to see if it has a parent, if it does, add a nodes property and push the element to it. If not add to the parent node.
const data = {
"0": {
"ID": 3,
"name": "KITCHEN",
"parentID": 2,
"createdBY": null,
"createdAT": null
},
"1": {
"ID": 4,
"name": "BATHROOM",
"parentID": 2,
"createdBY": null,
"createdAT": null
},
"2": {
"ID": 5,
"name": "OIL",
"parentID": 3,
"createdBY": null,
"createdAT": null
},
"3": {
"ID": 6,
"name": "LIQUID",
"parentID": 5,
"createdBY": null,
"createdAT": null
},
"4": {
"ID": 7,
"name": "SOLID",
"parentID": 5,
"createdBY": null,
"createdAT": null
}
}
const values = Object.values(data)
const lookup = values.reduce((obj, entry) => ({
[entry.ID]: entry,
...obj
}), {})
const result = values.reduce((arr, entry) => {
const parent = lookup[entry.parentID]
if (parent) {
parent.nodes = parent.nodes || []
parent.nodes.push(entry)
} else {
arr.push(entry)
}
return arr
}, [])
console.log(result)

JavaScript - Is there a simple way to get the values for every occurrence of a specific key in nested JSON

I have a JSON structure similar to this:
[
{
cells: [
{ id: "1", cellType: 3, widget: { id: 1, description: "myDesc"} },
{ id: "2", cellType: 4, widget: { id: 2, description: "myDesc2"} }
]
},
{
cells: [
{ id: "3", cellType: 5, widget: { id: 3, description: "myDesc3"} }
]
},
...
]
How do I get the value of every widget into a separate array using EcmaScript (or anything that's available in Angular 2+), and without using a library (including JQuery)? I need a final array like this:
[
{
id: 1,
description: "myDesc"
},
{
id: 2,
description: "myDesc2"
},
...
]
Update
(and thanks to #Felix Kling for the 1st part) - I found I can get all of the widgets with this:
JSON.parse(json)[0].forEach( c => c.cells.forEach( c2 => console.log(c2.widget)));
You can use .map() with .reduce()
let input = [
{
cells: [
{ id: "1", cellType: 3, widget: { id: 1, description: "myDesc"} },
{ id: "1", cellType: 4, widget: { id: 2, description: "myDesc2"} }
]
},
{
cells: [
{ id: "3", cellType: 5, widget: { id: 3, description: "myDesc3"} }
]
},
];
let result = input.reduce((result, current) => {
return result.concat(current.cells.map(x => x.widget));
}, [])
console.log(result);
You can use .map() and .concat() to get the desired result:
let data = [{
cells: [
{ id: "1", cellType: 3, widget: { id: 1, description: "myDesc"} },
{ id: "1", cellType: 4, widget: { id: 2, description: "myDesc2"} }
]}, {
cells: [
{ id: "3", cellType: 5, widget: { id: 3, description: "myDesc3"} }
]
}];
let result = [].concat(...data.map(({cells}) => cells.map(({widget}) => widget)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Normalizr with nested array of objects

I have a nested array of objects like this:
var matchs = [
{
id: 10689,
sport: 'Tennis',
players: [
{
id: 22,
name:'Rafa Nadal',
country: 'Spain',
odds: [
{id: 1, bookie_1: 1.60},
{id: 2, bookie_2: 1.61},
{id: 3, bookie_3: 1.62},
]
},
{
id: 23,
name:'Roger Federer',
country: 'Spain',
odds: [
{id: 4, bookie_1: 2.60},
{id: 5, bookie_2: 2.61},
{id: 6, bookie_3: 2.62},
]
}
]
},
{
id: 12389,
sport: 'Tennis',
players: [
{
id: 45,
name:'Fernando Verdasco',
country: 'Spain',
odds: [
{id: 7, bookie_1: 2.60},
{id: 8, bookie_2: 2.61},
{id: 9, bookie_3: 2.62},
]
},
{
id: 65,
name:'Andy Murray',
country: 'Spain',
odds: [
{id: 10, bookie_1: 1.60},
{id: 11, bookie_2: 1.61},
{id: 12, bookie_3: 1.62},
]
}
]
}
];
I want to use normalizr to simplify array and use with redux. I have read the Normalizr documentation but it has few examples and I do not know what I am doing wrong.
I have tried the following code without success. The result I get is an array with undefined.
import { normalize, schema } from 'normalizr';
const match = new schema.Entity('matchs');
const player = new schema.Entity('players');
const odd = new schema.Entity('odds');
match.define({
player: [player],
odd: [odd]
});
console.log(normalize(matchs, [match]));
I need something like this:
{
result: "123",
entities: {
"matchs": {
"123": {
id: "123",
players: [ "1","2" ],
odds: [ "1", "2" ]
}
},
"players": {
"1": { "id": "1", "name": "Rafa Nadal" },
"2": { "id": "2", "name": "Andy Murray" }
},
"odds": {
"1": { id: "1", "bookie_1": "1.20" }
"2": { id: "2", "bookie_2": "1.21" }
"3": { id: "3", "bookie_3": "1.22" }
}
}
}
I cannot find a straight solution using only normalizr, so my only choice is to pre-format the data before passing to the normalizer.
const preformattedData = data.map(sport => {
const oddArrays = sport.players.map(player => player.odds || []);
return {
...sport,
odds: [].concat.apply([], oddArrays)
}
})
const odd = new schema.Entity('odds')
const player = new schema.Entity('players',
{
odds: [ odd ]
}
)
const sport = new schema.Entity('sports',
{
players: [ player ],
odds: [odd]
}
)
const normalizedData = normalize(preformattedData, [ sport ]);
Demo: https://codesandbox.io/s/20onxowzwn
I think this is what you need
const odd = new schema.Entity('odds');
const player = new schema.Entity('players' , { odds: [ odd]});
const match = new schema.Entity('matchs', {players: [player]});
but the result will be different because your json it is structured like this, I mean, the odds key is child of players, not of matches, therefore the result will be this way.
Just take a look at the console
Here is a solution with latest version of normalizr
const odds = new schema.Entity("odds");
const players = new schema.Entity("players", {
odds: [odds]
});
const matches = new schema.Entity("matches", { players: [players] });
const normalizedData = normalize(data, [matches]);
It would group data in your question as
{
"entities": {
"odds": {
"1": {
"id": 1,
"bookie_1": 1.6
}
},
"players": {
"22": {
"id": 22,
"name": "Rafa Nadal",
"country": "Spain",
"odds": [
1,
2,
3
]
}
},
"matches": {
"10689": {
"id": 10689,
"sport": "Tennis",
"players": [
22,
23
]
}
}
},
"result": [
10689
]
}
You can achieve your desired result by tweaking the process and merge strategies. I don't have time to do the leg work for you, but I explain the approach in detail here:
https://medium.com/#JustinTRoss/normalizing-data-into-relational-redux-state-with-normalizr-47e7020dd3c1

Categories

Resources