Filtering out JSON by an array - javascript

I have a JSON file
{
"data": [
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}]
}
with all id's unique, and say I have an array of banned ids ["123","423"] and I would like to delete all entries that have an id number in the array (so as an output I'd like the following).
{
"data": [
{
"name": "Bob",
"id": "234"
}]
}
What would be a moderately efficient way (runs in a few seconds on an ordinary computer) to achieve this if there's a few thousand entries in the JSON and array?

You can use the Array.prototype.filter() method in conjunction with .indexOf():
var bannedIds = ["123", "423"];
var input = {
"data": [
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}]
};
input.data = input.data.filter(function(v) {
return bannedIds.indexOf(v.id) === -1;
});
console.log(input);
If you don't want to overwrite the original array then just assign the result of the .filter() call to a new variable.
If the above turns out to be too slow with your large amount of data, you can try replacing .filter() with a conventional for loop, and/or replacing .indexOf() with a lookup object created from the array of banned ids.

If you can use ES6, you can do this:
const source = {
"data": [
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}
]
};
const banned = ["123", "423"];
// O(n) startup cost for constant access time later
const bannedSet = new Set(banned);
// O(n)
const result = source.data.filter(x => !bannedSet.has(x.id));
console.log(result);
As mentioned in the comments, there's a startup cost for creating the Set. However, this lets you then call Set.prototype.has, which is constant.
Then, it's just a matter of iterating over every element and filtering out the ones that are in the banned set.
If you can't use ES6, you could replace Set with a plain JS object. If you have to support IE<9, use a polyfill for Array.prototype.filter (thanks #nnnnnn).
UPDATE
#SpencerWieczorek points out that the ES6 spec seems to indicate that Set.prototype.has iterates. I spoke too soon about the lookup being constant (I was carrying over my experience from other languages). Typically, sets will do better than O(n), e.g. constant or O(log n) depending on the underlying implementation. Your mileage may vary, so nnnnnn's answer may be faster in some cases.
Try a few of the solutions here with large amounts of data to confirm.

EDIT
I shied away from using filter or the like because that involves creating a new array. That's actually probably fine for the data sizes we're talking about, but the approach I have below is more efficient.
On my laptop, this whole program runs in about 0.2 seconds. (It uses 10,000 entries and 100 banned IDs.)
var o = {
data: []
};
for (var i = 0; i < 10000; i++) {
o.data.push({
name: i % 2 === 0 ? 'Jake' : 'Bob', // couldn't think of more names :-)
id: ''+i // convert to string
});
}
var banned = {};
for (var i = 0; i < 100; i++) {
banned[''+(i * 3)] = true; // ban 0, 3, 6, 9, 12, ...
}
for (var i = o.data.length - 1; i >= 0; i--) {
if (banned[o.data[i].id]) {
o.data.splice(i, 1);
}
}
console.log(o);
// { data:
// [ { name: 'Bob', id: '1' },
// { name: 'Jake', id: '2' },
// { name: 'Jake', id: '4' },
// { name: 'Bob', id: '5' },
// { name: 'Bob', id: '7' },
// { name: 'Jake', id: '8' },
// { name: 'Jake', id: '10' },
// ...

I am assuming that you have already parsed the JSON data and you have a variable pointing to the array you want to filter. Also, you have an array with the "banned" IDs.
var data = [{
"name": "Jake",
"id": "123"
}, {
"name": "Bob",
"id": "234"
}, {
"name": "Joe",
"id": "345"
}];
var banned = ["123", "345"];
The following function wil probably do the best job that can be done in terms of performance:
// Modifies the data array "in place", removing all elements
// whose IDs are found in the "banned" array
function removeBanned(data, banned) {
// Index the "banned" IDs by writing them as the properties
// of a JS object for really quick read access later on
var bannedObj = {};
banned.forEach(function(b) { bannedObj[b] = true; });
var index = data.length - 1;
while (index >= 0) {
if (bannedObj[data[index].id]) {
data.splice(index, 1);
}
--index;
}
}

