Convert flat json structure to hierarchical - javascript

I cannot get my head around this problem.
I have a settings object which looks like
const setting = [
{
Key: 'root/',
Value: null,
},
{
Key: 'root/second-root/',
Value: null,
},
{
Key: 'root/second-root/names/',
Value: null,
},
{
Key: 'root/second-root/names/capital-letter',
Value: true,
},
{
Key: 'root/second-root/countries/',
Value: null,
},
{
Key: 'root/second-root/countries/enabledcountries',
Value: 'US,UK,DK',
},
{
Key: 'root/second-root/countries/async',
Value: 'true',
},
{
Key: 'root/second-root/countries/manual',
Value: 'true',
},
{
Key: 'root/second-root/countries/limit',
Value: '4200',
},
{
Key: 'root/second-root/names/onyl-last-name',
Value: false,
},
];
I need to convert it to look like
const wantedResult = [
{
'root': {
'Value': null,
'second-root': {
'Value': null,
'names': {
'Value': null,
'capital-letter': {
'Value': true,
}
'onyl-last-name': {
'Value': false,
}
},
'countries': {
'Value': null,
'enabledcountries': {
Value: 'US,UK,DK',
},
'async': {
Value: 'true',
},
'manual': {
Value: 'true',
},
'limit': {
Value: '4200',
}
}
}
}
}
];
The it is the Key property which controls the hierarchy. If it ends with a / the item is a directory else it is a value.
Problem is that the flat structure doesnt have to return the items in a correct order. Like in the example, the last item is 'root/second-root/names/onyl-last-name', even though the names hiarchy was in the beginning of the flat structure.
I have tried a form of array.reduce but get stuck every time. Could someone please help me.

