filter JSON by two different values - javascript

I'm using a function to filter a JSON file based on the value of the year key, like so:
function filterYr(json, key, value) {
var result = [];
for (var year in json) {
if (json[year][key] === value) {
result.push(json[year]);
}
}
return result;
}
I'm then setting a default:
var chooseYear = filterYr(json, 'year', "2000");
However there's also a dropdown, so the JSON file can be filtered onchange of the dropdown select option.
My question is, can I use this same function to filter the same JSON file by another value, too?
So for instance, I also want to filter by the key 'type.'
If it were a new function it'd be:
function filterType(json, key, value) {
var result = [];
for (var type in json) {
if (json[type][key] === value) {
result.push(json[type]);
}
}
return result;
}
But how do I combine that into one function?
And then how do I set a default that passes both the 'type' and the 'year' to the function?
Is that possible?
Thank you and let me know if I can provide more detail if this isn't clear.
PS- I'd prefer to just use javascript and not a library, if possible.

If your data structure is like below, your current function just works well
var items = [
{
year: 2000,
type: 'type1'
},
{
year: 2001,
type: 'type2'
}
];
function filterYr(json, key, value) {
var result = [];
for (var year in json) {
if (json[year][key] === value) {
result.push(json[year]);
}
}
return result;
}
filterYr(items, 'type', 'type2'); //[ { year: 2001, type: 'type2' } ]
filterYr(items, 'year', 2000); //[ { year: 2000, type: 'type1' } ]
You just need to use a more general name for your function and year variable

You can modify the function so it accepts an object as criterion for filtering. The following function accepts an object with n number of properties:
function findWhere(collection, props) {
var keys = Object.keys(props), // get the object's keys
klen = keys.length; // cache the length of returned array
return collection.filter(function(el) {
// compare the length of the matching properties
// against the length of the passed parameters
// if both are equal, return true
return keys.filter(function(key) {
return el[key] === props[key];
}).length === klen;
})
}
var filteredCollection = findWhere(arrayOfObjects, {
type: 'foo',
anotherProp: 'aValue'
});

Related

Recursively adding and removing values from javascript object

So I'm building a basic facet search in React using data from an API, but I'm struggling with the adding & removing of values from the payload sent back to the server. The payload object can contain Arrays, Objects & Strings. So say I have a payload structure like the following:
payload = {
search_query: ""
topic: [],
genre: [],
rating: "",
type: {}
}
Using deepmerge I'm able to pass multiple values back to the API which is working fine, so an example payload would be...
payload = {
search_query: "Luther"
topic: ["9832748273", "4823794872394"],
genre: ["3827487483", "3287483274873"],
rating: "18",
type: {
args: {
when: "today",
min: 15
},
name: "spot"
}
}
So far so good, I get the expected results back. Now I have a toggle on the facet to remove it from the payload, which sends back the value to a function to remove from the payload. For example:
Clear Search, value to remove = "Luther"
Toggle of topic, value to remove = "9832748273"
Toggle of genre, value to remove = "3827487483"
Toggle of rating, value to remove = "18"
Toggle of type, value to remove = { args: { when: "today", min: 15}, name: "spot"}
Search & Rating would return empty strings, topic & genre would remove items from the arrays and type would return an empty object.
This works for removing the array values but feels dirty and I want a clean way to handle all types!
const removeObjValue = (obj, filterValue) => {
Object.entries(obj).forEach(([key, value]) => {
Object.entries(value).forEach(([subKey, subValue]) => {
if(subvalue === filterValue) {
if(Array.isArray(value)) {
const index = value.indexOf(subvalue);
if (index > -1) {
value.splice(index, 1);
}
}
}
});
});
return obj;
}
I just use delete keyword to remove object attributes, like this
if (payload[key]) {
if (payload[key] instanceof Array) {
var idx = payload[key].indexOf(value);
if (idx > -1) payload[key].splice(idx, 1);
} else {
delete payload[key];
}
}
code example

How to return new array with dynamically populated properties?