This one seems fast enough, but I'd suggest you make a free clean copy instead of modifying the existing array, - it may be faster.
function filterout(o,p,f) {
var i = 0; f = f.join();
while( o[i] ) {
if( f.match( o[i][p] ) ){ o.splice(i,1) }
i++
};
}
var filter = ["123","423"];
var object =
{
"data": [
{
"name": "John",
"id": "723"
},
{
"name": "Jake",
"id": "123"
},
{
"name": "Bob",
"id": "234"
}]
};
filterout( object.data, "id", filter );
console.log(JSON.stringify( object ));

Related

Generating a Nested Set Model from a POJO

I have been playing around with some Nested Set Models (NSM). One thing I wanted to do is to be able to generate a NSM from a given JavaScript object.
For example, given the following object:
var data = {
Clothes: {
Jackets: {
Waterproof: true,
Insulated: true
},
Hats: true,
Socks: true
},
}
I'd like to generate an array of objects like so.
[
{
"name": "Clothes",
"lft": 1,
"rgt": 12
},
{
"name": "Jackets",
"lft": 2,
"rgt": 7
},
{
"name": "Waterproof",
"lft": 3,
"rgt": 4
},
{
"name": "Insulated",
"lft": 5,
"rgt": 6
},
{
"name": "Hats",
"lft": 8,
"rgt": 9
},
{
"name": "Socks",
"lft": 10,
"rgt": 11
}
]
That is - a depth first walk through the object, assigning an ID and counting the left and right edge for each object in the hierarchy. So that each node has a unique ID and the correct lft and rgt values for a NSM.
I've tried various approaches but just can't seem to get the result I am after...I had some success by altering the model to use properties for the node name and child nodes - i.e.
var data2 = {
name: "Clothes",
children: [{
name: "Jackets",
children: [{
name: "Waterproof",
}, {
name: "Insulated"
}]
}, {
name: "Hats"
},
{
name: "Socks"
}
]
};
function nestedSet(o, c, l = 0) {
let n = {
name: o.name,
lft: l + 1
};
c.push(n);
let r = n.lft;
for (var x in o.children) {
r = nestedSet(o.children[x], c, r);
}
n.rgt = r + 1;
return n.rgt;
}
let out = [];
nestedSet(data2, out);
console.log(out)
This gives the correct result but requires altering the input data...is there a way to generate the same Nested Set Model using the original data object?
I actually managed to solve this in the end...I just forgot about it for a long while! Basically all that is required is to reclusively pass the Object.entries as kindly suggested in #CherryDT's comment. This way one can resolve the name/children to build the nested set model as required.
var data = {
Clothes: {
Jackets: {
Waterproof: {},
Insulated: {},
},
Hats: {},
Socks: {},
},
};
function ns(node, stack = [], lft = 0) {
var rgt = ++lft;
var item = {
name: node[0],
lft: lft,
};
stack.push(item);
Object.entries(node[1]).forEach(function (c) {
rgt = ns(c, stack, rgt);
});
item.rgt = ++rgt;
return rgt;
}
var result = [];
ns(Object.entries(data)[0], result);
console.log(result);

Javascript - Create and populate associative array containing sub arrays

