How to split an object to an array - javascript

I want to split the object to be an array when the child value is an array. The new array length is the longest object child value length.(the longest length is three in the example)
Example:
// source
const data = {
key1: 'key1',
key2: ['key2-1', 'key2-2'],
key3: ['key3-1', 'key3-2', 'key3-3'],
};
// expect to be:
const result = [
{ key1: 'key1', key2: 'key2-1', key3: 'key3-1' },
{ key2: 'key2-2', key3: 'key3-2' },
{ key3: 'key3-3' },
];
// my low code
const transform = (data) => {
const maxLen = Object.values(data).reduce((max, value) => {
if (Array.isArray(value)) {
if (value.length > max) {
return value.length;
}
}
return max;
}, 0);
const newArray = [];
for (let index = 0; index < maxLen; index++) {
Object.entries(data).forEach(([key, value]) => {
if (!newArray[index]) {
newArray[index] = {};
}
if (Array.isArray(value) && value[index]) {
newArray[index][key] = value[index];
} else if (index === 0) {
newArray[index][key] = value;
}
});
}
return newArray;
};
but my code is very low.have you a nice answer?

One way to achieve the result:
function toArray(data) {
return Object.values(data).reduce((acc, currValue) => {
Array.isArray(currValue)
? // e.g., ['key2-1', 'key2-3']
currValue.forEach(v => addValues(acc, v))
: // e.g., 'key1'
addValues(acc, currValue)
return acc
}, [])
function addValues(acc, value) {
/*
Split 'key2-1' into 'key2' and '1'
Then assign 'key' = 'key2', 'index' = '1'
*/
let [key, index] = value.split('-')
/*
The 'index' value is used to determine which object
the 'key' and 'value' will be stored in.
Since array element indexes start from 0 and the numbering of keys
in the provided data object start from 1 the 'index' needs to be
converted from string to number to substract 1 from it.
The ' || 1' part is becuase for the first value 'key1'.split('-')
will result in only ['key1'] so in the assignment:
let [key, index] = ['key1']
index will be undefined and it will be set to 0 :
index = (Number(undefined) || 1) - 1
index = (NaN || 1) - 1
index = (1) - 1
index = 0
*/
index = (Number(index) || 1) - 1
if (!acc[index]) {
acc[index] = {}
}
acc[index][key] = value
}
}
Example:
const data = {
key1: 'key1',
key2: ['key2-1', 'key2-2'],
key3: ['key3-1', 'key3-2', 'key3-3'],
}
function toArray(data) {
return Object.values(data).reduce((acc, currValue) => {
Array.isArray(currValue)
? currValue.forEach(v => addValues(acc, v))
: addValues(acc, currValue)
return acc
}, [])
function addValues(acc, value) {
let [key, index] = value.split('-')
index = (Number(index) || 1) - 1
if (!acc[index]) acc[index] = {}
acc[index][key] = value
}
}
const result = toArray(data)
console.log(result)
.as-console-wrapper {min-height: 100%}
References:
Object.values(data).reduce((acc, currValue) => { ... }, []):
Object.values()
.reduce()
Array.isArray(currValue) ? currValue.forEach(v => ... ) : ...:
Array.isArray()
Conditional (ternary ?:) operator
.forEach()
let [key, index] = value.split('-'):
Destructuring assignment
.split()
index = (Number(index) || 1) - 1:
Number()
Logical OR (||)

You can format it like so.
const data = { key1: 'key1', key2: ['key2-1', 'key2-2'] };
// [{"key1":"key1"},{"key2":"key2-1"},{"key2":"key2-2"}]
const res = Object.keys( data ).reduce( ( p, key ) => {
if (typeof data[key] === 'string') {
return p.concat( { [key]: key } )
} else {
return p.concat( Object.values( data[key] ).map( value => ({ [key]: value }) ) )
}
}, [] )

Related

JavaScript: Create object with nested properties from string split by specific character

How to use the name property in this object:
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
To create an object with the following format:
{
root: {
branch: {
subbranch: {
leaf: 'my-value'
}
}
}
}
You could do this using split and reduce
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
let newObj = {}
const parts = obj.name.split('/')
parts.reduce((prev, curr, i) => (
Object.assign(
prev,
{[curr]: i === parts.length - 1 ? obj.value : Object(prev[curr])}
),
prev[curr]
), newObj)
console.log(newObj)
I wrote a reciursive function and a wrapper to call it.
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
const recursiveNest = (result, value, arr, index = 0) => {
const path = arr[index]
if (index < arr.length - 1) {
result[path] = {}
index +=1;
recursiveNest(result[path], value, arr, index)
} else {
result[arr[index]] = value;
}
};
const createNestedObject = (obj, splitBy) => {
let result = {}
recursiveNest(result, obj.value, obj.name.split(splitBy))
return result;
}
console.log(createNestedObject(obj, '/'));
Lodash provides setWith(object, path, value, [customizer]):
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
console.log(_.setWith({}, obj.name.split('/'), obj.value, _.stubObject))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>

