How to check an array inside json data is empty using jmespath? - javascript

I have json data as below:
[
{
"id": "i_1",
"name": "abc",
"address": [
{
"city": [
"city1",
"city2"
]
},
{
"city": [
"city1",
"city2"
]
}
]
},
{
"id": "i_2",
"name": "def",
"address": [
{
"city": []
},
{
"city": []
}
]
}
]
Now, I want only that data where city array is not null. So in the above example the output should be 1st element i.e. with id i_1.
How to filter this json using jmespath library?

You can do this:
var arr = [
{
"id": "i_1",
"name": "abc",
"address": [
{
"city": [
"city1",
"city2"
]
},
{
"city": [
"city1",
"city2"
]
}
]
},
{
"id": "i_2",
"name": "def",
"address": [
{
"city": []
},
{
"city": []
}
]
}
];
console.log(jmespath.search(arr,"[?not_null(address[].city[])]"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jmespath/0.15.0/jmespath.js"></script>

You could do this in pure javascript using filter and every
const items=[{"id":"i_1","name":"abc","address":[{"city":["city1","city2"]},{"city":["city1","city2"]}]},{"id":"i_2","name":"def","address":[{"city":[]},{"city":[]}]}]
const filtered = items.filter(i => i.address.every(a => a.city && a.city.length > 0))
console.log(filtered)
This returns only if every object inside address has a non-empty city array.

var arr = [
{
"id": "i_1",
"name": "abc",
"address": [
{
"city": [
"city1",
"city2"
]
},
{
"city": [
"city1",
"city2"
]
}
]
},
{
"id": "i_2",
"name": "def",
"address": [
{
"city": []
},
{
"city": []
}
]
}
];
console.log(jmespath.search({ c: arr}, "not_null(c[].address[].city[])"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jmespath/0.15.0/jmespath.js"></script>
I don't know what it's your result. can you explain better?

You don't need to use jmespath library use filter and `every from the vanilla JS. It is more efficient.
let jsonTxt = '{"data":[{"id":"i_1","name":"abc","address":[{"city":["city1","city2"]},{"city":["city1","city2"]}]},{"id":"i_2","name":"def","address":[{"city":[]},{"city":[]}]}]}'
let jsonData = JSON.parse(jsonTxt);
let items = jsonData.data;
const result = items.filter(i => i.address.every(a => a.city && a.city.length))
console.log('id: ', result[0].id);
//using jmespath
console.log(jmespath.search({data: items}, "data[*].address[*].city"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jmespath/0.15.0/jmespath.js"></script>

Related

Group redundant objects into array js

I have a list of Cards :
"Cards": [
{
"BIN": "308103",
"TIER": {
"Nums": "1",
"Phone": "111111111 *",
}
},
{
"BIN": "308103",
"TIER": {
"Nums": "2",
"Phone": "0000000000",
}
},
{
"BIN": "308516",
"TIER": {
"Nums": "",
"Phone": "00000000 *",
}
},
]
What i need to do is merge all cards having similair BIN into the attribute TIER of the first object in the list.
The requested output is :
"Cards": [
{
"BIN": "308103",
"TIER": [
{
"Nums": "1",
"Phone": "111111111 *",
},
{
"Nums": "2",
"Phone": "0000000000",
}
],
},
{...}
I did it using combined loops into each other but i want a performance-friendly solution.
You can group by BIN by destructing each item and reducing them by assigning them to a BIN key and then accessing the values of the map.
const data = {
"Cards": [
{ "BIN": "308103", "TIER": { "Nums": "1", "Phone": "111111111 *" } },
{ "BIN": "308103", "TIER": { "Nums": "2", "Phone": "0000000000" } },
{ "BIN": "308516", "TIER": { "Nums": "", "Phone": "00000000 *" } }
]
};
const groupedByBin = {
Cards: Object.values(data.Cards.reduce((acc, { BIN, TIER: { Nums, Phone }}) =>
({ ...acc, [BIN] : { BIN, TIER: [...(acc[BIN]?.TIER ?? []), { Nums, Phone }] }}), {}))};
console.log(groupedByBin);
.as-console-wrapper { top: 0; max-height: 100% !important; }
let's say cardsArray was you original array.
const binMap = {};
cardsArray.forEach(card => {
if(binMap[card.BIN])
binMap[card.BIN].TIER.push(card.TIER);
else
binMap[card.BIN] = {
BIN: card.BIN
TIER: [ card.TIER ]
};
});
const finalArray = Object.values(binMap);
with finalArray being the desired output.
Note: Object.values is not accepted in all browsers, so if it doesn't work, you could do some additional steps:
const finalArray = [];
Object.keys(binMap).forEach(bin => finalArray.push(binMap[bin]));

How do i take items from an array and nest them in the correct order?

so I can't solve this issue. I have the following arrays:
var array1 = [
USA,
Georgia,
Atlanta
]
var array2 = [
USA,
New York
]
This is what I want:
{
"counties": [
{
"name": "USA",
"id": "1",
"children": [
{
"name": "Georgia",
"id": "1.1",
"parentId": "1",
"children": [
{
"name": "Atlanta",
"id": "1.1.1",
"parentId": "1.1"
}
]
},
{
"name": "New York",
"id": "1.2",
"parentId": "1"
}
]
}
]
}
The trick is to not to have any duplications, if there is any duplications on the same level, they should be merged into one and have both's children under. Like USA in the example.
I've got this transform methods. Pass any amount of arrays in parameters :
Demo:
const array1 = [
'USA',
'Georgia',
'Atlanta'
]
const array2 = [
'USA',
'New York'
]
const transform = (...arrays) => {
const result = [];
arrays.forEach(array => {
let node = result;
let parentID = ''
array.forEach(item => {
const current = node.find(c => c.name === item);
if(current){
node = current.children
}else{
const newNode = {
name: item,
children: [],
id: parentID === '' ? (node.length+1) + '' : parentID + '.' + (node.length+1),
parentID
};
parentID = newNode.id;
node.push(newNode);
node = newNode.children
}
})
})
return {counties: result}
}
const result = transform(array1, array2);
console.log(result);
// {
// "counties": [
// {
// "name": "USA",
// "id": "1",
// "parentID": "",
// "children": [
// {
// "name": "Georgia",
// "id": "1.1",
// "parentID": "1",
// "children": [
// {
// "name": "Atlanta",
// "id": "1.1.1",
// "parentID": "1.1",
// "children": []
// }
// ]
// },
// {
// "name": "New York",
// "children": [],
// "id": "2",
// "parentID": ""
// }
// ]
// }
// ]
// }

Object transformation using a functional approach

I am reading a simple data set from a data.txt file. I would like to take this data and transform it into a specific object as per my example below. I have managed to get it into a somewhat usable JSON object but this is not ideal. I have included an example of the desired object.
Here is my app.js file:
let output = fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n')
.map((line) => line.split(';'))
.reduce((customers, line) => {
customers.push({
name: line[0],
product: [{
item: line[1],
serial: line[2],
year: line[3]
}]
})
return customers
}, [])
console.log(JSON.stringify(output, null, 2))
This currently the above NodeJs code returns the following array object:
[
{
"name": "Nancy",
"product": [
{
"item": "Macbook Pro",
"serial": "A34D05980FCD4303",
"year": "2019"
}
]
},
{
"name": "Nancy",
"product": [
{
"item": "iPad",
"serial": "O0403X3028423C92",
"year": "2015"
}
]
},
{
"name": "Nancy",
"product": [
{
"item": "iPhone",
"serial": "X3830238S3309230",
"year": "2017"
}
]
},
{
"name": "John",
"product": [
{
"item": "Macbook Pro",
"serial": "X2020J393983H380",
"year": "2013"
}
]
},
{
"name": "John",
"product": [
{
"item": "iPhone",
"serial": "X38320093X032309",
"year": "2015"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iMac",
"serial": "F392D392033X3232",
"year": "2013"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iPad",
"serial": "FE322230D3223S21",
"year": "2011"
}
]
}
]
What I am trying to do is get the below object returned - ideally still following the same functional approach:
[
{
"name": "Nancy",
"product": [
{
"item": "Macbook Pro",
"serial": "A34D05980FCD4303",
"year": "2019"
},
{
"item": "iPad",
"serial": "O0403X3028423C92",
"year": "2015"
},
{
"item": "iPhone",
"serial": "X3830238S3309230",
"year": "2017"
}
]
},
{
"name": "John",
"product": [
{
"item": "Macbook Pro",
"serial": "X2020J393983H380",
"year": "2013"
},
{
"item": "iPhone",
"serial": "X38320093X032309",
"year": "2015"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iMac",
"serial": "F392D392033X3232",
"year": "2013"
},
{
"item": "iPad",
"serial": "FE322230D3223S21",
"year": "2011"
}
]
}
]
Here is my mock data set that lives in data.txt
Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011
Instead of an array you can use Map in reduce as accumulator, use name as key in Map and club value of all keys, finally just get the values Map to get desired output
const data = `Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011`
const final = data.split('\n')
.map(v => v.split(';'))
.reduce((op, [name, item, serial, year]) => {
let obj = { item, serial, year }
if (op.has(name)) {
op.get(name).products.push(obj)
} else{
op.set(name,{name, products:[obj]})
}
return op
}, new Map())
console.log([...final.values()])
Here is a "functional version" that utilizes a Map to find duplicates in O(1):
(map => (
fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n')
.map((line) => line.split(';'))
.forEach(([name, item, serial, year]) =>
map.has(name)
? map.get(name).product.push({ item, serial, year })
: map.set(name, { name, product: [{ item, serial, year }] })
),
[...map.values()]
)(new Map)
But seriously, whats so bad about imperative style?:
const customers = new Map;
const entries = fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n');
for(const entry of entries) {
const [name, item, serial, year] = entry.split(";");
const product = { item, serial, year };
if(customers.has(name)) {
customers.get(name).product.push(product);
} else customers.set(name, { name, product: [product] });
}
const result = [...customers.values()];
You can modify the .reduce function to only add a new item to the array if there isn't one with that name. If there is, just add the product to that item's product array.
const data = `Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011`;
const result = data.trim()
.split('\n')
.map((line) => line.split(';'))
.reduce((customers, line) => {
const product = {
item: line[1],
serial: line[2],
year: line[3]
};
const customer = customers.find(({
name
}) => name === line[0]);
if (customer) {
customer.product.push(product);
} else {
customers.push({
name: line[0],
product: [product]
});
}
return customers
}, []);
console.log(result);

Check for duplicated objects in an array populated dynamically

I'm trying to build a data structure where all elements would be grouped based on an object key.
Everything works fine except that I can't check if the new array has the data duplicated, as it's outside the for..of loop. I'm looking for a way to prevent pushing a further object if the new array already has it.
Current output (note that the list of characters from Japan appear twice)
[
[
{ "country": "US" },
[
{ "name": "Guile", "country": "US" }
]
],
[
{ "country": "Japan" },
[
{ "name": "E. Honda", "country": "Japan" },
{ "name": "Ryu", "country": "Japan" }
]
],
[
{ "country": "Japan" },
[
{ "name": "E. Honda", "country": "Japan" },
{ "name": "Ryu", "country": "Japan" }
]
],
[
{ "country": "Thailand" },
[
{ "name": "Sagat", "country": "Thailand" }
]
]
]
Expected output
[
[
{ "country": "US" },
[
{ "name": "Guile", "country": "US" }
]
],
[
{ "country": "Japan" },
[
{ "name": "E. Honda", "country": "Japan" },
{ "name": "Ryu", "country": "Japan" }
]
],
[
{ "country": "Thailand" },
[
{ "name": "Sagat", "country": "Thailand" }
]
]
]
What I have so far
var data = [
{name: 'Guile', country: 'US'},
{name: 'E. Honda', country: 'Japan'},
{name: 'Ryu', country: 'Japan'},
{name: 'Sagat', country: 'Thailand'}
]
const getNationList = (streetFighterList) => {
let filteredList = []
for (const [index, characterData] of streetFighterList.entries()) {
// .......................................................
// looking for ways here to check if `filteredList` already
// has the data I'm trying to push. Since it's empty
// I don't know how to check its index. :(
// NOTE: indexOf() doesn't seem to work
// .......................................................
const indexOf = filteredList.indexOf(streetFighterList[index].country)
if (indexOf == -1) {
filteredList.push([
{ country: characterData.country },
streetFighterList.filter((character) => {
return character.country === characterData.country
})
])
}
}
return filteredList
}
console.log(getNationList(data))
Note: I understand that given the country object is always unique this data structure would be better and easier if I used a string instead. However this is a sample data and in a real life code I do need it stored as an object.
I would recommend using some to validate as following
var data = [
{name: 'Guile', country: 'US'},
{name: 'E. Honda', country: 'Japan'},
{name: 'Ryu', country: 'Japan'},
{name: 'Sagat', country: 'Thailand'}
]
const getNationList = (streetFighterList) => {
let filteredList = []
for (const [index, characterData] of streetFighterList.entries()) {
const entry = filteredList.some(item => item[0].country === streetFighterList[index].country)
if (!entry) {
filteredList.push([
{ country: characterData.country },
streetFighterList.filter((character) => {
return character.country === characterData.country
})
])
}
}
return filteredList
}
console.log(getNationList(data))
Reduce the array to object, with the country names as the keys. Combine players that are under the same country to an object, with the player's name as the key.
When done, convert back to an array using Object.values(), and map the array to convert the player's objects to arrays via Object.values() as well.
const data = [[{"country":"US"},[{"name":"Guile","country":"US"}]],[{"country":"Japan"},[{"name":"E. Honda","country":"Japan"},{"name":"Ryu","country":"Japan"}]],[{"country":"Japan"},[{"name":"E. Honda","country":"Japan"},{"name":"Ryu","country":"Japan"}]],[{"country":"Thailand"},[{"name":"Sagat","country":"Thailand"}]]]
const result = Object.values(data.reduce((r, [c, p]) => {
if(!r[c.country]) r[c.country] = [c, {}]
const players = r[c.country][1];
p.forEach(o => { if(!players[o.name]) players[o.name] = o; })
return r;
}, {})).map(([c, p]) => [c, Object.values(p)]);
console.log(result)
You could first create a Set of unique countries and then loop through them to combine each one with a list of fighters from that country. For example:
const data = [{ name: 'Guile', country: 'US' }, { name: 'E. Honda', country: 'Japan' }, { name: 'Ryu', country: 'Japan' }, { name: 'Sagat', country: 'Thailand' }];
const countries = new Set(data.map((obj) => obj.country));
const output = [...countries].map((country) => {
const entries = data.filter((obj) => obj.country === country);
return [{ country }, entries];
});
console.log(output);
/*
[
[
{"country": "US"},
[{"name": "Guile", "country": "US"}]
],
[
{"country": "Japan"},
[{"name": "E. Honda", "country": "Japan"}, {"name": "Ryu", "country": "Japan" }]
],
[
{"country": "Thailand"},
[{"name": "Sagat", "country": "Thailand"}]
]
]
*/

javascript map object multidimension

i have this data structure:
tree = [
{
"name": "Men Section",
"categories": [
{
"name": "Clothings",
"Subcategories": [
{
"name": "Jackets",
"products": [
{
"name": "jacket 01",
"price": 100
},
{
"name": "jacket 02",
"price": 140
},
// ..and so on
]
]
},
// ..and so on
]
} // ..and so on
]
how can i add new property in products item isSelected: false in javascript (ES5 or ES6 is fine) so the object will be
{
"name": "jacket 01",
"price": 100,
"isSelected": false
}
?
It could be useful, it works for me
tree.forEach(base => {
base.categories.forEach(categories => {
categories.Subcategories.forEach(subCategory =>{
subCategory.products.forEach(product => product.isSelected = false)
})
});
});
so yeah, i should traverse deep to the object with nested foreach. i came up with this:
tree.categories.forEach(function (category) {
category.subcategories.forEach(function (subcategory) {
subcategory.products.forEach(function (product) {
product.isSelected = false;
})
})
})
thanks for the enlightment

Categories