Related
I have input data like this:
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}]
I am trying to achieve an output like this based on substring '[index]' (i.e. if that substring is not present then that element should be an object instead of an array):
{
"outField2": "something",
"outField3[index]": [{
"outField4": "something",
"outField5": "something",
"outField6": {
"outField7": "something"
}
}]
}
My current code (below) is able to produce the outField3 as an object if there is no substring '[index]' but I'm unable to find a good solution to generate it as an array in the presence of the substring. Can someone help out? I've tried a few options but none gives me the desired result.
function doThis(item, index) {
let path = map[index].name.split(".");
if (path.length > 1) {
createNestedObject(mapOutput, path, map[index].value);
} else {
mapOutput[map[index].name] = map[index].value;
};
};
function createNestedObject(element, path, value) {
var lastElement = arguments.length === 3 ? path.pop() : false;
for (var i = 0; i < path.length; i++) {
if (path[i].includes('[index]')) {
/*some logic here to push the child elements
that do not contain [index] as an array into
the ones that contain [index]*/
} else {
element = element[path[i]] = element[path[i]] || {};
};
}
if (lastElement) element = element[lastElement] = value;
return element;
};
const map = [{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}];
let mapOutput = {};
map.forEach(doThis);
let mapOutputJSON = JSON.stringify(mapOutput, null, 2);
console.log(mapOutputJSON);
.as-console-wrapper { min-height: 100%!important; top: 0; }
you can do something like this
const data = [{
"name": "outField2",
"value": "something"
},
{
"name": "outField3[index].outField4",
"value": "something"
},
{
"name": "outField3[index].outField5",
"value": "something"
},
{
"name": "outField3[index].outField6.outField7",
"value": "something"
}
]
const buildObject = (paths, value, obj) => {
if (paths.length === 0) {
return value
}
const [path, ...rest] = paths
if(path.includes('[index]')) {
return {
...obj,
[path]: [buildObject(rest, value, (obj[path] || [])[0] || {})]
}
}
return {
...obj,
[path]: buildObject(rest, value, obj[path] || {})
}
}
const result = data.reduce((res, {
name,
value
}) => buildObject(name.split('.'), value, res), {})
console.log(result)
A possible generic approach which in my opinion also assigns the correct type of the OP's "outField3[index]" property (object type instead of an Array instance) is based on reduce where ...
the outer loop iterates the array of { name, value } items
by executing a single function accumulateObjectTypeFromPathAndValue where ...
this function does split each name-value into an array of object-path keys which then gets iterated by the inner reduce method where the passed object programmatically accumulates nested key-value pairs.
function accumulateObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
}
return obj[key];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above implementation of the 2nd reducer function then could be changed according to the OP's custom array-type requirements ...
function accumulateCustomObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
if (key.endsWith('[index]')) {
obj[ key ] = [obj[ key ]];
}
}
return Array.isArray(obj[ key ])
//? obj[ key ].at(-1) // last item.
? obj[ key ][obj[ key ].length - 1] // last item.
: obj[ key ];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateCustomObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I get a json response from an api that I want to groom and create a new object from.
const things = [{
"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [{
"value": "3",
"onclick": "CreateNewDoc()"
},
{
"value": "5",
"onclick": "OpenDoc()"
},
{
"value": "8",
"onclick": "CloseDoc()"
}
]
}
}
},
{
"menu": {
"id": "image",
"value": "Image",
"popup": {
"menuitem": [{
"value": "New",
"onclick": "CreateNewImage()"
},
{
"value": "Open",
"onclick": "OpenImage()"
},
{
"value": "Close",
"onclick": "CloseImage()"
}
]
}
}
}
];
I know the old way of doing this like so:
const chs = [];
things.forEach((e) => {
const i = {};
i.Id = e.menu.id;
i.Value = e.menu.value;
i.PopupValue = e.menu.popup.menuitem[0].value;
i.SomethingComputed = e.menu.popup.menuitem[0].value - e.menu.popup.menuitem[1];
i.ShiftedUp = e.menu.popup.menuitem;
chs.push(ch);
});
now I want to do this using ES6 and destructuring. but I don't think I have taken it as far as I can go because I: 1)still have the loop; 2)have to create this new object; and 3) need these separate computed lines.
Can I get this more compact?
const chs = [];
things.forEach((e) => {
const {
Id: {id},
Value: {value},
PopupValue : {menu: {popup} },
} = e;
// computed
const someComputedValue = Value - PopupValue;
// new object
const ch = {
Id,
Value,
SomeComputedValue
}
chs.push(ch);
});
You can use map() instead of forEach(), so you don't need the chs.push() step at the end.
You can put the destructuring directly in the argument list, so you don't need the assignment step. Whether this is more readable is debatable.
If the calculation for SomeComputedValue isn't too complex, you can put it directly in the returned object. Then you can get rid of that assignment, and you can use the shorthand form of arrow function that just returns a value.
const things = [{
"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [{
"value": "3",
"onclick": "CreateNewDoc()"
},
{
"value": "5",
"onclick": "OpenDoc()"
},
{
"value": "8",
"onclick": "CloseDoc()"
}
]
}
}
},
{},
{
"menu": {
"id": "image",
"value": "Image",
"popup": {
"menuitem": [{
"value": "New",
"onclick": "CreateNewImage()"
},
{
"value": "Open",
"onclick": "OpenImage()"
},
{
"value": "Close",
"onclick": "CloseImage()"
}
]
}
}
}
];
const chs = things.map(({
menu: {
id: Id,
value: Value,
popup : PopupValue,
} = {id: "defaultID", value: "defaultValue", popup: "defaultPopup"}}) => ({
Id,
Value,
SomeComputedValue: Value - PopupValue
})
);
console.log(chs);
The non-destructuring way is actually more like this:
const chs = things.map(e => ({
Id: e.menu.id,
Value: e.menu.value,
PopupValue: e.menu.popup.menuitem[0].value,
SomethingComputed: e.menu.popup.menuitem[0].value - e.menu.popup.menuitem[1],
ShiftedUp: e.menu.popup.menuitem,
}));
And destructuring, or just the general concept of splitting stuff into more variables, doesn’t have to be all or nothing:
const chs = things.map(({menu}) => {
const {menuitem} = menu.popup;
return {
Id: menu.id,
Value: menu.value,
PopupValue: menuitem[0].value,
SomethingComputed: menuitem[0].value - menuitem[1],
ShiftedUp: menuitem,
};
});
You cannot avoid loop because things is an array so you have to iterate it. However you can avoid new object creation and pushing in chs array using the map function because it essentially creates a new element for each element of the orignial array and returns the array with newly created elements or objects in you case. So basically both looping and new object creation will be handled by the map function in this case.
Also, you can move the destructuring to the callback parameter of the map function and computation can be performed while returning the object:
const chs=things.map(({menu:{id,value,popup}}) => ({
Id: id,
Value: value,
PopupValue : popup,
SomeComputedValue: value+id
})
);
I have tested the above destructuring, it works for your things array as you provided it.
It's not pretty, but it is possible to do this using destructuring. You could create a arrow function like this which destructures each object in the array and returns a new object. Then use that as callback to map
const things=[{menu:{id:"file",value:"File",popup:{menuitem:[{value:"3",onclick:"CreateNewDoc()"},{value:"5",onclick:"OpenDoc()"},{value:"8",onclick:"CloseDoc()"}]}}},{menu:{id:"image",value:"Image",popup:{menuitem:[{value:"New",onclick:"CreateNewImage()"},{value:"Open",onclick:"OpenImage()"},{value:"Close",onclick:"CloseImage()"}]}}}];
const callback = ({
menu: {
id: Id,
value: Value,
popup: {
menuitem
}
}
}) => ({
Id,
Value,
ShiftedUp: menuitem,
PopupValue: menuitem[0].value,
SomethingComputed: menuitem[0].value - menuitem[1].value
})
console.log(things.map(callback))
You could even destrcuture the menuitem array indexes to get the first 2 values to separate variables like this:
const callback = ({
menu: {
id: Id,
value: Value,
popup: {
menuitem
},
popup: {
menuitem: [
{ value: Value0 },
{ value: Value1 }
]
}
}
}) => ({
Id,
Value,
ShiftedUp: menuitem,
PopupValue: Value0,
SomethingComputed: Value0 - Value1
})
In Javascript, how to retrieve an object in an array by one of its property ?
Hi all,
let's assume that we have the below :
"Attributes":[
{
"Name":"Brief",
"Value":"This skirt was fabriced from ...."
},
{
"Name":"Details",
"Value":"Measurements and Pictures are real"
},
{
"Name":"SKUNumber",
"Value":"12345678"
}
]
What I need to do is to get the value of "Value" based on "Name"..
For example :
console.log(Attributes.Brief) ==> "This skirt was fabriced from ...."
So I need a function to help doing that
Note that I don't want to use the index of the object, because its order may changed.
Thank you
Well, it's always better to show what you have attempted rather than just asking..
You can use Array.find to achieve this
let Attributes = [
{
"Name":"Brief",
"Value":"This skirt was fabriced from ...."
},
{
"Name":"Details",
"Value":"Measurements and Pictures are real"
},
{
"Name":"SKUNumber",
"Value":"12345678"
}
]
function getValueByName(name) {
return Attributes.find(d => d.Name.toLowerCase() == name.toLowerCase()).Value
}
console.log(getValueByName('Brief'))
console.log(getValueByName('details'))
console.log(getValueByName('SKUNumber'))
One option you have is to use Array.prototype.filter:
const d = [{
"Name": "Brief",
"Value": "This skirt was fabriced from ...."
},
{
"Name": "Details",
"Value": "Measurements and Pictures are real"
},
{
"Name": "SKUNumber",
"Value": "12345678"
}
]
console.log(d.filter(x=>x.Name==="Brief")[0].Value)
You can also make it more generic:
const d = [{
"Name": "Brief",
"Value": "This skirt was fabriced from ...."
},
{
"Name": "Details",
"Value": "Measurements and Pictures are real"
},
{
"Name": "SKUNumber",
"Value": "12345678"
}
]
const getValOfXfromArrByValOfY = (arr, x, y, val) => arr.find(z => z[y] === val)[x]
console.log(getValOfXfromArrByValOfY(d, 'Value', 'Name', 'SKUNumber'))
You could use a Proxy with a getter for the key, which returns a find of the object with the value.
var object = { attributes: [{ Name: "Brief", Value: "This skirt was fabriced from ...." }, { Name: "Details", Value: "Measurements and Pictures are real" }, { Name: "SKUNumber", Value: "12345678" }] },
attributes = new Proxy(
object.attributes,
{ get: (array, prop) => (array.find(({ Name }) => Name === prop) || {}).Value }
);
console.log(attributes.Brief);
console.log(attributes.SKUNumber);
You can use javascript find function see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find see bellow sample code:
var Attributes =[
{
"Name":"Brief",
"Value":"This skirt was fabriced from ...."
},
{
"Name":"Details",
"Value":"Measurements and Pictures are real"
},
{
"Name":"SKUNumber",
"Value":"12345678"
}
]
var found = Attributes.find(function(element) {
return element.Name == "Details";
});
console.log(found.Value); //output : Measurements and Pictures are real
I have an array from an API call.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
I connect to a lot of other API's and they return me a similar response but the keys change. Sometimes it might be
var response = {
"data": {
"data": [{
"values": "Harry",
"index": "name"
}, {
"values": 45,
"index": "score"
}]
}
}
var response = {
"data": {
"data": [{
"4": "Richard",
"index": "name"
}, {
"4": 98,
"index": "score"
}]
}
}
I would like to get an array like this.
[
{
name: 'Arun',
score: 70.78
}
]
This is what I did.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const result = [];
const mappedData = _.map(response.data.data, (item) => {
return {
[item.index]: item[1]
};
});
const resultObject = _.reduce(mappedData, (result, currentObject) => {
for (const key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
result[key] = currentObject[key];
}
}
return result;
}, {});
result.push(resultObject)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
So instead of hardcoding "1" or "values" in the map function, is there a more universal way to get the key and achieve the same result?
Thanks.
Use reduce rather than map, so you're updating the same object, not creating an array.
And since the property containing the value can vary, I use a loop to look for the first property that isn't named index, and use its value.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const mappedData = response.data.data.reduce((acc, item) => {
var value;
// find the property that isn't named "item"
for (var i in item) {
if (i != "index") {
value = item[i];
break;
}
}
acc[item.index] = value;
return acc;
}, {});
console.log(mappedData)
There's no need for lodash for this, the built-in reduce function is fine (but _.reduce will work similarly).
Since you only care about the values of that object and it only has two keys you can do this quite easily in lodash with reduce & fromPairs:
var response = { "data": { "data": [{ "1": "Arun", "index": "name" }, { "1": 70.78, "index": "score" }] } }
const rv = (o) => _.reverse(_.values(o))
const r = _.reduce(response.data.data, (a,c) => _.fromPairs([rv(a), rv(c)]))
console.log(r)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The same thing converted to ES6 would be:
var response = { "data": { "data": [{ "1": "Arun", "index": "name" }, { "1": 70.78, "index": "score" }] } }
const rv = (o) => Object.values(o).reverse() // reverse values
const fp = (arr) => arr.reduce((r, [k,v]) => (r[k] = v, r), {}) // from pairs
const result = response.data.data.reduce((a,c) => fp([rv(a), rv(c)]))
console.log(result)
The main idea here is to first get the object values in an array form, reverse them so the key & value are in the correct order and then reduce that array via from pairs to create the final object.
The main advantage of this approach is that we never deal with the object keys and only focus on the values which is what you really care about. This way the keys can be any value and it would still not matter.
You could try deleting the key-pair index and using the first value of the resulting object:
const mappedData = _.map(response.data.data, (item) => {
var tempObj = Object.assign({}, item)
var index = tempObj.index;
delete tempObj.index;
var otherData = Object.values(tempObj)[0];
return {
[index]: otherData
};
});
Just modified the #barmar approach. I have used Object.keys to get keys from object. This will remove the any hard-coded dependency.
var response = {
"data": {
"data": [{
"1": "Arun",
"index": "name"
}, {
"1": 70.78,
"index": "score"
}]
}
}
const mappedData = response.data.data.reduce((acc, item,i) => {
var key = Object.keys(item);
acc[item[key[1]]] = item[key[0]]
return acc ;
}, {});
console.log(mappedData)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
I have an array of data. Some of the key in the array are same. I would like to create a new array based on the key and add the other data.
This is my array
var myObjOne = [
{
"name":"John",
"id":1,
"car":"maruti"
},
{
"name":"John",
"id":2,
"car":"wolks"
},
{
"name":"John",
"id":3,
"car":"bmw"
},
{
"name":"Peter",
"id":4,
"car":"alto"
},
{
"name":"Peter",
"id":5,
"car":"swift"
}
];
I would like to convert the array in to the below format.
var myObj = [
{
"name":"John",
"items": [
{ "id":1, "car":"maruti" },
{ "id":2, "car":"wolks" },
{ "id":3, "car":"bmw" }
]},
{
"name":"Peter",
"items": [
{ "id":4, "car":"alto" },
{ "id":5, "car":"swift" },
]
}
];
I am working on a node environment.
You can create an object using Array#reduce first which maps name with items, and then create the final array by looping over the intermediate map using a for...of loop:
var source = [{"name":"John","id":1,"car":"maruti"},{"name":"John","id":2,"car":"wolks"},{"name":"John","id":3,"car":"bmw"},{"name":"Peter","id":4,"cars":"alto"},{"name":"Peter","id":5,"cars":"swift"}];
const map = source.reduce((acc, {name, ...obj}) => {
if (!acc[name]) {
acc[name] = [];
}
acc[name].push(obj);
return acc;
}, {});
const result = [];
for (let[name, items] of Object.entries(map)) {
result.push({name, items});
}
console.log(result);
Array.reduce is at rescue.This method accepts an accumulator and current
item. Check in the accumulator if there exist an object where the value of name property is John or Peter
var myObjOne = [{
"name": "John",
"id": 1,
"car": "maruti"
},
{
"name": "John",
"id": 2,
"car": "wolks"
},
{
"name": "John",
"id": 3,
"car": "bmw"
},
{
"name": "Peter",
"id": 4,
"car": "alto"
},
{
"name": "Peter",
"id": 5,
"car": "swift"
}
];
var newObj = myObjOne.reduce(function(acc, curr, currIndex) {
// using findIndex to check if there exist an object
// where the value of the name property is John, Peter
// if it exist it will return the index else it will return -1
let ifNameExist = acc.findIndex(function(item) {
return item.name === curr.name;
})
// if -1 then create a object with name and item property and push
// it to the accumulator
if (ifNameExist === -1) {
let nameObj = {};
nameObj.name = curr.name;
nameObj.items = [];
nameObj.items.push({
id: curr.id,
car: curr.car
})
acc.push(nameObj)
} else {
// if such an object already exist then just update the item array
acc[ifNameExist].items.push({
id: curr.id,
car: curr.car
})
}
return acc;
}, []);
console.log(newObj)
Use .reduce to group by name, and use .find inside the reducer to find if the matching name has already been added:
const input=[{"name":"John","id":1,"car":"maruti"},{"name":"John","id":2,"car":"wolks"},{"name":"John","id":3,"car":"bmw"},{"name":"Peter","id":4,"cars":"alto"},{"name":"Peter","id":5,"cars":"swift"}]
const output = input.reduce((a, { name, ...item }) => {
const foundNameObj = a.find(nameObj => nameObj.name === name);
if (foundNameObj) foundNameObj.items.push(item);
else a.push({ name, items: [item] });
return a;
}, []);
console.log(output);