I'm trying to collate some data. I would like to populate an array containing sub arrays, for example, I have some json data that I am iterating over:
{
"name": "name1",
"prices": "209.67"
},
{
"name": "name1",
"prices": "350"
},
{
"name": "name2",
"price": "195.97"
},
I would like to create an array that ends up looking something like the following:
myArray['name1']prices[0] = 209.67,
prices[1] = 350,
['name2']prices[0] = 195.97
I thought that the code below would achieve what I wanted but it doesn't work. It throws an exception. It doesn't seem to recognise the fact that the prices are an array for a given index into the main array. Instead the prices appear at the same level as the names. I want the main array for a given name to contain an inner array of prices.. Does anybody have any idea how I could modify to make this work?
function doStuff() {
var cryptoData = getData();
var datasetValues = {};
datasetValues.names = [];
datasetValues.names.prices = [];
for (var result = 0; result < cryptoData.length; result++) {
var data = cryptoData[result];
if (datasetValues.names.indexOf(data.cryptoname) === -1)
{
datasetValues.names.push(data.cryptoname);
}
// This works
//datasetValues.names.prices.push(data.prices);
// This doesn't!
datasetValues.cryptoNames[data.cryptoname].prices.push(data.prices);
}
}
You could reduce the array by using an object and take a default object if the property is not set. Then push the price.
var data = [{ name: "name1", price: "209.67" }, { name: "name1", price: "350" }, { name: "name2", price: "195.97" }],
result = data.reduce((r, { name, price }) => {
r[name] = r[name] || { name, prices: [] };
r[name].prices.push(+price);
return r;
}, Object.create(null));
console.log(result);
Try this
function parseData(input){
return input.reduce(function(o,i){
o[i.name] = {};
if(!o[i.name]['prices']){
o[i.name]['prices'] = [];
}
o[i.name]['prices'].push(i.prices);
return o;
},{});
}

get at least two matches from json

I have this JSON
{
"people": [{
"games": [
"destiny",
"horizon",
"fifa",
"cuphead"
],
"name": "Cartman"
},
{
"games": [
"fifa",
"pes"
],
"name": "Kyle"
},
{
"games": [
"cuphead",
"destiny"
],
"name": "Stan"
},
{
"games": [
"pokemon",
"metroid"
],
"name": "Clyde"
},
{
"games": [
"fifa",
"metroid",
"pes"
],
"name": "Butters"
}
]
}
and I need to get the users that have at least two games in common so for this JSON the result will be:
Cartman and Stan plays destiny and cuphead
Kyle and Butters plays fifa and pes
Note: Clyde will be not in the results because theres only one game match with other player
I thought about two workarounds
1- have a dictionary using two tags (covering all possibilities) as a key and filled it if it doesnt exist and after other key match that way I will know that theres already two matches
2- get the first user compare it against the others and it if matches at least two printed it then continue with the next one and compare with the rest and so on
Any help will be appreciated thanks
An approach with reducing the games/peoples by checking if only one exists and delet then the respondend part of the other object.
It returns more items than one, but more than two.
For getting a result, you could eliminate the one with two parts and adjust the rest.
var object = { people: [{ games: ["destiny", "horizon", "fifa", "cuphead"], name: "Cartman" }, { games: ["fifa", "pes"], name: "Kyle" }, { games: ["cuphead", "destiny"], name: "Stan" }, { games: ["pokemon", "metroid"], name: "Clyde" }, { games: ["fifa", "metroid", "pes"], name: "Butters" }] },
peoples = {},
games = {},
change = false;
object.people.forEach(function (person) {
person.games.forEach(function (game) {
games[game] = games[game] || {};
games[game][person.name] = true;
peoples[person.name] = peoples[person.name] || {};
peoples[person.name][game] = true;
});
});
do {
change = false;
Object.keys(games).forEach(function (k) {
var keys = Object.keys(games[k]);
if (keys.length === 1) {
delete peoples[keys[0]][k];
delete games[k];
change = true;
}
});
Object.keys(peoples).forEach(function (k) {
var keys = Object.keys(peoples[k]);
if (keys.length === 1) {
delete games[keys[0]][k];
delete peoples[k];
change = true;
return;
}
});
} while (change);
console.log(peoples);
console.log(games);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Given a value, search for an adjacent key:value in an object

