Normalizr with nested array of objects - javascript

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

Related

How do I check that the correct objects are being returned via a function (expect function returns [Function Anonymous])?

I have a function:
const sort =
(pets,attribute) =>
_(pets)
.filter(pets=> _.get(pets, attribute) !== null)
.groupBy(attribute)
.value()
Some data:
const pets= [{
id: 1,
name: 'snowy',
},
{
id: 2,
name: 'quacky',
},
{
id: 3,
name: 'snowy',
age: 5,
},
{
id: null,
name: null,
age: null
}
]
const attribute = 'name'
I am currently trying to write some Jest unit tests for this, that tests if the function returns the correct resultant object after being sorted based off an attribute.
The result of:
sort(pets,attribute) is something like this:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Is there a way I can do a expect to match the two objects snowy and quacky here?
The thing I want to test for is that the objects are being correctly grouped by the key.
I've tried using something like
const res = sort(users,key)
expect(res).toEqual(
expect.arrayContaining([
expect.objectContaining({'snowy' : [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5 } ]},
expect.objectContaining({'quacky' : [ { id: 2, name: 'quacky' } ]}))
])
)
which doesn't seem to work, the received output seems to output:
Expected: ArrayContaining [ObjectContaining {"snowy": [{"id": 1, "name": "snowy"}, {"age": 5, "id": 3, "name": "snowy"}]}]
Received: [Function anonymous]
I am unsure what the best method to test this kind of function is either so advice on that would be appreciated.
If this is what your arrangeBy() returns:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Then you can just do:
const expected = {
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
const res = arrangeBy(users,key)
expect(res).toEqual(expected)
But looking at your Error message I guess you have something else mixed up. In the beginning you listed the implementation of a sort function which seems to not be used in the test. Where is arrangeBy coming from now.
Please provide more code examples.

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)

Grouping array data according to a id

With a API call i'm receiving a response like below
[
{
stationId: "10"
name: "Jinbaolai"
group: {id: "18", stationGroupName: "Ali"}
},
{
stationId: "13"
name: "Stack"
group: {id: "18", stationGroupName: "Ali"}
},
{
stationId: "20"
name: "Overflow"
group: {id: "19", stationGroupName: "Baba"}
}
]
As you can see first two records consist with the same group. I want to group these data according to the group. So for example it should look like this
[
{
groupId: "18",
groupName : "Ali",
stations : [
{
stationId: "10",
name: "Jinbaolai"
},
{
stationId: "13",
name: "Stack"
}
]
},
{
groupId: "19",
groupName : "Baba",
stations : [
{
stationId: "20",
name: "Overflow"
},
]
}
]
I want to do the grouping logic in my reducer where i also set the full data array that is shown in the beginning of the question.
case EVC_SUCCESS:
return {
...state,
chargingStations: action.evcData.chargingStations,
chargingStationGroups: //This is where my logic should go. ('action.evcData.chargingStations' is the initial data array)
tableLoading: false
}
How can i do this? I tried something using filter but not successful.
The best way to do this is to use Array.prototype.reduce()
Reduce is an aggregating function where you put in an array of something and get a single vaule back.
There may be a starting value as last parameter like I used {}.
The signature is reduce(fn, startingValue) where fn is a function taking two parameters aggregate and currentValue where you return the aggregate in the end.
const groupData = (data)=> {
return Object.values(data.reduce((group,n)=>{
if (!group[n.group.id]){
group[n.group.id] = {
groupId:n.group.id,
groupName: n.group.stationGroupName,
stations:[]}
}
group[n.group.id].stations.push({
stationID: n.stationId,
name: n.name
})
return group;
}, {}))
}
Here is the fiddle
A simple JS algorithm can do that for you
const list = [
{
stationId: "10",
name: "Jinbaolai",
group: {id: "18", stationGroupName: "Ali"}
},
{
stationId: "13",
name: "Stack",
group: {id: "18", stationGroupName: "Ali"}
},
{
stationId: "20",
name: "Overflow",
group: {id: "19", stationGroupName: "Baba"}
}
];
const groups = {};
list.forEach((item) => {
const groupId = item.group.id;
const group = groups[groupId] || {groupId: groupId, groupName: item.group.stationGroupName, stations: []};
group.stations.push({stationId: item.stationId, name: item.name});
groups[groupId] = group;
});
const groupedArray = Object.keys(groups).map((groupId) => groups[groupId]);
console.log(groupedArray); // This will be the output you want
I think chaining multiple functions will work.
const stations = [
{
"stationId": 10,
"name": "Jinbaolai",
"group": {"id": "18", "stationGroupName": "Ali"}
},
{
"stationId": 13,
"name": "Stack",
"group": {"id": 18, "stationGroupName": "Ali"}
},
{
"stationId": 20,
"name": "Overflow",
"group": {"id": "19", "stationGroupName": "Baba"}
}
]
const groups = _.chain(stations)
.groupBy((station) => { return station.group.id })
.map((values, key) => {
return {
"groupId": _.first(values).group.id,
"groupName": _.first(values).group.id,
"stations": _.map(values,(value)=>{ return { "stationId": value.stationId, "name": value.name } })
}
})
console.log("groups",groups)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>

