Sort-Index from nested JSON with Javascript - javascript

How can I recursively add a sort key to an infinite hierarchy like this:
[
{
"id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"name": "A",
"parent_id": null,
"sortNr": 1,
"children": [
{
"id": "07E556EE-F66F-49B5-B5E4-54AFC6A4DD9F",
"name": "A-C",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 3,
"children": []
},
{
"id": "8C63981E-0D30-4244-94BE-658BAAF40EF3",
"name": "A-A",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 1,
"children": [
{
"id": "0BA32F23-A2CD-4488-8868-40AD5E0D3F09",
"name": "A-A-A",
"parent_id": "8C63981E-0D30-4244-94BE-658BAAF40EF3",
"sortNr": 1,
"children": []
}
]
},
{
"id": "17A07D6E-462F-4983-B308-7D0F6ADC5328",
"name": "A-B",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 2,
"children": []
}
]
},
{
"id": "64535599-13F1-474C-98D0-67337562A621",
"name": "B",
"parent_id": null,
"sortNr": 2,
"children": []
},
{
"id": "1CE38295-B933-4457-BBAB-F1B4A4AFC828",
"name": "C",
"parent_id": null,
"sortNr": 3,
"children": [
{
"id": "D1E02274-33AA-476E-BA31-A4E60438C23F",
"name": "C-A",
"parent_id": "1CE38295-B933-4457-BBAB-F1B4A4AFC828",
"sortNr": 1,
"children": [
{
"id": "76A8259C-650D-482B-91CE-D69D379EB759",
"name": "C-A-A",
"parent_id": "D1E02274-33AA-476E-BA31-A4E60438C23F",
"sortNr": 1,
"children": []
}
]
}
]
}
]
I want to get a sortable index.
For example 0000.0001.0003 or 0001.0003 for node A-C.
The function for leadingZeroes is
function fillZeroes (num) {
var result = ('0000'+num).slice(-4);
if (num===null){
return result
} else {
return '0000';
}
}
It should be sorted by sort number in each level of hierarchy, the sort number should be set newly every time, because I want to do rearrangement by setting it 1,5 to insert it between 1 and 2 (later for drag and drop capability). so 1;1,5;2 should become 1;2;3 and can then be translated to a sort-index like above.
I will also need it for indentation and breadcrumb-stuff.
How do I insert the proper sort-index to each object ?
The question is mainly about the recursion part. I am quite new to JavaScript
Thanks a lot

Based on great answer by #georg. A bit adjusted solution based on sortNr object property.
You can run it straight as is with json being your object. The sort index is written into sortOrder property.
// Mutates the given object in-place.
// Assigns sortOrder property to each nested object
const indexJson = (json) => {
const obj = {children: json};
const format = (xs) => xs.map(x => pad(x, 4)).join('.');
const pad = (x, w) => (10 ** w + x).toString().slice(-w);
const renumber = (obj, path) => {
obj.path = path;
obj.sortOrder = format(path);
obj.children.slice()
.sort((obj1, obj2) => obj1.sortNr - obj2.sortNr)
.forEach((c, n) => renumber(c, path.concat(n+1)));
};
renumber(obj, []);
};
indexJson(json);
console.log(JSON.stringify(json, null, 2));

Basically
let renumber = (obj, path) => {
obj.path = path
obj.children.forEach((c, n) => renumber(c, path.concat(n)))
}
renumber({children: yourData}, [])
this creates a path property, which is an array of relative numbers. If you want to format it in a special way, then you can do
obj.path = format(path)
where format is like
let format = xs => xs.map(pad(4)).join(',')
let pad = w => x => (10 ** w + x).toString().slice(-w)

Related

Nested array issue in JavaScript