You could iterate the array and take the value without the last slash and split it as path to the object for assigning the value.
If necessary put the result in an array.
In forEach works
a destructuring assignment for getting the key and value out of the object,
a replacement which looks for a slash at the end of the string with an empty string,
a splitting of the string by slash, it returns an array with strings without slashes,
using Array#reduce with the result object as accumulator.
Inside it uses a default pattern with a logical OR || which check if the left side is a truthy value and if not, it assigns an object. This value is returned for the check with the next key.
at the end of the iteration it retuns an object reference and then the value gets assigned.
var setting = [{ Key: 'root/', Value: null }, { Key: 'root/second-root/', Value: null }, { Key: 'root/second-root/names/', Value: null }, { Key: 'root/second-root/names/capital-letter', Value: true }, { Key: 'root/second-root/countries/', Value: null }, { Key: 'root/second-root/countries/enabledcountries', Value: 'US,UK,DK' }, { Key: 'root/second-root/countries/async', Value: 'true' }, { Key: 'root/second-root/countries/manual', Value: 'true' }, { Key: 'root/second-root/countries/limit', Value: '4200' }, { Key: 'root/second-root/names/onyl-last-name', Value: false }],
result = {};
setting.forEach(({ Key, Value }) => Key
.replace(/\/$/, '')
.split('/')
.reduce((o, k) => o[k] = o[k] || {}, result).Value = Value
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How can I extract and pair the values of an array based object

I'm trying to create a String based upon an object consisting of several key-value pairs.
Example:
[{ name: 'cookie1', value: 'false' },
{ name: 'cookie2', value: '123' },
{ name: 'cookie3',value: 'abc'}]
What I'm trying to achieve (string):
cookie1: false, cookie2: 123, cookie3: abc
I've tried to extract just the val using map like this (played around moving around values):
var output = cookies.map(d => {
return {
"name": d.name,
"value": d.value,
}
})
One way to do this is to map the array of objects into name: value strings and then join them with , :
const data = [{ name: 'cookie1', value: 'false' },
{ name: 'cookie2', value: '123' },
{ name: 'cookie3',value: 'abc'}]
const result = data.map(({ name, value }) => `${name}: ${value}`).join(', ')
console.log(result)

Object gets duplicated inside array

This is the result I want to achieve
dataset: [
dataset: [
{
seriesname: "",
data: [
{
value: "123",
},
{
value: "123",
},
]
},
]
]
My problem right now is that the second dataset gets duplicated.
This is how I am setting it (val is an integer and allYears is an array of integers):
this.grphColumn.dataSource.dataset[0].dataset = this.allYears.map(el => {
return {
seriesname: "Planned",
data: [{value: val}, {value: val}]
}
});
How can I make it so the dataset doesn't get duplicated?
You have to map the values separately, if you dont want the seriesName to be Repeated..
const yearsMap = this.allYears.map((el) => { return { value: el } });
this.grphColumn.dataSource.dataset[0].dataset = {
seriesname: "Planned",
data: yearsMap
}

How to return a new array of object from object nested props with reduce in JavaScript

I'm trying to return a new array of objects from the nested properties of another object. This is my current object:
const teams = {
GRYFFINDOR: {
display: 'Gryffindor',
channel: ['#team-gryffindor'],
ui: 'lion',
},
HUFFLEPLUFF: {
display: 'Hufflepuff',
channel: ['#team-hufflepuff'],
ui: '', // empty string on purpose
},
SLYTHERIN: {
display: 'Slytherin',
channel: ['#team-slytherin'],
ui: 'snake',
},
}
Now, I'm trying to return an array of objects like so:
[
{ value: 'lion' },
{ value: '' },
{ value: 'snake' },
]
This is what I have tried:
const teamsUI = [Object.keys(teams).reduce((carry, item) => {
return {...carry, ...{ ['value']: teams[item].ui }}
}, {})];;
However, I get this:
console.log(teamsUI); // returns [ { value: 'snake' }]
What am I doing wrong?
You could do like this:
const teams = {
GRYFFINDOR: {
display: 'Gryffindor',
channel: ['#team-gryffindor'],
ui: 'lion',
},
HUFFLEPLUFF: {
display: 'Hufflepuff',
channel: ['#team-hufflepuff'],
ui: '', // empty string on purpose
},
SLYTHERIN: {
display: 'Slytherin',
channel: ['#team-slytherin'],
ui: 'snake',
},
}
const result = Object.values(teams).map(({ ui }) => ({ value: ui }));
console.log(result);
Beside nested result structure, you could take the values and destructure the wanted property formapping.
const
teams = { GRYFFINDOR: { display: 'Gryffindor', channel: ['#team-gryffindor'], ui: 'lion' }, HUFFLEPLUFF: { display: 'Hufflepuff', channel: ['#team-hufflepuff'], ui: '' }, SLYTHERIN: { display: 'Slytherin', channel: ['#team-slytherin'], ui: 'snake' } },
result = Object.values(teams).map(({ ui: value }) => ({ value }));
console.log(result);
Here is the shortest way to achieve what you want
Object.keys(teams).map(t=>({value: teams[t].ui}))
Now what are you doing wrong?
Take a look at this part
Object.keys(teams).reduce((carry, item) => {
return {...carry, ...{ ['value']: teams[item].ui }}
}, {});
You're reducing your object to a single value with the same key called value. This means that the value of teams[item].ui of the current object in the reduce function will override the previous and hence you'll end up with the last object of that reduce function. You're finally wrapping this last object in an array which gives you what you have.
A better way to achieve that will be to do something like this
Object.keys(teams).reduce((carry, item, index)=>{
carry[index] = { value: teams[item].ui };
return carry;
}, []);

How to extract paths to 'enabled' objects in nested object arrays

I'm a novice to recursion and I have a JSON structure with arrays of nested objects. Some of these objects have a boolean enabled: true. I'm trying to figure out how to extract the paths to all enabled objects and their children.
I tried both cleaning up the original object by removing unused paths but I got lost in accessing the parents. I also tried building a separate array of paths using dot-notation, as I can probably build a new nested object from that. My latest attempt at the dot-notation extract:
const sourceData = {
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Floors"
},
{ title: "Windows", enabled: true },
{ title: "Ceilings", enabled: true }
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
},
{
title: "Roof"
}
]
},
{
title: "Gardening"
}
]
};
function getEnabledPaths(level, acc) {
for (const tag of level.tags) {
if (tag.enabled) {
return tag.title;
} else if (tag.hasOwnProperty("tags")) {
var path = this.getEnabledPaths(tag);
if (path) acc.push(tag.title + "." + path);
}
}
return acc;
}
console.log(getEnabledPaths(sourceData, []));
I only get:
[
"Cleaning.Windows",
"Maintenance.Walls"
]
I would ideally end up with something like this:
[
'Work.Cleaning.Windows',
'Work.Cleaning.Ceilings',
'Work.Maintenance.Walls.Brickwall',
'Work.Maintenance.Walls.Wooden Wall'
]
In a perfect world (but I tried for days and went back to getting the dot notation results):
{
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Windows",
enabled: true
},
{
title: "Ceilings",
enabled: true
}
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
}
]
}
]
};
The key to the recursion function is to both a) deal with children and b) the item itself.
Here's my take, which seems to work:
const sourceData = {title:"Work",tags:[{title:"Cleaning",tags:[{title:"Floors"},{title:"Windows",enabled:true},{title:"Ceilings",enabled:true}]},{title:"Maintenance",tags:[{title:"Walls",enabled:true,tags:[{title:"Brickwall"},{title:"Woodenwall"}]},{title:"Roof"}]},{title:"Gardening"}]};
function itemFilter(item) {
// enabled? done with this item
if (item.enabled) return item;
// not enabled and no tags? set to null
if (!item.tags) return null;
// filter all children, remove null children
item.tags = item.tags.map(child => itemFilter(child)).filter(child => child);
return item;
}
console.log(itemFilter(sourceData));
.as-console-wrapper {
max-height: 100vh !important;
}
You could pass enabled parameter down to lower levels of recursion if true value is found on some of the upper levels and based on that add path to the results or not.
const data ={"title":"Work","tags":[{"title":"Cleaning","tags":[{"title":"Floors"},{"title":"Windows","enabled":true},{"title":"Ceilings","enabled":true}]},{"title":"Maintenance","tags":[{"title":"Walls","enabled":true,"tags":[{"title":"Brickwall"},{"title":"Wooden wall"}]},{"title":"Roof"}]},{"title":"Gardening"}]}
function paths(data, prev = '', enabled = false) {
const result = [];
prev += (prev ? "." : '') + data.title;
if (!enabled && data.enabled) enabled = true;
if (!data.tags) {
if (enabled) {
result.push(prev);
}
} else {
data.tags.forEach(el => result.push(...paths(el, prev, enabled)))
}
return result;
}
const result = paths(data)
console.log(result)