Javascript Object Tranformation

I am using the map to transforming the JSON. Instead of Single object, I am getting an Array of data.
Snipt Shown Below.
I am trying to achieve following JSON
{
"data": {
"test1": {
"abc": {
"size": "big",
"id": "1"
}
},
"test2": {
"abc": {
"size": "small",
"id": "2"
}
}
}
}
But getting following JSON
{
"data": [{
"test1": {
"abc": {
"size": "big",
"id": "1"
}
}
}, {
"test2": {
"abc": {
"size": "big",
"id": "2"
}
}
}]
}
Here is my code.
const test = [{ id: 'ddec9c7f-95aa-4d07-a45a-dcdea96309a9',
ad_id: 'test1',
country: 'ID',
abc: { size: 'big', id: '1' },
},
{ id: 'ddec9c7f-95aa-4d07-a45a-dcdea96309a9',
ad_id: 'test2',
country: 'ID',
abc: { size: 'small', id: '2' },
},
];
const transformedTest = test.map(result =>
({ [result.ad_id]: { abc: result.abc } }));
const data = { data: transformedTest };
console.log(JSON.stringify(data));
Any help will be appreciated
Try this:
const test = [
{
"id": "ddec9c7f-95aa-4d07-a45a-dcdea96309a9",
"ad_id": "test1",
"country": "ID",
"abc": {
"size": "big",
"id": "1"
}
},
{
"id": "ddec9c7f-95aa-4d07-a45a-dcdea96309a9",
"ad_id": "test2",
"country": "ID",
"abc": {
"size": "big",
"id": "2"
}
}
];
const result = test.reduce((acc,{ad_id, abc})=> (acc.data[ad_id] = {abc}, acc),{data:{}})
console.log(result);
const transformedTest = test.reduce((pv, result) =>
({...pv, [result.ad_id]: { abc: result.abc } }), {});
You could take Object.fromEntries and map new key/value pairs and get a new object from it.
const
test = [{ id: 'ddec9c7f-95aa-4d07-a45a-dcdea96309a9', ad_id: 'test1', country: 'ID', abc: { size: 'big', id: '1' } }, { id: 'ddec9c7f-95aa-4d07-a45a-dcdea96309a9', ad_id: 'test2', country: 'ID', abc: { size: 'small', id: '2' } }];
transformedTest = Object.fromEntries(test.map(({ ad_id, abc} ) => [ad_id, { abc }]));
data = { data: transformedTest };
console.log(data);
Loop through the array using a simple for...of loop and create a data object. Use Shorthand property names to create the abc nesting:
const test=[{id:'ddec9c7f-95aa-4d07-a45a-dcdea96309a9',ad_id:'test1',country:'ID',abc:{size:'big',id:'1'},},{id:'ddec9c7f-95aa-4d07-a45a-dcdea96309a9',ad_id:'test2',country:'ID',abc:{size:'small',id:'2'}}]
const data = {}
for(const { ad_id, abc } of test) {
data[ad_id] = { abc }
}
console.log({ data })
.as-console-wrapper { min-height: 100%; }
The function map returns an array rather than a specific key-value object, an alternative is using the function reduce to build the desired output.
const test = [{ id: 'ddec9c7f-95aa-4d07-a45a-dcdea96309a9', ad_id: 'test1', country: 'ID', abc: { size: 'big', id: '1' },},{ id: 'ddec9c7f-95aa-4d07-a45a-dcdea96309a9', ad_id: 'test2', country: 'ID', abc: { size: 'big', id: '2' },}],
data = { data: test.reduce((a, result) =>
({...a, [result.ad_id]: { abc: result.abc } }), Object.create(null))};
console.log(JSON.stringify(data, null, 2));
.as-console-wrapper { min-height: 100%; }

How to define schema for recursive model with Normalizr

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
]
}

Categories

Resources