I have the following array
Array["MyArray",
{
"isLoaded":true,
"items":
[{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}],
"coupons":null
}
]
I need to load product names and their quantity from the array.
const result = [key, value].map((item) => `${item.name} x ${item.quantity}`);
Here's one possible way to achieve the desired result:
const getProductsAndQuantity = ([k , v] = arr) => (
v.items.map(it => `${it.name} x ${it.quantity}`)
);
How to use it within the context of the question?
localforage.iterate(function(value, key, iterationNumber) {
console.log([key, value]);
const val2 = JSON.parse(value);
if (val2 && val2.items && val2.items.length > 0) {
console.log(val2.items.map(it => `${it.name} x ${it.quantity}`).join(', '))
};
});
How it works?
Among the parameters listed in the question ie, value, key, iterationNumber, only value is required.
The above method accepts the key-value pair as an array (of 2 elements) closely matching the console.log([key, value]); in the question
It uses only v (which is an object). On v, it accesses the prop named items and this items is an Array.
Next, .map is used to iterate through the Array and return each product's name and quantity in the desired/expected format.
Test it out on code-snippet:
const arr = [
"MyArray",
{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}
];
const getProductsAndQuantity = ([k, v] = arr) => (
v.items.map(
it => `${it.name} x ${it.quantity}`
)
);
console.log(getProductsAndQuantity());
I understood. You should learn about array methods such as map, filter, reduce. Here you go...
const items = [{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}];
const result = items.map((item) => `${item.name} x ${item.quantity}`);
console.log(result);
I think I understand the question to say that the input is an array of objects, each containing an array of items. The key is that a nested array requires a nested loop. So, we iterate the objects and their internal items (see the lines commented //outer loop and // inner loop below)
Also, half-guessing from the context, it looks like the that the OP aims to assemble a sort of invoice for each object. First a demo of that, (and see below for the version simplified to exactly what the OP asks)...
const addInvoice = obj => {
let total = 0;
// inner loop
obj.invoice = obj.items.map(i => {
let subtotal = i.quantity * i.price;
total += subtotal
return `name: ${i.name}, qty: ${i.quantity}, unit price: ${i.price}, subtotal: ${subtotal}`
});
obj.invoice.push(`invoice total: ${total}`);
}
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
// outer loop
objects.forEach(addInvoice);
console.log(objects);
If my guess about the goal went to far, just remove the unit price, subtotal and total lines from the invoice function...
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
const summaryString = obj => {
return obj.items.map(i => `${i.name}, ${i.quantity}`);
}
const strings = objects.map(summaryString);
console.log(strings);

How to get distinct value from an array of objects containing array

I am trying to get an array of distinct values from the data structure below. I tried using reduce and object keys with no luck. What can I try next?
Data:
var data = [{
"id": 1,
"Technologies": ["SharePoint", "PowerApps"]
},
{
"id": 2,
"Technologies": ["SharePoint", "PowerApps", "SomethingElse"]
},
{
"id": 3,
"Technologies": ["SharePoint"]
},
{
"id": 4,
"Technologies": ["PowerApps"]
},
{
"id": 5,
"Technologies": null
}
]
Finished result should look like:
var distintValues = ["PowerApps", "SharePoint", "SomethingElse", null]
My attempt:
https://codepen.io/bkdigital/pen/MWEoLXv?editors=0012
You could use .flatMap() with a Set. .flatMap allows you to map each object's technology to one resulting array, and the Set allows you to remove the duplicates. With the help of optional chaining ?., you can also keep the null value (so it doesn't throw when accessing Technologies) like so:
const data = [{ "id": 1, "Technologies": ["SharePoint", "PowerApps"] }, { "id": 2, "Technologies": ["SharePoint", "PowerApps", "SomethingElse"] }, { "id": 3, "Technologies": ["SharePoint"] }, { "id": 4, "Technologies": ["PowerApps"] }, { "id": 5, "Technologies": null } ];
const res = [...new Set(data.flatMap(obj => obj?.Technologies))];
console.log(res);
[...new Set(
data
.map(v => Array.isArray(v.Technologies) ? v.Technologies : [v.Technologies])
.reduce((t, v) => [...t, ...v], [])
)];
I tried to solve this through JS. Here is my code:
const data = [{
"id": 1,
"Technologies": ["SharePoint", "PowerApps"]
}, {
"id": 2,
"Technologies": ["SharePoint", "PowerApps", "SomethingElse"]
}, {
"id": 3,
"Technologies": ["SharePoint"]
}, {
"id": 4,
"Technologies": ["PowerApps"]
}, {
"id": 5,
"Technologies": null
}]
const distintValues = [];
for (let element of data) {
if (element.Technologies != null) {
for (let elem of element.Technologies) {
if (!distintValues.includes(elem)) {
distintValues.push(elem);
}
}
}
}
console.log(distintValues);
In your attempt you tried to do it with reduce so here is how I would do it
var data = [{
"id": 1,
"Technologies": ["SharePoint", "PowerApps"]
},
{
"id": 2,
"Technologies": ["SharePoint", "PowerApps", "SomethingElse"]
},
{
"id": 3,
"Technologies": ["SharePoint"]
},
{
"id": 4,
"Technologies": ["PowerApps"]
},
{
"id": 5,
"Technologies": null
}
];
const objAsArray = Object.keys(data) // first we get the keys
.map(key => data[key]) // then we map them to their value
const technologyMap = objAsArray.reduce((acc, data) => {
// if the entry has technologies we set the key in the accumulation object to true
if (data.Technologies) {
data.Technologies.forEach(tech => acc[tech] = true)
}
return acc;
}, {})
// at the very end we get the keys of the accumulation object
const uniqueTechnologies =
Object.keys(
technologyMap
)