What does these ES6 syntaxes really change?

Trying to learn some functional javascript and es6 concepts.
I have an array
var _ = require('underscore');
var raw =[
{
key :"name",value:"henry"
},
{
key :"age",value:"old"
},
{
key :"food",value:"matooke"
},
{
key :"kids",value:"Acacia"
},
{
key :"garbageA",value:"kasailoA"
},
{
key :"garbageB",value:"kasasiroB"
},
]
I am trying to filter out data with garbage keys . I have two codes that return different results and I wonder why they do not return the same results.
When i write
const endShape = _(raw)
.filter(key =>!/garbage/.test(key));
console.log(endShape);
in my console it prints.
[ { key: 'name', value: 'henry' },
{ key: 'age', value: 'old' },
{ key: 'food', value: 'matooke' },
{ key: 'kids', value: 'Acacia' },
{ key: 'garbageA', value: 'kasailoA' },
{ key: 'garbageB', value: 'kasasiroB' } ]
showing that my filter dint work.
When i write
const endShape = _(raw)
.filter({key} =>!/garbage/.test(key));
console.log(endShape);
It brings a syntax error.
But when i write
const endShape = _(raw)
.filter(({key}) =>!/garbage/.test(key));
console.log(endShape);
my filter works well and it prints
[ { key: 'name', value: 'henry' },
{ key: 'age', value: 'old' },
{ key: 'food', value: 'matooke' },
{ key: 'kids', value: 'Acacia' } ]
Why is it this way ? yet i know from phat arrow syntax that its okay to write
var x = y=>y+1;
and also
var x =(y)=>y+1
Actually the first and the second key for your filter is quite different.
On the first run when you do:
const endShape = _(raw)
.filter(key =>!/garbage/.test(key));
You are passing an object from your raw array, and your check is being evaluated like:
!/garbage/.test({ key: 'name', value: 'henry' })
Which will always be evaluated to false, and then you negate it so every condition will be true, thus your filter let every entry pass.
On the second run you do:
const endShape = _(raw)
.filter(({key}) =>!/garbage/.test(key));
Where you're destructuring key from your object, and thus the test makes sense, and your filter works fine!
Hope it helps!

Categories

Resources