So my call returns something like:
data:
{
nameData: 'Test33333',
emailData: email#email.com,
urlLink: link.com
additionalDetails: [
{
field: 'email',
value: 'other#email.com'
},
{
field: 'name',
value: 'name1223'
}
]
}
Now, I want to make a function that would take the passed parameter (data) and make an array of objects, that should look like below. It should be done in more generic way.
Array output expectation:
fullData = [
{
name: 'data_name'
value: 'Test33333'
},
{
name: 'data_email',
value: 'email#email.com'
},
{
name: 'data_url',
value: 'Link.com'
},
extraData: [
//we never know which one will it return
]
];
It should be done in the function, with name, for example:
generateDataFromObj(data)
so
generateDataArrFromObj = (data) => {
//logic here that will map correctly the data
}
How can this be achieved? I am not really proficient with JavaScript, thanks.
Assuming that you keep your data property keys in camelCase this will work for any data you add, not just the data in the example. Here I've used planetLink. It reduces over the object keys using an initial empty array), extracts the new key name from the existing property key, and concatenates each new object to the returned array.
const data = { nameData: 'Test33333', emailData: 'email#email.com', planetLink: 'Mars' };
function generateDataArrFromObj(data) {
const regex = /([a-z]+)[A-Z]/;
// `reduce` over the object keys
return Object.keys(data).reduce((acc, c) => {
// match against the lowercase part of the key value
// and create the new key name `data_x`
const key = `data_${c.match(regex)[1]}`;
return acc.concat({ name: key, value: data[c] });
}, []);
}
console.log(generateDataArrFromObj(data));
Just run a map over the object keys, this will return an array populated by each item, then in the func map runs over each item, build an object like so:
Object.keys(myObj).map(key => {return {name: key, value: myObj[key]}})

Javascript forEach where value equals value in different array

Javascript:
$(document).ready(function() {
const ores = "../js/json/oreList.json";
const priceURL = "https://esi.tech.ccp.is/latest/markets/prices/?datasource=tranquility";
let oreArray = [];
let priceArray = [];
let total = 0;
// Retrieve list of ores
function getOres() {
$.getJSON(ores, function(ores) {
ores.forEach(function(ore) {
total++;
if (total === 48) {
getPrices();
}
oreArray.push(ore);
});
});
}
// Retrieve all items & prices via API
function getPrices() {
$.getJSON(priceURL, function(prices) {
prices.forEach(function(data) {
priceArray.push(data);
console.log(data);
});
});
}
getOres();
});
The first function creates an internal array from my .JSON file and the second function creates an internal array from the URL.
In the first array oreArray, an object looks like this:
{ id: 1234, name: "Title" }
In the second array priceArray, an object looks like this:
{ type_id: 1234, average_price: 56.34 }
My oreArray has 48 objects and unfortunately the priceArray has about 11,000 objects. I need to create a new array by comparing the two arrays and building new objects, where the ID's match. So for example objects in newArray would look like:
{ id: 1234, name: "Title", average_price: 56.34 }
Basically I'm having trouble figuring out the logic for:
For each object in oreArray, find the object with the same ID value in priceArray and append the new array with a new object using values from both arrays.
I would do it this way:
const ores = "../js/json/oreList.json",
priceURL = "https://esi.tech.ccp.is/latest/markets/prices/?datasource=tranquility";
let oreArray,
priceArray,
joinedArray = [];
function getOres() {
$.getJSON(ores, function(ores) {
oreArray = ores;
getPrices();
});
}
function getPrices() {
$.getJSON(priceURL, function(prices) {
priceArray = prices;
joinPrices();
});
}
function joinPrices() {
oreArray.forEach(function(ore) {
var matchingPrice = getMatchingPrice(ore);
if(matchingPrice !== false) {
joinedArray.push({
id: ore.id,
name: ore.name,
average_price: matchingPrice.average_price
});
}
});
}
function getMatchingPrice(ore) {
for(var i=0; i<priceArray.length; i++) {
if(priceArray[i].type_id === ore.id) {
return priceArray[i];
}
}
return false;
}
getOres();
I think that a good way to approach this problem is by changing the data structure of the average prices a little bit.
Instead of having them in an array, where each item has type_id and average_price field, you might want to consider using an object to store them, where the key is the type_id and the value is the average_price.
To be more concrete, you can replace:
prices.forEach(function(data) {
priceArray.push(data);
});
With:
const pricesMap = {};
prices.forEach(price => {
pricesMap[price.type_id] = price.average_price
});
And when looping on the oreArray, you can access each product's average_price by simply referring to pricesMap[ore.id]
You can check out this JSBin: http://jsbin.com/fogayaqexe/edit?js,console
You can use reduce to loop over each oreArr item and collect the data you need in the accumulator:
var oreArr=[
{ id: 1234, name: "Title" },
{ id: 2234, name: "2Title" },
]
var priceArr= [
{ type_id: 1234, average_price: 56.34 },
{ type_id: 2234, average_price: 256.34 },
{ type_id: 3234, average_price: 56.34 },
{ type_id: 4234, average_price: 56.34 },
]
var resArr = oreArr.reduce((ac,x) => {
var priceMatch = priceArr.find( z => z.type_id === x.id )
if(! priceMatch)
return ac //bail out if no priceMatch found
var res = Object.assign({}, x, priceMatch)
ac.push(res)
return ac
},[])
console.log(resArr)
other methods used:
arrayFind to check intersection
Object.assign to create the merged object to populate the accumulator
I suggest you to change your small json as object
eg : '{"1234":{"id": 1234, "name": "Title" }}';
var json = '{"1234":{"id": 1234, "name": "Title" }}';
oreArray = JSON.parse(json);
alert(oreArray['1234'].name); // oreArray[priceArraySingle.id].name
we can easily match priceArray id with oreArray.

