I have this array
myarr = [
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...'
];
symbol = indicates that is is a property, next to that is a list of items that belongs to that property
I want to transform that array into object
myObj = {
title1: [
'longText0...',
'longtText1...',
],
title2: [
'longTextA...',
'longtTextB...',
'longtTextC...'
]
}
I come up with this code so far:
const arrayToObject = (array) =>
array.reduce((obj, item) => {
if(item.startsWith('=')) {
const itemName = item.replace('=', '')
obj[itemName] = itemName;
} else {
//add the rest....
}
return obj
}, {})
console.log(arrayToObject(myarr))
My challenges so far is that I am not sure how to turn obj[itemName] so I can assign the items to it. Any ideas how to do that?
A reduce based approach which does not depend on outer scope references for keeping track of the currently to be built/aggregated property makes this information part of the reducer function's first parameter, the previousValue which serves as an accumulator/collector object.
Thus, as for the OP's task, this collector would feature two properties, the currentKey and the result, where the former holds the state of the currently processed property name and the latter being the programmatically built result.
// - reducer function which aggregates entries at time,
// either by creating a new property or by pushing a
// value into the currently processed property value.
// - keeps the state of the currently processed property
// by the accumulator`s/collector's `currentKey` property
// whereas the result programmatically gets build as
// the accumulator`s/collector's `result` property.
function aggregateEntry({ currentKey = null, result = {} }, item) {
const key = (item.startsWith('=') && item.slice(1));
if (
(key !== false) &&
(key !== currentKey)
) {
// keep track of the currently processed property name.
currentKey = key;
// create a new entry (key value pair).
result[currentKey] = [];
} else {
// push value into the currently processed property value.
result[currentKey].push(item);
}
return { currentKey, result };
}
console.log([
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...',
].reduce(aggregateEntry, { result: {} }).result);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I wouldn't do that with reduce but with a simple for loop, because you have to carry the itemname over multiple iterations
let o = {}, n = '';
for (let k of arr) {
if (k.startsWith('=')) {
n = k.substring(1);
o[n] = []
} else {
o[n].push(k);
}
}
You can of course also do it with reduce, but you have to put the declaration of itemname outside of the callback
let n = '';
let o = arr.reduce((a, c) => {
if (c.startsWith('=')) {
n = c.substring(1);
a[n] = [];
} else {
a[n].push(c);
}
return a;
}, {});
Please be aware, there is no error handling, ie the code assumes your array is well structured and the first element in the array must start with =
The following function will give you the desired results
function arrayToObject(arr)
{
let returnObj={};
for(let i =0; i <arr.length; i++)
{
if(arr[i].startsWith('='))
{
let itemName = arr[i].replace('=','');
returnObj[itemName]=[];
for(let j=i+1; j <arr.length;j++)
{
if(arr[j].startsWith('='))
{
break;
}
else
{
let value = arr[j];
returnObj[itemName].push(value) ;
}
}
}
}
return returnObj;
}
Here a version with reduce
const myarr = [
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...'
];
const obj = myarr.reduce((res, el) => {
if(el.startsWith('=')){
const key = el.substring(1)
return {
data: {
...res.data,
[key]: [],
},
key
}
}
return {
...res,
data:{
...res.data,
[res.key]: [...res.data[res.key], el]
}
}
}, {
data: {},
key: ''
}).data
console.log(obj)
You don't need to keep the key somewhere separate for a reduce method:
const myarr = ['=title1', 'longText0...', 'longtText1...', '=title2', 'longTextA...', 'longtTextB...', 'longtTextC...'];
const res = Object.fromEntries(
myarr.reduce((acc, item) => {
if(item.startsWith('='))
acc.push([item.substring(1), []]);
else
acc[acc.length - 1]?.[1].push(item);
return acc;
}, [])
);
console.log(JSON.stringify( res ));
Related
I'm trying to create a function which returns me halve the data sumed up. I was able to do it on a non-nested Array but failing on the nested Array. I get the error Cannot read properties of undefined (reading 'push').
How the returned data should look like:
var data = [{"Key":1,"values":[
{"LastOnline":"21-11-29","Value":2},
{"LastOnline":"21-12-01","Value":2},
{"LastOnline":"21-12-03","Value":2}
]}];
What I have right now:
var data = [{"Key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data){
var newData = [];
var temp = [{"key":data.key,"values":[{}]}];
// sum 2 togheter
for(var i=1;i<data.values.length;i++){
if(data.values[i]){
temp.values[i].push({"LastOnline":data.values[i].LastOnline, "Value":(data.values[i].Value + data.values[[i-1]].Value)});
}
}
for(var i=0;i<temp.values.length;i++){
if(i % 2 == 0){
newData.push(temp.values[i]);
}
}
return newData;
}
console.log(halveMonth(data));
JS variables are case sensitive. Keep the key consistent everywhere. If you don't plan to use reduce here is the solution.
var data = [{"key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data){
let newData = []
for (let i = 0; i < data.length; i++) {
let temp = {"key":data[i].key,"values":[]}
for (let j = 0; j < data[i].values.length; j += 2) {
const res = (j+1===data[i].values.length) ? data[i].values[j].Value : data[i].values[j].Value + data[i].values[j+1].Value
temp.values.push({"LastOnline":(j+1===data[i].values.length)?data[i].values[j].LastOnline:data[i].values[j+1].LastOnline,"Value":res});
}
newData.push(temp);
}
return newData
}
console.log(halveMonth(data));
The variable data you declare at the first line of your snippet is an array. So you can't do data.values. You first need to indicate which index of your array you want to read. In this case : data[0].values
First things first, you data is itself an array - so assuming your real data has more than 1 element you'll need to do the same thing for each one.
It's helpful to start off with a method which does the work on just 1 element
const justASingle = {"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1}]};
function halveMonthSingle(data) {
return {
...data,
values: data.values.reduce((acc, item, idx) => {
if ((idx % 2) != 0)
acc.push({
...item,
Value: data.values[idx - 1].Value + item.Value
})
return acc;
}, [])
}
}
console.log(halveMonthSingle(justASingle))
Once you have that you can just use map do do it for every element
const data = [{"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1}]}];
function halveMonthSingle(data) {
return {
...data,
values: data.values.reduce((acc, item, idx) => {
if ((idx % 2) != 0)
acc.push({
...item,
Value: data.values[idx - 1].Value + item.Value
})
return acc;
}, [])
}
}
const result = data.map(halveMonthSingle)
console.log(result)
I would use reduce - saves me from trying to figure out why your two loops do not work other than data.values should be data[0].values
var data = [{"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1},]}];
const newArr = data.slice(0); // to not mutate original
newArr[0].values = data[0].values.reduce((acc,item,i) => {
if (i%2 !== 0) { // every second
acc.push(data[0].values[i]); // push the item
acc[acc.length-1].Value += data[0].values[i-1].Value; // add the first
}
return acc
},[])
console.log(newArr)
This works too. I basically did your idea, skipping the temp array and merging the two steps into one.
const data = [{"Key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data) {
const newData = [];
newData.push({
Key: data[0].Key,
values: []
});
for(let i = 0; i < data[0].values.length; i++){
if (i % 2 !== 0) {
newData[0].values.push({
LastOnline: data[0].values[i].LastOnline,
Value: data[0].values[i].Value + data[0].values[i-1].Value
});
}
}
return newData;
}
console.log(halveMonth(data));
This question already has answers here:
Create an object based on file path string
(3 answers)
Closed 1 year ago.
I have an array of structured strings with have connection | as a format which self-divided into levels. I want to convert it into a structured object with multiple levels.
Input:
[
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
Expected output:
{
clothes: {
tshirt: {
tshirt-for-kids: {},
tshirt-for-men: {
luxury-tshirt: {}
}
},
coat: {
raincoat: {}
leather-coat: {}
}
}
}
Very simple task - just enumerate the array and create the relevant object keys:
var myArray = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var result = {}, levels, current, temp;
while(myArray.length > 0)
{
levels = myArray.pop().split('|');
temp = result;
while(levels.length > 0)
{
current = levels.shift();
if(!(current in temp)) temp[current] = {};
temp = temp[current];
}
}
console.log(result);
You could try this:
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
function convertStrToObject(str, sep, obj) {
const sepIndex = str.indexOf(sep);
if (sepIndex == -1) {
obj[str] = obj[str] || {};
} else {
const key = str.substring(0, sepIndex);
obj[key] = obj[key] || {};
convertStrToObject(str.substring(sepIndex + 1), sep, obj[key]);
}
}
const all = {};
for (let i = 0; i < input.length; ++i) {
convertStrToObject(input[i], "|", all);
}
console.log(all);
Assuming you intend to collect properties, all having an empty object as leaf node.
// input
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
// Here, we collect the properties
const out = {};
// map the input array, splitting each line at |
input.map(i => i.split('|'))
.filter(a => a.length > 0) // lets not entertain empty lines in input
.forEach(a => { // process each array of property names
// start at outermost level
let p = out;
// iterate properties
for(const v of a){
// create a property if it is not already there
if(!p.hasOwnProperty(v)){
p[v] = {};
}
// move to the nested level
p = p[v];
}
});
// lets see what we have created
console.log(out);
A number of solutions have been suggested already, but I'm surprised none involves reduce() - which would seem the more idiomatic solution to me.
var array = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var object = array.reduce(function (object, element) {
var keys = element.split("|")
keys.reduce(function (nextNestedObject, key) {
if (!nextNestedObject[key]) nextNestedObject[key] = {}
return nextNestedObject[key]
}, object)
return object
}, {})
console.log(object)
One Liner With eval
Used eval to evaluate strings like the following:
'o["clothes"]??={}'
'o["clothes"]["tshirt"]??={}'
'o["clothes"]["tshirt"]["tshirt-for-kids"]??={}'
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat", "clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
arr = data.map((d) => d.split("|")),
res = arr.reduce((r, a) => (a.forEach((k, i) => eval(`r["${a.slice(0, i + 1).join('"]["')}"]??={}`)), r), {});
console.log(res)
One Liner Without eval
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat","clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
res = data.reduce((r, d) => (d.split("|").reduce((o, k) => (o[k] ??= {}, o[k]), r), r), {});
console.log(res)
I'm trying to merge 2 objects which contains arrays in one of their elements. I don't achieve the disered result when using spread syntax and the first object array is being replaced by the second one. The objects are like the following:
const objectA1 = {
keyA1:'valueA1',
keyArr:[{
arrKeyA01:'arrValueA01',
arrKeyA02:'arrValueA02',
},
{
arrKeyA11:'arrValueA11',
arrKeyA12:'arrValueA12',
}
]
}
const objectB1 = {
keyB1:'valueB1',
keyArr:[{
arrKeyB01:'arrValueB01',
arrKeyB02:'arrValueB02',
},
{
arrKeyB11:'arrValueB11',
arrKeyB12:'arrValueB12',
}
]
}
And I want to get:
const objectRes = {
keyA1:'valueA1',
keyB1:'valueB1',
keyArr:[{
arrKeyA01:'arrValueA01',
arrKeyA02:'arrValueA02',
arrKeyB01:'arrValueB01',
arrKeyB02:'arrValueB02',
},
{
arrKeyA11:'arrValueA11',
arrKeyA12:'arrValueA12',
arrKeyB11:'arrValueB11',
arrKeyB12:'arrValueB12',
}
]
}
What I'm using is
{...objectA1 ,...objectB1}
But as said, the keyArr doesn't keep the objectA1 elements.
How I can merge both objects and keep the array data using spread syntax?
Thanks for any comment/help :)
Create an object and place the first 2 values from A1 and B2 object. Customize the array separately by using reduce
const objectA1 = {
keyA1: 'valueA1',
keyArr: [{
arrKeyA01: 'arrValueA01',
arrKeyA02: 'arrValueA02',
},
{
arrKeyA11: 'arrValueA11',
arrKeyA12: 'arrValueA12',
}
]
}
const objectB1 = {
keyB1: 'valueB1',
keyArr: [{
arrKeyB01: 'arrValueB01',
arrKeyB02: 'arrValueB02',
},
{
arrKeyB11: 'arrValueB11',
arrKeyB12: 'arrValueB12',
}
]
}
const arr = objectA1.keyArr.reduce((acc, x) => {
const res1 = objectB1.keyArr.reduce((acc2, y) => ({...x,...y}), {})
return acc = [...acc, res1];
}, [])
const result = {
keyA1: objectA1.keyA1,
keyB1: objectB1.keyB1,
keyArr: arr
}
console.log(result)
I wanted to share my attemp solving this problem, I take the array and merge it in one using loops:
const objectA1 = {
keyA1:'valueA1',
keyArr:[{
arrKeyA01:'arrValueA01',
arrKeyA02:'arrValueA02',
},
{
arrKeyA11:'arrValueA11',
arrKeyA12:'arrValueA12',
}
]
}
const objectB1 = {
keyB1:'valueB1',
keyArr:[{
arrKeyB01:'arrValueB01',
arrKeyB02:'arrValueB02',
},
{
arrKeyB11:'arrValueB11',
arrKeyB12:'arrValueB12',
}
]
}
objects = [objectA1, objectB1];
let i = 0;
new_array = {};
for(i; i < objects.length; i++){
object = objects[i];
keys = Object.keys(object);
for(j = 0; j < keys.length; j++){
//if property already exists, example keyArr
this_key = keys[j];
console.log(this_key);
if(new_array[this_key] != undefined){
//loop through that property in the object
object[this_key].forEach((object_value, index) => {
//add all properties that previous object did not have
Object.assign(new_array[this_key][index], object_value);
});
}else{
//initialize that value with the first element array
new_array[this_key] = object[this_key];
}
}
}
console.log(objects);
console.log(new_array);
I have some data which is
var currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
The data is flat and I need to convert it into something like:
{
'ticket': 'CAP',
children : [{
'ticket' : 'CT-1',
'children' : [{
'ticket' : 'CT-1-A',
'children' : []
}, {
'ticket' : 'CT-1-B',
'children' : []
}],
[{
'ticket' : 'CT-2',
'children' : []
}]
}]
}
(I think the above is valid)?
I'm very lost as to how. I am going to show my effort but, I'm not sure if my approach is correct or not.
var currentData = [{'ticket':'cap', 'child':'CT-1'},{'ticket':'cap', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'},{'ticket':'CT-1', 'child':'CT-1-B'}];
var newList = [];
function convert(list){
if (newList.length <= 0){
var child = [];
var emptyChild = [];
child.push({'ticket': list[0].child, 'child': emptyChild });
newList.push({'ticket': list[0].ticket, 'children' : child});
list.splice(0,1);
} // the if statement above works fine
for(var i = 0; i < list.length; i++) {
var ticket = list[i].ticket;
for(var j = 0; j < newList.length; j++) {
if (newList[j].ticket == ticket){
var child;
var emptyChild = [];
child = {'ticket': list[i].child, 'child': emptyChild };
newList[j].children.push(child);
list.splice(i,1);
break;
} // the if above works
else{
var child2 = getFromChildren(ticket, newList, list[i]); // child2 is Always null, even if getFromChildren returns an object
newList[j].children.push(child2);
list.splice(i,1);
break;
}
}
}
if (list.length > 0){
convert(list);
}
}
function getFromChildren(ticket, list, itemToAdd){
if (list == null || list[0].children == null)
return;
for(var i = 0; i < list.length; i++) {
if (list[i] == null)
return;
if (list[i].ticket == ticket){
list[i].child.push(itemToAdd.child); // ** can't do this, javascript passes by value, not by reference :(
} else{
getFromChildren(ticket, list[i].children, itemToAdd);
}
}
}
convert(currentData);
I think I've made a mess of it. In the comments I've put a ** explaining that it isn't working due to JavaScript not passing by reference, however upon further reading I don't think that is correct as I'm passing the object which is by reference?
Edit
The data, shown with currentData will not always start at the root sadly either
function convert(arr) {
var children = {}; // this object will hold a reference to all children arrays
var res = arr.reduce(function(res, o) { // for each object o in the array arr
if(!res[o.ticket]) { // if there is no object for the element o.ticket
res[o.ticket] = {ticket: o.ticket, children: []}; // then creates an object for it
children[o.ticket] = res[o.ticket].children; // and store a reference to its children array
}
if(!res[o.child]) { // if there is no object for the element o.child
res[o.child] = {ticket: o.child, children: []}; // then creates an object for it
children[o.child] = res[o.child].children; // and store a reference to its children array
}
return res;
}, {});
arr.forEach(function(o) { // now for each object o in the array arr
children[o.ticket].push(res[o.child]); // add the object of o.child (from res) to its children array
delete res[o.child]; // and remove the child object from the object res
});
return res;
}
var currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
console.log(convert(currentData));
Explanation:
The reduce part creates an object of the form: { ticket: "...", children: [] } for each element (child or not). So right after reduce, the object res will be:
res = {
'CAP': { ticket: 'CAP', children: [] },
'CT-1': { ticket: 'CT-1', children: [] },
'CT-2': { ticket: 'CT-2', children: [] },
'CT-1-A': { ticket: 'CT-1-A', children: [] },
'CT-1-B': { ticket: 'CT-1-B', children: [] },
}
Now comes the forEach bit which loops over the array once more, and now for each object it fetches the object of .child from res above, push it into .ticket object's children (which a reference to it is stored in children object), then remove the .child object from the object res.
Below uses reduce to get the data grouped to a Map, then I convert the data to an object like you've shown above. You'll need a modern browser to run below snippet, or use a transpiler like babeljs to convert it to es5 syntax.
let currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
let children = currentData.map(e => e.child);
currentData.sort((a,b) => children.indexOf(a.ticket));
let res = currentData.reduce((a,b) => {
if (! children.includes(b.ticket)) {
return a.set(b.ticket, (a.get(b.ticket) || [])
.concat({ticket: b.child,
children: currentData
.filter(el => el.ticket === b.child)
.map(el => ({ticket: el.child, children: []}))}))
}
return a;
}, new Map);
let r = {};
for (let [key,value] of res.entries()) {
r.ticket = key;
r.children = value;
}
console.log(r);
Solution using recursion, starting node can be changed.
var currentData = [{'ticket': 'cap','child': 'CT-1'}, {'ticket': 'cap','child': 'CT-2'}, {'ticket': 'CT-1','child': 'CT-1-A'}, {'ticket': 'CT-1','child': 'CT-1-B'}];
function convert(data, start){
return {
ticket: start,
childs: data.filter(d => d.ticket == start)
.reduce((curr, next) => curr.concat([next.child]), [])
.map(c => convert(data, c))
}
}
let result = convert(currentData, 'cap');
console.log(result);
.as-console-wrapper{top: 0; max-height: none!important;}
I would go with a simple for approach, like this:
var currentData = [
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'},
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'}
];
var leafs = {};
var roots = {};
var tickets = {};
for(var i=0; i<currentData.length; i++){
var ticket = currentData[i].ticket;
var child = currentData[i].child;
if(!tickets[ticket]){
tickets[ticket] = {ticket:ticket,children:[]};
if(!leafs[ticket]){
roots[ticket] = true;
}
}
if(!tickets[child]){
tickets[child] = {ticket:child,children:[]};
}
delete roots[child];
leafs[child] = true;
tickets[ticket].children.push(tickets[child]);
}
for(var ticket in roots){
console.log(tickets[ticket]);
}
Well, if you are not familiar with reduce, map , forEach with callbacks to iterate, then here is a approach I came with, where the code is flat, storing object references in another map object and iterating exactly once the source array.
The code is much cleaner, if something is understandable add comments I will explain;
var currentData = [
{'ticket':'CT-1', 'child':'CT-1-A'},
{'ticket':'CT-1', 'child':'CT-1-B'},
{'ticket':'CAP', 'child':'CT-1'},
{'ticket':'CAP', 'child':'CT-2'}
];
function buildHierarchy(flatArr) {
let root = {},
nonRoot = {},
tempMap = {};
Object.setPrototypeOf(root, nonRoot);
for (let idx = 0; idx < flatArr.length; idx++) {
let currTicket = flatArr[idx];
let tempTicket = tempMap[currTicket.ticket] || {ticket: currTicket.ticket, children: []};
tempMap[currTicket.ticket] = tempTicket;
if (currTicket.child) {
let tempChild = tempMap[currTicket.child] || {ticket: currTicket.child, children: []};
tempTicket.children.push(tempChild);
tempMap[currTicket.child] = tempChild;
delete root[tempChild.ticket];
nonRoot[tempChild.ticket] = true;
}
root[tempTicket.ticket] = true;
}
return tempMap[Object.keys(root)[0]];
}
console.log(buildHierarchy(currentData));
I have changed the sequence of your source array in order to put the root object anywhere, and the code should work on that.
I'm trying to convert an environment variable into an object of values for configuration in JavaScript but I don't know the best way to achieve this.
The idea would be to output SAMPLE_ENV_VAR=value as:
{
sample: {
env: {
var: value
}
}
}
What I have so far:
const _ = require('lodash');
const process = require('process');
_.each(process.env, (value, key) => {
key = key.toLowerCase().split('_');
// Convert to object here
}
Here's a more complete solution based on yours:
const _ = require('lodash');
const result = {};
// We'll take the following as an example:
// process.env = { HELLO_WORLD_HI: 5 }
// We'll expect the following output:
// result = { hello: { world: { hi: 5 } } }
_.each(process.env, (value, key) => {
// We'll separate each key every underscore.
// In simple terms, this will turn:
// "HELLLO_WORLD_HI" -> ['HELLO', 'WORLD', 'HI']
const keys = key.toLowerCase().split('_');
// We'll start on the top-level object
let current = result;
// We'll assign here the current "key" we're iterating on
// It will have the values:
// 'hello' (1st loop), 'world' (2nd), and 'hi' (last)
let currentKey;
// We'll iterate on every key. Moreover, we'll
// remove every key (starting from the first one: 'HELLO')
// and assign the removed key as our "currentKey".
// currentKey = 'hello', keys = ['world', 'hi']
// currentKey = 'world', keys = ['hi'], and so on..
while ( (currentKey = keys.shift()) ) {
// If we still have any keys to nest,
if ( keys.length ) {
// We'll assign that object property with an object value
// result =// { HELLO: {} }
current[currentKey] = {};
// And then move inside that object so
// could nest for the next loop
// 1st loop: { HELLO: { /*We're here*/ } }
// 2nd loop: { HELLO: { WORLD: { /*We're here*/ } } }
// 3rd loop: { HELLO: { WORLD: { HI : { /*We're here*/ } } } }
current = current[currentKey];
} else {
// Lastly, when we no longer have any key to nest
// e.g., we're past the third loop in our example
current[currentKey] = process.env[key]
}
}
});
console.log(result);
To simply put:
We'll loop through every environment variable (from process.env)
Split the key name with an underscore, and, again, loop each key (['HELLO', 'WORLD', 'HI'])
Assign it to an object ({ hello: {} } -> { hello: { world: {} } } -> { hello: world: { hi: ? } } })
When we no longer have any keys left, assign it to the actual value ({ hello: { world: { hi: 5 } } })
Funnily enough, I just finished code for this last night for a personal project. What I ended up using is not ideal, but is working for me:
export function keyReducer(
src: any,
out: any,
key: string,
pre: string,
del: string
): ConfigScope {
const path = key.toLowerCase().split(del);
if (path[0] === pre.toLowerCase()) {
path.shift();
}
if (path.length === 1) { // single element path
const [head] = path;
out[head] = src[key];
} else {
const tail = path.pop();
const target = path.reduce((parent: any, next: string) => {
if (parent[next]) {
return parent[next];
} else {
return (parent[next] = <ConfigScope>{});
}
}, out);
target[tail] = src[key];
}
return out;
}
static fromEnv(env: Environment, {prefix = 'ABC', delimiter = '_'} = {}) {
const data: ConfigScope = Object.keys(env).filter(key => {
return StringUtils.startsWith(key, prefix);
}).reduce((out, key) => {
return keyReducer(env, out, key, prefix, '_');
}, <ConfigScope>{});
return new Config(data);
}
(with TypeScript type annotations)
The idea here is to split each key, create the target objects on the way down, then set the final value.
This is my quick take at it:
var object = {}; // the object to store the value in
var name = "SAMPLE_ENV_VAR"; // the environment variable key
var value = "value"; // the value of the environment variable
// helper function to automatically create an inner object if none exists
function getOrCreateInnerObj(obj, name) {
if (!obj.hasOwnProperty()) {
obj[name] = {};
}
return obj[name];
}
// an array of the individual parts (e.g. ["sample", "env", "var"])
var keyParts = name.toLowerCase().split("_");
// innerObj will contain the second to last element object in the tree based on the array of keys
var innerObj = getOrCreateInnerObj(object, keyParts[0]);
for (var i = 1; i < keyParts.length - 1; i++) {
innerObj = getOrCreateInnerObj(innerObj, keyParts[i]);
}
// set the value itself
innerObj[keyParts[keyParts.length - 1]] = value;
$("body").html(JSON.stringify(object));
The gist of it is, for all but the last element in the array of key parts, you get or create an object in the current parent object for that key, and once you've repeated this for all but the last key, you'll have the second-to-last inner object, which you can then set the value on.
Edit: Working example
Edit 2: Here is a much cleaner example that uses a little bit of recursion to accomplish the same thing
const basic = {};
let current;
`YOUR_VARIABLE_NAME`
.split(`_`)
.forEach((item, index, array) => {
if(index === 0) {
return current = basic[item] = {};
}
if(index === array.length - 1) {
return current[item] = process.env.HE_LO_NA;
}
current = current[item] = {};
});
console.log(require('util').inspect(basic, {depth: 10}));
const _ = require('lodash');
const process = require('process');
const result = Object.entries(process.env).reduce((acc, [key, value]) => {
_.set(acc, key.toLowerCase().replace('_', '.'), value);
return acc;
}, {})