Getting an Element from Array Property,.I am getting the right Answer so why won't it accept?

function getElementOfArrayProperty(obj, key, index) {
for(var i in obj) {
if(obj[i].length === 0 || !Array.isArray(obj[i])) {
return undefined;
}
for(var y = 0; y < obj[i].length; y++) {
if(!obj[key][index]) {
return undefined;
}
if(obj[i][y] === obj[key][index]) {
return obj[key][index];
}
}
}
}
var obj = {
key: ['Jamil', 'Albrey']
};
var output = getElementOfArrayProperty(obj, 'key', 0);
console.log(output); // --> 'Jamil'
Here are the criteria for this problem -
X) should return the element at the index of the array at the key of the passed in object
✓ should return undefined if the index is out of range
✓ should return undefined if the property at the key is not an array
✓ should return undefined if there is no property at the key
I am returning 'Jamil' as requested, so why does it not like my output? Is there something I'm conceptually not getting here? Or is the exercise bugged?
Try this:
If obj doesn't have the key, or obj[key] is not an array, or the index is invalid => return undefined
Else the arguments are valid, hence, return obj[key][index]
function getElementOfArrayProperty(obj, key, index) {
if(!obj[key] || !Array.isArray(obj[key]) || index < 0 || index >= obj[key].length)
return undefined;
return obj[key][index];
}
const obj = { key: ['Jamil', 'Albrey'], key1: 'str' };
console.log( 'obj, key, 0 => ', getElementOfArrayProperty(obj, 'key', 0) );
console.log( 'obj, key, 2 => ', getElementOfArrayProperty(obj, 'key', 2) );
console.log( 'obj, key, -1 => ', getElementOfArrayProperty(obj, 'key', -1) );
console.log( 'obj, key1, 0 => ', getElementOfArrayProperty(obj, 'key1', 0) );
console.log( 'obj, key2, 0 => ', getElementOfArrayProperty(obj, 'key2', 0) );
Use Array.find on the entries of the object
function getElementOfArrayProperty(obj, key, index) {
const maybeFoundIt = Object.entries(obj)
.find(([xkey, value]) => xkey === key &&
Array.isArray(value) &&
value.length &&
+index > -1 &&
index < value.length);
// return the value if something was found, otherwise undefined
return maybeFoundIt ? maybeFoundIt[1][index] : undefined;
}
const obj = {
key: ['Jamil', 'Albrey'],
notArray: {
x: 1
},
};
const yepNope = isOk => isOk ? "✓" : "X";
console.log(`should return undefined if the index is out of range: ${
yepNope(!getElementOfArrayProperty(obj, 'key', 24))}`);
console.log(`**should return undefined if no index is given: ${
yepNope(!getElementOfArrayProperty(obj, 'key'))}`);
console.log(`should return undefined if the property at the key is not an array: ${
yepNope(!getElementOfArrayProperty(obj, 'notArray', 0))}`);
console.log(`should return undefined if there is no property at the key: ${
yepNope(!getElementOfArrayProperty(obj, 'dontknowthis', 0))}`);
console.log(`otherwise returns a value: ${
yepNope(getElementOfArrayProperty(obj, 'key', 0))}`);

How to convert a nested object to a string indexed object