Reverse Traverse a hierarchy

I have a hierarchy of objects that contain the parent ID on them. I am adding the parentId to the child object as I parse the json object like this.
public static fromJson(json: any): Ancestry | Ancestry[] {
if (Array.isArray(json)) {
return json.map(Ancestry.fromJson) as Ancestry[];
}
const result = new Ancestry();
const { parents } = json;
parents.forEach(parent => {
parent.parentId = json.id;
});
json.parents = Parent.fromJson(parents);
Object.assign(result, json);
return result;
}
Any thoughts on how to pull out the ancestors if I have a grandchild.id?
The data is on mockaroo curl (Ancestries.json)
As an example, with the following json and a grandchild.id = 5, I would create and array with the follow IDs
['5', '0723', '133', '1']
[{
"id": "1",
"name": "Deer, spotted",
"parents": [
{
"id": "133",
"name": "Jaime Coldrick",
"children": [
{
"id": "0723",
"name": "Ardys Kurten",
"grandchildren": [
{
"id": "384",
"name": "Madelle Bauman"
},
{
"id": "0576",
"name": "Pincas Maas"
},
{
"id": "5",
"name": "Corrie Beacock"
}
]
},
There is perhaps very many ways to solve this, but in my opinion the easiest way is to simply do a search in the data structure and store the IDs in inverse order of when you find them. This way the output is what you are after.
You could also just reverse the ordering of a different approach.
I would like to note that the json-structure is a bit weird. I would have expected it to simply have nested children arrays, and not have them renamed parent, children, and grandchildren.
let data = [{
"id": "1",
"name": "Deer, spotted",
"parents": [
{
"id": "133",
"name": "Jaime Coldrick",
"children": [
{
"id": "0723",
"name": "Ardys Kurten",
"grandchildren": [
{
"id": "384",
"name": "Madelle Bauman"
},
{
"id": "0576",
"name": "Pincas Maas"
},
{
"id": "5",
"name": "Corrie Beacock"
}
]
}]
}]
}]
const expectedResults = ['5', '0723', '133', '1']
function traverseInverseResults(inputId, childArray) {
if(!childArray){ return }
for (const parent of childArray) {
if(parent.id === inputId){
return [parent.id]
} else {
let res = traverseInverseResults(inputId, parent.parents || parent.children || parent.grandchildren) // This part is a bit hacky, simply to accommodate the strange JSON structure.
if(res) {
res.push(parent.id)
return res
}
}
}
return
}
let result = traverseInverseResults('5', data)
console.log('results', result)
console.log('Got expected results?', expectedResults.length === result.length && expectedResults.every(function(value, index) { return value === result[index]}))

Javascript - Get occurence of json array with ESLINT 6

I can't set up an algo that counts my occurrences while respecting ESlint's 6 standards in javascript.
My input table is :
[
{
"id": 2,
"name": "Health",
"color": "0190fe"
},
{
"id": 3,
"name": "Agriculture",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
}
]
And i want to get :
{"Urban planning": 2, "Health": 1, ...}
But that does not work with ESLINT / REACT compilation...
This is my code :
const jsonToIterate = *'MyPreviousInputJson'*
const names = []
jsonToIterate.map(item => (names.push(item.name)))
const count = []
names.forEach(item => {
if (count[item]){
count.push({text: item, value: 1})
} else {
count.forEach(function(top){top.text === item ? top.value =+ 1 : null})
}
})
Thank you so much
Well, you want an object in the end, not an array, so count should be {}. I also wouldn't use map if you're not actually returning anything from the call. You can use reduce for this:
let counts = topicsSort.reduce((p, c, i, a) => {
if (!p.hasOwnProperty(c.name)) p[c.name] = 0;
p[c.name]++;
return p;
}, {});
I'm half exppecting someone to close this as a duplicate because all you've asked for is a frequency counter. But here's an answer anyway:
const jsonToIterate = *'MyPreviousInputJson'*;
const names = {};
jsonToIterate.map(obj => {
if(obj.name in names){
names[obj.name]++
}
else{
names[obj.name] = 1;
}
})

In JSON Object parent key name remove and Include its inner content

This is my JSON Object in this i want to remove "item" key from json and want to keep its inner object include with "children" and its tree like JOSN. How can I do this?
[
{
"item": {
"id": 11865,
"parentid": null,
"levelid": 63,
"name": "Total"
},
"children": [
{
"item": {
"id": 10143,
"parentid": 11865,
"levelid": 19,
"name": "Productive"
}
}
]
}
]
If I'm understanding what you want your object to look like after correctly, then this should do the trick:
var arrayOfObjects = [
{
"item": {
"id": 11865,
"parentid": null,
"levelid": 63,
"name": "Total"
},
"children": [
{
"item": {
"id": 10143,
"parentid": 11865,
"levelid": 19,
"name": "Productive"
}
}
]
}
]
arrayOfObjects.forEach(function(obj) {
obj.id = obj.item.id;
obj.parentid = obj.item.parentid;
obj.levelid = obj.item.levelid;
obj.name = obj.item.name;
delete obj.item;
});
All this is doing is manually moving the data from obj.item to obj and then deleting obj.item entirely.
I would do this:
//your original array of objects
var array = [{
"item": {
"id": 11865,
"parentid": null,
"levelid": 63,
"name": "Total"
},
"children": [
{
"item": {
"id": 10143,
"parentid": 11865,
"levelid": 19,
"name": "Productive"
}
}
]
}, ...];
array.forEach(function(parent) {
flattenKey(parent, 'item');
});
function flattenKey(parent, keyName) {
var section = parent[keyName];
var child = section ? section : {};
var keys = Object.keys(child);
keys.forEach(function(key) {
parent[key] = child[key];
})
delete parent[keyName];
}
basically, the function flattenKey would flatten any key for a given object (given its key).
logic is similar to other solutions here: iterate through child keys and assign their values to the parent object (flattening).
then it deletes the child key after step 1.
try
objArray = objArray.map( function(value){
var item = value.item;
for( var key in item )
{
value[key] = item[key];
}
delete value.item;
return value;
});
DEMO
Explanation
1) Use map to iterate on each item (value) of this given array objArray.
2) Get the item property of value, assign them to value directly
3) Finally delete the item property of the value.
Faster option
objArray = objArray.map( function(value){
var item = value.item;
var itemKeys = Object.keys(item);
for( var counter = 0; counter < itemKeys.length; counter++ )
{
value[itemKeys[counter]] = item[itemKeys[counter]];
}
delete value.item;
return value;
});
You can use a recursion, which keeps the content of item and adds the children property as well.
function delItem(a, i, aa) {
var children = a.children;
if (a.item) {
aa[i] = a.item;
aa[i].children = children;
delete a.item;
}
Array.isArray(children) && children.forEach(delItem);
}
var array = [{ "item": { "id": 11865, "parentid": null, "levelid": 63, "name": "Total" }, "children": [{ "item": { "id": 10143, "parentid": 11865, "levelid": 19, "name": "Productive" } }] }];
array.forEach(delItem);
document.write('<pre>' + JSON.stringify(array, 0, 4) + '</pre>');

Categories

Resources