We have an array that contains objects like this:
[
{
"id":29751,
"refId":20293494,
"pCode":712,
"paymentDate":140101,
"title":"Sample title",
"heading":"A heading",
"value":4214000,
"remainder":429740000,
"createdAt":"2023-01-31T07:34:29.000Z"
},
{
"id":29752,
"refId":20293495,
"pCode":712,
"paymentDate":140102,
"title":"Sample title",
"heading":"A heading",
"value":4214000,
"remainder":429740000,
"createdAt":"2023-01-31T07:34:29.000Z"
},
{
"id":29753,
"refId":20293496,
"pCode":712,
"paymentDate":140103,
"title":"Sample title",
"heading":"A heading",
"value":4214000,
"remainder":429740000,
"createdAt":"2023-01-31T07:34:29.000Z"
}
]
So thing is to group items based on paymentDate. I mean to create a new array of objects and when main array maps, if paymentDate is 140101, goes in 140101 item. The result would be like this:
{
"140101":[
{
"id":29751,
"refId":20293494,
"pCode":712,
"paymentDate":140101,
"title":"Sample title",
"heading":"A heading",
"value":4214000,
"remainder":429740000,
"createdAt":"2023-01-31T07:34:29.000Z"
}
],
"140102":[
{
"id":29752,
"refId":20293495,
"pCode":712,
"paymentDate":140102,
"title":"Sample title",
"heading":"A heading",
"value":4214000,
"remainder":429740000,
"createdAt":"2023-01-31T07:34:29.000Z"
}
],
"140103":[
{
"id":29753,
"refId":20293496,
"pCode":712,
"paymentDate":140103,
"title":"Sample title",
"heading":"A heading",
"value":4214000,
"remainder":429740000,
"createdAt":"2023-01-31T07:34:29.000Z"
}
]
}
What we are looking for is to handle this situation dynamically. Couse this is not the only situation. we may have a larger data with more paymentDates.
I found a solution.
const newArray = {};
items.map((item) => {
if (newArray.hasOwnProperty(item.paymentData)) {
newArray[item.paymentData].push(item);
} else {
newArray[item.paymentData] = [];
newArray[item.paymentData].push(item);
}
});
Related
I currently have an array of items that looks a bit like this: I want to group the items by category lookup, with the slight problem that category lookup is potentially an array, such that Parent Item 2 would be listed twice (once in My Cat) and once in something else) I tried using https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/groupBy but it doesn't seem to be able to handle this?
[
{
"tool_id": "4-19de-454673d9-9ef5-4545",
"somekey" : "Parent Item 2"
"categoryLookup": [
{
"category_name": "New Item",
}
]
},
{
"tool_id": "be7ea707-19de-43d9-9ef1-d4a3ff79f77a",
"somekey" : "Parent Item"
"categoryLookup": [
{
"category_name": "My Cat",
},
{
"category_name": "Something Else",
}
]
}
]
The final result would look something like:
[
{
New Item: [
{...contains 4-19de-454673d9-9ef5-4545 }
],
My Cat: [
{...contains be7ea707-19de-43d9-9ef1-d4a3ff79f77a}
],
Something Else: [
{... contain be7ea707-19de-43d9-9ef1-d4a3ff79f77a}
]
}
]
You can iterate over the original array and create the final one:
var data = [{
"tool_id": "4-19de-454673d9-9ef5-4545",
"somekey": "Parent Item 2",
"categoryLookup": [{
"category_name": "New Item",
}]
},
{
"tool_id": "be7ea707-19de-43d9-9ef1-d4a3ff79f77a",
"somekey": "Parent Item",
"categoryLookup": [{
"category_name": "My Cat",
},
{
"category_name": "Something Else",
}
]
}
];
function groupByCategory(data) {
const res = {};
data.forEach(item => {
item.categoryLookup.forEach(category => {
const name = category.category_name;
res[name] ??= [];
res[name].push({
item: item.tool_id //or all the properties you want
});
});
});
return res;
}
console.log( groupByCategory(data) );
This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
Closed 10 months ago.
I'm trying to manipulate an array like this:
data = [
{
"id":"1",
"items":[
{
"title":"item 1"
},
{
"title":"item 2"
}
]
},
{
"id":"2",
"items":[
{
"title":"item2 1"
},
{
"title":"item2 2"
}
]
}
]
I need, for example, to push another array:
[
{
"title":"item new 1"
},
{
"title":"item new 2"
}
]
inside data[0].items and obtain:
data = [
{
"id":"1",
"items":[
{
"title":"item new 1"
},
{
"title":"item new 2"
}
]
},
{
"id":"2",
"items":[
{
"title":"item2 1"
},
{
"title":"item2 2"
}
]
}
]
...how can I do this maintaining immutability, for example with Lodash?
Not understand anding how to change only a specific sub object in a data structure.
Somebody have suggestions?
Thanks
Presented below is one possible way to immutably add given array into a particular index "items" prop.
Code Snippet
const immutableAdd = (aIdx, addThis, orig) => {
const newData = structuredClone(orig);
newData[aIdx].items = addThis;
return newData;
};
const data = [{
"id": "1",
"items": [{
"title": "item 1"
},
{
"title": "item 2"
}
]
},
{
"id": "2",
"items": [{
"title": "item2 1"
},
{
"title": "item2 2"
}
]
}
];
const addThisArr = [{
"title": "item new 1"
},
{
"title": "item new 2"
}
];
console.log('immutableAdd result: ', immutableAdd(0, addThisArr, data));
console.log('original data: ', data);
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Use structuredClone() to deep-clone existing data array
Navigate to the aIdx of the cloned-array
Assign the given array (to be added) into aIdx's items prop
NOTE
This solution does not use lodash as it is not mandatory (to use lodash) to perform immutable operations.
If you want to maintain the immutability of original data, just map the content of original data to the new data as you want, and wrap your logic into a pure function to improve readability.
const dataOriginal = [{
"id": "1",
"items": [{
"title": "item 1"
},
{
"title": "item 2"
}
]
},
{
"id": "2",
"items": [{
"title": "item2 1"
},
{
"title": "item2 2"
}
]
}
]
const dataNew = createDataWithSomethingNew(dataOriginal, [{
"title": "item new 1"
},
{
"title": "item new 2"
}
])
function createDataWithSomethingNew(data, props) {
return data.map(function changeItemsOfId1ToProps(value) {
if (value.id === '1') {
return {
id: value.id,
items: props
}
} else {
return value
}
})
}
lodash has a method _.update can modify object with the correct path in string provided.
Another method _.cloneDeep can copy you object deeply. So that change in the pre-copied object will not affect the cloned object.
Finally use a deep freeze function to call Object.freeze recursively on the cloned object to make it immutable.
var data = [
{
"id":"1",
"items":[
{
"title":"item 1"
},
{
"title":"item 2"
}
]
},
{
"id":"2",
"items":[
{
"title":"item2 1"
},
{
"title":"item2 2"
}
]
}
]
var clonedData = _.cloneDeep(data) // copy the full object to avoid modify the source data
// update the data of that path '[0].items' in clonedData
_.update(clonedData, '[0].items', function(n) {
return [
{
"title":"item new 1"
},
{
"title":"item new 2"
}
]
})
// provide object immutability
const deepFreeze = (obj1) => {
_.keys(obj1).forEach((property) => {
if (
typeof obj1[property] === "object" &&
!Object.isFrozen(obj1[property])
)
deepFreeze(obj1[property])
});
Object.freeze(obj1)
};
deepFreeze(clonedData)
data[2] = {id: 3} // data will be changed
data[1].items[2] = {title: "3"} // and also this one
clonedData[2] = {id: 3} // nothing will be changed
clonedData[1].items[2] = {title: "3"} // and also this one
console.log(`data:`, data);
console.log(`clonedData:`, clonedData);
Runkit Example
I've got an object that i'm trying to map to a react component (using lodash). The current shape of the objects that I get back from my API (firebase) looks like this...
// ex. 1
{
"-Kdkahgiencls0dnh": {
"name": "a name",
"desc": "a description",
"other": "some other guff"
},
"-Ksdfadfvcls0dsnh": {
"name": "another name",
"desc": "another description",
"other": "some more"
},
"-kgoandiencls0dnh": {
"name": "I am a name",
"desc": "I am a description",
"other": "I am some other guff"
}
}
...but, I loose the primary key when i run through _.map()
What i'm trying to do is get my object in the shape of:
// ex. 2
[
{
"id": "-Kdkahgiencls0dnh",
"name": "a name",
"desc": "a description",
"other": "some other guff"
},
{... the next object ...},
{... etc ...}
]
What i'm doing now is getting my data in the componentWillMount lifecycle method like so:
componentWillMount() {
firebaseRef.on('value', snap => {
let data = snap.val() // the whole original object (see ex. 1)
let tempArray = [] // an array to store my newly formatted objects
_.forEach(data, (item, key) => {
// Here's where i'm not really sure what to do.
// I want to use Object.assign to set a new key:value
// That adds "id": "-theobjectsmainkey" to a new object
// then push to my tempArray and finally setState with the
// correctly formatted array of objects.
})
})
}
Ideas? Thoughts? Thanks.
You can use Object.entries(), .map() and object spread
const data = {
"-Kdkahgiencls0dnh": {
"name": "a name",
"desc": "a description",
"other": "some other guff"
},
"-Ksdfadfvcls0dsnh": {
"name": "another name",
"desc": "another description",
"other": "some more"
},
"-kgoandiencls0dnh": {
"name": "I am a name",
"desc": "I am a description",
"other": "I am some other guff"
}
}
let res = Object.entries(data).map(([id, prop]) => ({id, ...prop}));
console.log(res);
Lodash's _.map() callback receives as a 2nd parameter the iterated key. Use object assign, to create a new object with the key as id:
const array = _.map(data, (item, id) => Object.assign({ id }, item))
Demo:
const data = {"-Kdkahgiencls0dnh":{"name":"a name","desc":"a description","other":"some other guff"},"-Ksdfadfvcls0dsnh":{"name":"another name","desc":"another description","other":"some more"},"-kgoandiencls0dnh":{"name":"I am a name","desc":"I am a description","other":"I am some other guff"}};
const array = _.map(data, (item, id) => Object.assign({ id }, item));
console.log(array);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Here you go, using only pure JS :
const raw = {
"-Kdkahgiencls0dnh": {
"name": "a name",
"desc": "a description",
"other": "some other guff"
},
"-Ksdfadfvcls0dsnh": {
"name": "another name",
"desc": "another description",
"other": "some more"
},
"-kgoandiencls0dnh": {
"name": "I am a name",
"desc": "I am a description",
"other": "I am some other guff"
}
}
let formatted = Object.keys(raw).map(
key=>Object.assign(raw[key], {"id": ""+key})
);
Here is a fiddle to get a live demo.
componentWillMount() {
firebaseRef.on('value', snap => {
let data = snap.val() // the whole original object (see ex. 1)
let tempArray = Object.keys(data).map((item, key) => {
return {
"id": item,
"name": data[item].name // etc, a structure what you want
...
};
})
})
}
I have a json object as below in my web application. It's an array of product objects and each product object has a category property which contains an array of categories that the product belongs to.
var products = [
{
"id":1,
"name":"Product 1",
"price":10,
"category":[
{
"id":10,
"name":"Category 1"
},
{
"id":20,
"name":"Category 2"
}
]
},
{
"id":2,
"name":"Product 2",
"price":20,
"category":[
{
"id":20,
"name":"Category 2"
},
{
"id":30,
"name":"Category 3"
}
]
}
]
So now I want to display them grouped by categories so the end result will look like below. I am already using Underscore.js in my project so it will be good if I can use it to achieve this.
var categories = [
{
"id":10,
"name":"Category 1",
"products":[
{
"id":1,
"name":"Product 1",
"price":10
}
]
},
{
"id":20,
"name":"Category 2",
"products":[
{
"id":1,
"name":"Product 1",
"price":10
},
{
"id":2,
"name":"Product 2",
"price":20,
}
]
},
{
"id":30,
"name":"Category 3",
"products":[
{
"id":2,
"name":"Product 2",
"price":20,
}
]
}
]
I'm not entirely sure whether there is an out-of-the-box solution to this problem with underscore, however solving this by hand shouldn't be too hard, either:
var categoriesIndexed = {};
var categories = [];
products.forEach(function(product) {
product.category.forEach(function(category) {
// create a new category if it does not exist yet
if(!categoriesIndexed[category.id]) {
categoriesIndexed[category.id] = {
id: category.id,
name: category.name,
products: []
};
categories.push(categoriesIndexed[category.id]);
}
// add the product to the category
categoriesIndexed[category.id].products.push({
id: product.id,
name: product.name,
price: product.price
});
});
});
here is what I would do
var categories = [];
var cat = new Map();
var addUniqueCategory(category) { /* determine if category is already in list of categories, if not add it to categories */ };
products.each (function (item) {
item.categories.each(function (c) {
if (!cat.has(c.name)) cat.set(c.name, []);
var list = cat.get(c.name);
list.push( { id: item.id, name: item.name, price: item.price });
addUniqueCategory(c);
});
});
categories.each( function (c) {
var list = cat.get(c.name);
if (!c.products) c.products = [];
c.products.splice( c.length, 0, list);
});
roughly, I'm on a phone
How would I split the following AJAX response into three separate objects based on the attribute 'product_range' using JS/jQuery, i.e. one object array for all 'range-1' products, one for 'range-2' and so on?
[
{
title: "Product 1",
price: "12.00",
product_range: "range-1"
},
{
title: "Product 2",
price: "12.00",
product_range: "range-2"
},
{
title: "Product 3",
price: "12.00",
product_range: "range-3"
}
]
I would just use reduce and push items into an object that holds arrays.
var items = [
{
title: "Product 1",
price: "12.00",
product_range: "range-1"
},
{
title: "Product 2",
price: "12.00",
product_range: "range-2"
},
{
title: "Product 3",
price: "12.00",
product_range: "range-3"
}
];
var grouped = items.reduce( function (obj, item) {
if (!obj[item.product_range]) obj[item.product_range] = [];
obj[item.product_range].push(item);
return obj;
}, {});
console.log(grouped);
console.log(grouped["range-1"]);
Use the method "filterByProductRange" to filter out the data by product_range.
var obj = [
{
title: "Product 1",
price: "12.00",
product_range: "range-1"
},
{
title: "Product 2",
price: "12.00",
product_range: "range-2"
},
{
title: "Product 3",
price: "12.00",
product_range: "range-3"
}
];
function filterByProductRange(data, product_range) {
return data.filter(function(item) { return item['product_range'] == product_range; });
}
var range1= filterByProductRange(obj, 'range-1');
console.log(range1);
if you mean grouping your data by product_range, then:
//json is your json data
var result = {};
for(var i=0; i<json.length;i++)
{
if(result[json[i].product_range] === undefined) result[json[i].product_range] = [];
result[json[i].product_range].push(json[i]);
}
Something like this should group by range.
var ranged = {};
var data = [{
title: "Product 1",
price: "12.00",
product_range: "range-1"
}, {
title: "Product 2",
price: "12.00",
product_range: "range-2"
}, {
title: "Product 3",
price: "12.00",
product_range: "range-3"
}]
$.each(data, function(i, x) {
if (ranged[x.product_range]) {
ranged[x.product_range].push(x);
} else {
ranged[x.product_range] = [x];
}
});
console.log(JSON.stringify(ranged));
You should then be able to retrieve all object for a given range by querying the ranged object.
You can use $.map() to achieve a similar result.
var range1 = new Object(),
range2 = new Object(),
range3 = new Object();
$.map(data, function(d){
if (d.product_range === "range-1") {
for (var i in d) {
range1[i] = d[i];
}
}
});
Where data is your object array.
Here's a simple fiddle to demonstrate this.