Given a data structure like this:
var userList= [
{
employeeId: 10,
name: "Bill",
},
{
employeeId: 12,
name: "Tom",
},
{
employeeId: 14,
name: "Sue",
},
]
And an input of 10 how can I find the value Bill?
So, in other words, given a value that pairs with another Key: Value in an array of objects, how might I search for the other keys/values within that object?
I'm guessing step 1 involves searching for the value within each object in the array and step 2 involves returning the index of the object within the array meaning step 3 would be mapping to that index somehow.
EDIT: So, I ultimately just used a function with a for...in loop to solve this problem as shown below. While I may have done a particularly poor job of articulating my problem I was trying to take an input of an employee number and match it with an employee's name. The for...in loop seems to be the simplest I can find but I was looking for a more elegant object method or some such to find it. Cheers!
function personLookup(x){
for (i = 0; i<userList.length; i++){
if (userList[i].employeeID== x){
return userList[i].name;
}
};
}
Arrays have a .find function which can be used to look for an element that matches a certain condition. So to find the person with employeeNumber 10, you can do the following:
let employees = [{
name: 'Jill',
employeeNumber: 9
}, {
name: 'Bill',
employeeNumber: 10
}];
let numberTen = employees.find(function (person) {
return person.employeeNumber == 10;
});
if (numberTen !== undefined) {
console.log(numberTen.name)
}
Note that .find is appropriate if you want exactly one result, but if there are possibly multiple that match the criterion you're looking for, .filter is a better choice.
See documentation for these at:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
You can do
let person= {
"name" : "Bill",
"employeeNumber" : 10
}
for(let element of Object.keys(person)){
if(person[element] == 10){
console.log(element);
console.log(person.name);
}
}
If there are multiple number 10s in your data, you can use .filter
let employees = [{
name: 'Jill',
employeeNumber: 9
}, {
name: 'Bill',
employeeNumber: 10
}, {
name: 'Billy',
employeeNumber: 10
}
}];
const employData = employees.filter(employee => employee.employeeNumber == 10)
if(employData.length > 0){
console.log("names are ", employData.map(employee => employee.name))
} else {
console.log("no employ found")
}
So the for...in loop seemed to be the easiest in the long run.
https://codepen.io/mizzoudavis/pen/pWNvxw
var userList= [
{
'employeeID': 10,
'name': "Bill",
},
{
'employeeID': 12,
'name': "Tom",
},
{
'employeeID': 14,
'name': "Sue",
},
]
var x;
$('#submit').click(function(){
x = $('#empID').val();
personLookup(x);
})
function personLookup(x){
for (i = 0; i<userList.length; i++){
if (userList[i].employeeID== x){
$('div').text(userList[i].name);
}
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="empID"/>
<button id="submit">submit</button>
<div></div>

how to get unique values from array of objects with addition of numbers

I'm working in wso2 carbon dashboard. My table is containing 2 fields (Name and Number). I Have duplicate name in the objects but with different number. I want unique name with addition of numbers.
[
{
"Name":"Abc",
"number":2
},
{
"Name":"bcd",
"number":3
},
{
"Name":"Abc",
"number":5
}
]
expected output
[
{
"name":"Abc",
"Number":7
},
{
"name":"bcd",
"Number":3
}
]
I'm using java script to achieve such task. please help me
Use Array#reduce method with a reference object.
var data = [{
"Name": "Abc",
"number": 2
}, {
"Name": "bcd",
"number": 3
}, {
"Name": "Abc",
"number": 5
}];
// object for index reference
var ref = {};
// iterate and generate the array
var res = data.reduce(function(arr, o) {
// check index already defined
if (!(o.Name in ref)) {
// if not defined then define index
ref[o.Name] = arr.length;
// and push the element
// you can also use
// arr.push({Name:o.Name, number:o.number});
arr.push(Object.assign({}, o));
} else {
// if index already defined update the number
arr[ref[o.Name]].number += o.number;
}
// return the array reference
return arr;
// set initial value as empty array
}, []);
console.log(res);

Categories

Resources