I am trying to convert a nested object to a string indexed object, so I can use Vue to display all properties in an object. For example:
var obj = {
key1: 'value1',
key2: {
key3: {
key5: 'value5',
key6: 'value6'
},
key4: 'value4'
}
};
Should be convert to this:
var obj = {
'key1': 'value1',
'key2.key3.key5': 'value5',
'key2.key3.key6': 'value6',
'key2.key4': 'value4'
}
I tried to walk recursively through the object, but I didn't figure out how to get the correct index value and return both the index and the object at the same time.
What I've tried so far:
// let result = objConversion(obj)
objConversion (obj) {
let resultObject = {}
// Walk through the first level
for (let index in obj) {
if (obj.hasOwnProperty(index)) {
let extractedObj = getObjNameRecursive(obj[ index ], index)
resultObject = { ...resultObject, ...extractedObj }
}
}
return resultObject
}
getObjNameRecursive (obj, name) {
let resultObject = {}
if (typeof obj === 'object') {
// Dive into an object
for (let index in obj) {
if (obj.hasOwnProperty(index)) {
if (typeof obj[ 'index' ] === 'object') {
resultObject = { ...resultObject, ...getObjNameRecursive(obj[ 'index' ], name + '.' + index) }
} else {
resultObject = {...resultObject, [name + '.' + index]: obj[index]}
}
}
}
} else {
// Simple assign if not an object
resultObject[ name ] = obj
}
return resultObject
}
But this gives the result like:
obj = {
'key1': 'value1',
'key2.key3.key5': [object Object],
'key2.key3.key6': [object Object],
'key2.key4': 'value4'
}
The answer in Convert string to an attribute for a nested object in javascript is very close to what I want. But what I want is to get the string of attributes of a nested object.
Is there any better way to do this?
Thanks.
Try this
function convert(obj, key, result) {
if(typeof obj !== 'object') {
result[key] = obj;
return result;
}
const keys = Object.keys(obj);
for(let i = 0; i < keys.length; i++){
const newKey = key ? (key + '.' + keys[i]) : keys[i];
convert(obj[keys[i]], newKey, result);
}
return result;
}
call it
convert(obj, '', {});

Transform an object to add new keys based on existing key-values