React: Calling filter on Object.keys

A React component is passed a state property, which is an object of objects:
{
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
}
It is also passed (as a router parameter) a name. I want the component to find the matching object in the things object by comparing name values.
To do this I use the filter method:
Object.keys(this.props.things).filter((id) => {
if (this.props.things[id].name === this.props.match.params.name) console.log('found!');
return (this.props.things[id].name === this.props.match.params.name);
});
However this returns undefined. I know the condition works because of my test line (the console.log line), which logs found to the console. Why does the filter method return undefined?
Object.keys returns an array of keys (like maybe ["2"] in your case).
If you are interested in retrieving the matching object, then you really need Object.values. And if you are expecting one result, and not an array of them, then use find instead of filter:
Object.values(this.props.things).find((obj) => {
if (obj.name === this.props.match.params.name) console.log('found!');
return (obj.name === this.props.match.params.name);
});
Be sure to return that result if you use it within a function. Here is a snippet based on the fiddle you provided in comments:
var state = {
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
};
var findThing = function(name) {
return Object.values(state.things).find((obj) => {
if (obj.name === name) console.log('found!');
return obj.name === name;
});
}
var result = findThing('fridge');
console.log(result);
You need to assign the result of filter to a object and you get the result as the [id]. You then need to get the object as this.props.things[id]
var data = {
things: {
1: {
name: 'fridge',
attributes: []
},
2: {
name: 'ashtray',
attributes: []
}
}
}
var name = 'fridge';
var newD = Object.keys(data.things).filter((id) => {
if (data.things[id].name === name) console.log('found!');
return (data.things[id].name === name);
});
console.log(data.things[newD]);

Alternative for _.invert() using underscore library

I need to change the existing map swapping keys into values and values into keys. As there is duplicate values in my map for the keys I cannot use _.invert() of underscore library.
function map() {
return {
'eatables': {
apple: 'fruits',
orange: 'fruits',
guava: 'fruits',
brinjal: 'vegetables',
beans: 'vegetables',
rose: 'flowers',
}
}
}
var reverseMap = _.invert(map()['eatables']);
// invert function works for distinct values.
console.log (reverseMap);
// which is giving Object {fruits: "guava", vegetables: "brinjal",flowers:"rose"}
But i am expecting an output as
Object {fruits: ["apple","orange","guava"], vegetables: ["brinjal","beans"], flowers:"rose"}
I tried as below, i just stuck how to find whether map value is distinct or multiple?
var newObj = invert(map()['eatables']);
_.each(newObj, function(key) {
if (Array.isArray(key)) {
_.each( key, function(value) {
console.log(value);
});
} else {
console.log("else:"+key);
}
});
function invert(srcObj) {
var newObj = {};
_.groupBy(srcObj, function(value, key ) {
if (!newObj[value]) newObj[value] = []; //Here every thing is array, can i make it string for values which are unique.
newObj[value].push(key);
});
return newObj;
}
Let me any alternative using underscore library.
You can use this function. This function uses Object.keys to generate an array containing the keys of the object passed in input. Then, it accesses the values of the original object and use them as key in the new object. When two values map to the same key, it pushes them into an array.
function invert(obj) {
var result = {};
var keys = Object.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (result[obj[keys[i]]] instanceof Array) {
result[obj[keys[i]]].push(keys[i])
} else if (result[obj[keys[i]]]) {
var temp = result[obj[keys[i]]];
result[obj[keys[i]]] = [temp, keys[i]];
} else {
result[obj[keys[i]]]=keys[i];
}
}
return result;
}
https://jsfiddle.net/6f2ptxgg/1/
You can use the underscore each to iterate through your data and push the result in an array. It should give you your expected output.
function customInvert(data) {
var result = {};
_.each(data, function (value, key) {
if (_.isUndefined(result[value])) {
result[value] = key;
} else if(_.isString(result[value])) {
result[value] = [result[value], key];
} else {
result[value].push(key)
}
});
return result;
}
customInvert({
apple: 'fruits',
orange: 'fruits',
guava: 'fruits',
brinjal: 'vegetables',
beans: 'vegetables',
rose: 'flowers',
})

Categories