I need to transform this object :
myObject = {
"pageName": "home",
"dataExtract": "data1|data2=value2|data3=value3|data4=value4a,value4b,value4c"}
To this one:
myObject_mod = {
'pageName' : 'home',
'dataExtract' : {
'data1' : '', //no value for 'data1'
'data2' : 'value2',
'data3' : 'value3',
'data4' : {
'data4key1' : 'value4a',
'data4key2' : 'value4b',
'data4key3' : 'value4c'
}
}
I've started by taking the 'dataExtract' key, and split it by "|", so I get its values splited:
myObject.dataExtract.split("|");
(4) ["data1", "data2=value2", "data3=value3", "data4=value4a,value4b,value4c"]
How can I continue?
Use reduce to build up your result object for every entry in your data extract. The general form would be:
myArray.reduce((resultObject, entryString) => {
// some logic here
resultObject[key] = resultValue;
return resultObject
}, {});
For example:
array = {
"pageName": "home",
"dataExtract": "data1|data2=value2|data3=value3|data4=value4a,value4b,value4c"
};
array_mod = {
pageName: array.pageName,
dataExtract: array.dataExtract.split("|").reduce((obj, entry) => {
// handle cases like data1
if (entry.indexOf('=') === -1) {
obj[entry] = '';
return obj;
}
// handle cases like data2 and data3
const [key, value] = entry.split('=', 2);
if (value.indexOf(',') === -1) {
obj[key] = value;
return obj;
}
// handle cases like data4
const values = value.split(',');
obj[key] = values.reduce((o, v, i) => (o[`${key}key${i+1}`] = v, o), {});
return obj;
}, {})
};
console.log(array_mod);
Use a combination of Array.split(), Array.reduce(), and destructuring, to break the string into keys and values, and rebuild as an object:
const object = {
"pageName": "home",
"dataExtract": "data1|data2=value2|data3=value3|data4=value4a,value4b,value4c"
}
const result = {
...object,
dataExtract: object.dataExtract.split('|')
.reduce((r, kv) => {
const [key, value = ''] = kv.split('=')
r[key] = !value.includes(',') ?
value
:
value.split(',').reduce((ra, val, i) => {
ra[`${key}key${i + 1}`] = val;
return ra;
}, {})
return r
}, {})
}
console.log(result)
An alternative to what was already posted, also using String.split and Array.reduce:
const array = {
"pageName": "home",
"dataExtract": "data1|data2=value2|data3=value3|data4=value4a,value4b,value4c"
};
const array_mod = {
pageName: array.pageName,
dataExtract: array.dataExtract.split('|').reduce((r, cur) => {
const [k, v = ''] = cur.split('=', 2);
const vs = v.split(',');
r[k] = vs.length > 1 ? vs.reduce((sr, scur, i) => (sr[`${k}key${i + 1}`] = scur, sr), {}) : v;
return r;
}, {})
};
console.log(array_mod);
With comments and more explicit variable names:
const array = {
"pageName": "home",
"dataExtract": "data1|data2=value2|data3=value3|data4=value4a,value4b,value4c"
};
const array_mod = {
pageName: array.pageName,
// Split by | and build a key => value object from it
dataExtract: array.dataExtract.split('|').reduce((data, entry) => {
// Grab key (before `=`) and value (after `=`)
const [key, value = ''] = entry.split('=', 2);
// Grab subvalues (separated by `,`) if any
const subValues = value.split(',');
// If there are at least 2 subvalues, build a key => value object from them
data[key] = subValues.length > 1
? subValues.reduce((sub, subVal, i) => (sub[`${key}key${i + 1}`] = subVal, sub), {})
// Otherwise return the value as string
: value;
return data;
}, {})
};
console.log(array_mod);

JS update nested object key with string of key names concatenating with "."

Lets say that I have this object:
var obj = {
level1 :{
level2: {
level3: {
title: "winner"
}
}
}
}
Now I want to update the title key using the next string (notice, I have a string, not actual variable)
I have:
let myString = "level1.level2.level3.title"; // note - myString value comes from $http method or something
Maybe something like this:
obj[myString] = "super-winner";
Unfortunately the above doesn't work.
In addition - sometimes I need to update an undefined object so I need something to make the object to be defined with a new empty object.
For example, If I have the next object:
var obj = {
level1 : {}
}
}
I still want to modify the obj with the level3.winner as above.
Reminder:
obj[myString] = "super-winner";
How can I do that?
This works
const obj = {
// level1: {
// level2: {
// level3: {
// title: "winner"
// }
// }
// }
}
const myString = "level1.level2.level3.title"; // note - myString value comes from $http method or something
const title = 'super-winner'
myString.split('.')
.reduce(
(acc, curr) => {
if (acc[curr] === undefined && curr !== 'title') {
acc[curr] = {}
}
if (curr === 'title') {
acc[curr] = title
}
return acc[curr]
}, obj
);
console.log(obj) // {"level1":{"level2":{"level3":{"title":"super-winner"}}}}
This is zero-dependency solution, i.e. you don't have to use lodash or something bloating the size of your app.
Used "reduce" to achieve your desired result. Created a function "updateValue" where in you can pass obj - object to modify, str - property path to alter, value - value to be assigned at the property path
var obj1 = {
level1 :{
level2: {
level3: {
title: "winner"
}
}
}
}
var obj2 = { level1: {} }
var obj3 = {
level1 :{
level2: {
level3: {
title: "winner"
}
}
}
}
function updateValue(obj, str, value) {
let props = str.split('.'), arrIndex = -1
props.reduce((o,d,i) => (
arrIndex = d.indexOf('[') > -1 && d[d.indexOf('[') + 1],
arrIndex && (d = d.slice(0, d.indexOf('['))),
i == props.length - 1
? o[d] = value
: (o[d] = o[d] || {}, (arrIndex && (Array.isArray(o[d]) || (o[d] = [o[d]]))), arrIndex && o[d][arrIndex] || o[d])
)
, obj)
}
updateValue(obj1, 'level1.level2.level3.title', 'abcd')
updateValue(obj2, 'level1.level2.level3.title', 'abcd')
updateValue(obj3, 'level1.level2[0].title', 'abcd')
console.log(obj1)
console.log(obj2)
console.log(obj3)
This can be done by hand, indexing into the object structure repeatedly and creating new objects as necessary along the path to the destination key:
const updateField = (o, path, entry) => {
path = path.split(".");
let curr = o;
while (path.length > 1) {
const dir = path.shift();
const parent = curr;
curr = curr[dir];
if (undefined === curr) {
parent[dir] = {};
curr = parent[dir];
}
}
if (path.length === 1) {
curr[path.shift()] = entry;
}
return o;
};
var obj = {
level1 : {
level2: {
level3: {
title: "winner"
}
}
}
};
console.log(JSON.stringify(updateField(obj, "level1.level2.level3.title", "super-winner"), null, 2));
console.log(JSON.stringify(updateField({}, "level1.level2.level3.title", "super-winner"), null, 2));
You can use .set function of lodash https://lodash.com/docs#set
ex: _.set(obj, 'level1.level2.level3.title', 'super-winner');
Or use ES6 syntax function:
var str = 'level1.level2.level3.title';
str.split('.').reduce((p, c, index) => {
if (index === str.split('.').length - 1) {
if (typeof p[c] !== "object") { // string, number, boolean, null, undefined
p[c] = 'super-winner'
}
return p[c];
} else {
if (!p[c] || typeof p[c] !== 'object') {
p[c] = {};
}
return p[c];
}
}, obj)
console.log(obj);

Categories

Resources