I'm relatively new to nodeJS/Javascript's asynchronous nature (Python background) and trying am trying to figure how to step through a nested JSON object, extract it's values using asnyc.js.
I came across this snippet, How to navigate in nested JSON.
function recursiveGetProperty(obj, lookup, callback) {
for (property in obj) {
if (property == lookup) {
callback(obj[property]);
} else if (obj[property] instanceof Object) {
recursiveGetProperty(obj[property], lookup, callback);
}
}
}
Which works great with this sample object, foo.
var foo = {
'key_1' : 'val1',
'key_2': {
'key_3': 'val3',
'key_4': 'val4'
}
}
recursiveGetProperty(foo, 'key_1', function(obj) {
console.log(obj);
});
returns 'val1'
recursiveGetProperty(foo, 'key_3', function(obj) {
console.log(obj);
});
returns 'val3'
This is exactly what I needed but when I feed it key values via iteration:
var keys = ['val1', 'val3'];
for (var keys in keys) {
recursiveGetProperty(foo, keys, function(obj) {
console.log(obj);
});
}
nothing is logged to console. so I wrote a logging function:
function log(obj) {
console.log(obj);
}
and tried:
for (var key in keys) {
recursiveGetProperty(foo, keys, log(obj));
}
but i get ReferenceError: obj is not defined.
I was told it's not a great idea to execute a callback inside a for loop, I'm not exactly sure why so I looked into async.js. It seems like the right solution for what I want but I have no idea how to go about it.
Using async.js, I would like to build a series of recursiveGetProperty functions, store them in an array, and then execute those calls asynchronously but I'm stumped on how to approach the problem.
What I would ultimately want is something like this:
async.each(['key_1', 'key_2', 'key_3'], recursiveGet(key) {
doSomethingWithData();
}, function(err) {
doSomethingWhenDone();
});
This would be used on an ExpressJS server to parse JSON and do something with it afterwards.
Any help or suggestion would be greatly appreciated.
There are bug in this code
var keys = ['val1', 'val3'];
for (var keys in keys) {
recursiveGetProperty(foo, keys, function(obj) {
console.log(obj);
});
}
The array of keys (line 1) is overwritten by the keys index (line 2) of the for loop. So let's rename that key.
For an iterated array a key would be a number (0, 1, ..., n), not a string. You need to use those numbers as indices to keys
Also your recursiveGetProperty finds by keys not values.
So the code should be
var keys = ['key_1', 'key_2'];
for (var key in keys) {
recursiveGetProperty(foo, keys[key], function(obj) {
console.log(obj);
});
}
var keys = ['key_1', 'key_2'];
keys.forEach(function (key) {
recursiveGetProperty(foo, key, function(obj) {
console.log(obj);
});
});
http://jsfiddle.net/fPRQK/
Issues in your code:
for(var key in obj) works with object properties only, not with array values. Use Array.forEach or a regular for loop instead.
recursiveGetProperty(foo, keys, log(obj)): This would call log instantly and pass it's return value to recursiveGetProperty. You could either pass just log or function (obj) { log(obj); }, which in this case mean the same. Also in this case what you want to pass is key.
Related
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性
console.log(key,val);
}
function observe(data) {
Object.keys(data).forEach(function(key) {
console.log(data,key,data[key])
defineReactive(data, key, data[key]);
});
};
var library = {
book1: {
name: ''
}
};
observe(library);
The above is the code part, and the screenshot below is running. Why does 'book1' print?
You are getting keys in library object in this line Object.keys(data).
the keys in library? it is book1. Unless you want to find keys inside book1 you need to do Object.keys for nested object.
TL.DR: Object.keys(obj) only find keys in the first level.
Thanks for your time.
I have the following object in JavaScript:
{
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
Now I want to parse for a specific value and if the value exists, I want to return the key associated with it. Suppose that I am passing 'value3', it should return key1; if passing value5, it should return me key2 and so on.
What is the best way to implement it using Javascript, keeping in mind the execution time. I have tried using sting manipulation functions like indexOf, substr; but not most effective I believe.
TIA.
Here is a slightly different approach that will generate a map where the key is actually the value of your original values object.
The generated map will be a sort of fast lookup. Just to make things clear this solution is efficient as long as you need to do a lot of lookups. For a single, unique lookup this solution is the less efficient, since building the hashmap requires much more time than just looking up for a single value.
However, once the map is ready, acquiring values through keys will be incredibly fast so, if you need to later acquire multiple values, this solution will be more suitable for the use case.
This can be accomplished using Object.entries and Object.values. Further explanations are available in the code below.
The code below (despite not required) will also take care of avoiding indexOf with limit cases like searching 'value9' over 'value9999' which, on a regular string, would actually work with indexOf.
const values = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6",
"key3":"value444,value129839,value125390", // <-- additional test case.
"key4": "value9" // <-- additional test case.
};
const map = Object.entries(values).reduce((acc, [key, value]) => {
// If the value is invalid, return the accumulator.
if (!value) return acc;
// Otherwise, split by comma and update the accumulator, then return it.
return value.split(',').forEach(value => acc[value] = key), acc;
}, {});
// Once the map is ready, you can easily check if a value is somehow linked to a key:
console.log(map["value444"]); // <-- key 3
console.log(map["value9"]); // <-- key 4
console.log(map["Hello!"]); // undefined
To me, the fastest and most concise way of doing that would be the combination of Array.prototype.find() and String.prototype.includes() thrown against source object entries:
const src={"key1":"value1,value2,value3","key2":"value4,value5,value6"};
const getAkey = (obj, val) => (Object.entries(obj).find(entry => entry[1].split(',').includes(val)) || ['no match'])[0];
console.log(getAkey(src, 'value1'));
console.log(getAkey(src, 'value19'));
p.s. while filter(), or reduce(), or forEach() will run through the entire array, find() stops right at the moment it finds the match, so, if performance matters, I'd stick to the latter
Lodash has a function for this called findKey which takes the object and a function to determine truthiness:
obj = { 'key1': 'value1, value2, value3', 'key2': 'value4,value5,value6' }
_.findKey(obj, val => val.includes('value3'))
# 'key1'
_.findKey(obj, val => val.includes('value5'))
# 'key2'
Based on your search, you can use indexOf after looping through your object.
Here's an old school method:
var obj = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
function search (str) {
for (var key in obj) {
var values = obj[key].split(',');
if (values.indexOf(str) !== -1) return key
}
return null;
}
console.log(search('value1'))
console.log(search('value6'))
Or you can use Object.keys() with filter() method and get the index 0 of the returned array.
var obj = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
function search (str) {
return Object.keys(obj).filter((key) => {
const values = obj[key].split(',');
if (values.indexOf(str) !== -1) {
return key
}
})[0]
}
console.log(search('value1'))
console.log(search('value6'))
You can try iterating over each value in your object and then splitting the value on each comma, then checking if the value is in the returned array like so:
const myObj = {"key1":"value1,value2,value3","key2":"value4,value5,value6"}
function findKeyByValue(obj, value) {
for (var key in myObj) {
const valuesArray = myObj[key].split(',')
if (valuesArray.includes(value)) {
return key
}
}
}
const key = findKeyByValue(myObj, 'value5') // returns 'key2'
console.log(key)
EDIT: Changed loop for efficiency, and extracted code to function
This should do it. Just uses Object.entries and filters to find the entries that contain the value you're looking for. (Can find more than one object that has the desired value too)
var obj = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
var find = 'value2';
var key = Object.entries(obj).filter(([k, v]) => v.split(',').includes(find))[0][0];
console.log(key);
Might want to check the return value of Object.entries(obj).filter((o) => o[1].split(',').includes(find)) before trying to access it, in case it doesn't return anything. Like so:
var obj = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
function findKey(objectToSearch, valueToFind) {
var res = Object.entries(objectToSearch).filter(([key, value]) => value.split(',').includes(valueToFind));
if(res.length > 0 && res[0].length > 0) {
return res[0][0];
}
return false;
}
console.log(findKey(obj, 'value5'));
includes can be used to check whether a value is present in an array. Object.keys can be used for iteration and checking for the match.
function findKey(json, searchQuery) {
for (var key of Object.keys(json)) if (json[key].split(',').includes(searchQuery)) return key;
}
const json = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
}
console.log(findKey(json, 'value5'))
Use Object.entries with Array.prototype.filter to get what the desired key.
const data = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
const searchStr = 'value3';
const foundProp = Object.entries(data).filter(x => x[1].indexOf(searchStr) !== -1);
let foundKey = '';
if (foundProp && foundProp.length) {
foundKey = foundProp[0][0];
}
console.log(foundKey);
im currently working on a project that uses javascript as it's front end and im having a bit trouble on adding a key on my existing array.
i have an object that i wanted to be converted on array javascript.
here is my code on how to convert my object to array.
var obj = data[0];
var site_value = Object.keys(obj).map(function (key) { return obj[key]; });
var site_key = $.map( obj, function( value, key ) {
return key;
});
the site_value has the value of my objects.
the site_key has the key.
i want to add my site_key to the site_value array as a Key.
example data:
site_value:
0:Array[4]
0:Array[4]
1:Array[1]
2:Array[1]
3:Array[0]
site_key:
Array[49]
0:"AGB"
1:"BAK"
2:"BAN"
3:"BAR"
i want my array to be
AGB:Array[4]
0:Array[4]
1:Array[1]
2:Array[1]
3:Array[0]
Update:
Here is my object.
Array[1]0:
Object
AGB: Array[4]
BAK: Array[4]
BAN: Array[4]
etc.
You have almost done it and I have modified it a bit below to return it as array object,
var obj = data[0];
var site_value = Object.keys(obj).map(function (key) {
var output = {};
output[key] = obj[key];
return output;
});
I might be misunderstanding the question, sorry if I am. I think you would like to use a key "AGB" instead of an integer for an array index. In this case, you would probably be better served to use an object instead of an array. Maybe something like this
var myObject = {
AGB: Array[4],
AGBarrays: [Array[4],Array[1],Array[1],Array[0]]
};
Then you could access AGB by key and your additional arrays by index
What is the best/most efficient way to find the common/different properties of an array of objects.
I need to identify the set of properties that exists in all objects and all have the same value(the common).
Preferably I would also like to get an array with all other properties (the diff).
I have searched for an efficient library/function that can do it. But didn't find anything. So I tried on my own.
Consider this array of JS objects:
var objects = [{
id: '2j2w4f',
color: 'red',
height: 20,
width: 40,
owner: 'bob'
}, {
id: '2j2w3f',
color: 'red',
height: 20,
width: 41,
owner: 'bob'
}, {
id: '2j2w2f',
color: 'red',
height: 21,
}, {
id: '2j2w1f',
color: 'red',
height: 21,
width: 44
}];
I would like to identify color (with value red) as the only common property.
Note that they do not have the same set of properties. E.g. owner is not a common property.
This is my own attempt to solve it (using lodash):
function commonDifferentProperties(objects) {
// initialize common as first object, and then remove non-common properties.
var common = objects[0];
var different = [];
var i, obj;
// iterate through the rest (note: i === 0 is not supposed to be covered by this)
for (i = objects.length - 1; i > 0; i--) {
obj = objects[i];
// compare each property of obj with current common
_.forOwn(obj, function (value, key) {
// if property not in current common it must be different
if (_.isUndefined(common[key])) {
if (!_.contains(different, key)) {
different.push(key);
}
} else if (common[key] !== value) { // remove property from common if value is not the same
delete common[key];
different.push(key);
}
});
// check that all properties of common is present in obj, if not, remove from common.
_.forOwn(common, function (value, key) {
if (_.isUndefined(obj[key])) {
delete common[key];
different.push(key);
}
});
}
return {
common: common,
different: different
};
}
jsFiddle with the example
I have also tried a mapReduce approach, but that seemed even worse.
I still think this seems a bit complex/time consuming, and I will do this on 1000-10000 objects or more with 20-50 properties each.
Any suggestions?
There are two things that are look wrong in your solution:
By var common = objects[0]; you don't copy the object, so you're going to corrupt the objects
You both check that all properties of common is present in obj, but also compare each property of obj with current common. That seems to be once too much. Didn't realize at first that you needed the different properties as well.
I'd loop over the data in two passes. In the first, you collect all apparent properties in one object, in the second you test whether they're common or not:
function commonDifferentProperties(objects) {
var common = _.reduce(objects, function(acc, obj) {
for (var p in obj)
acc[p] = obj[p];
return acc;
}, {});
var different = _.reduce(objects, function(acc, obj) {
for (var p in common)
if (common[p] !== obj[p]) {
delete common[p];
acc.push(p);
}
return acc;
}, []);
return {
common: common,
different: different
};
}
Here's what I did using just vanilla JS:
function commonDifferentProperties(objects) {
var common = JSON.parse(JSON.stringify(objects[0]));
var unmatchedProps = {};
for (var i = 1; i < objects.length; i++) {
for (var prop in objects[i]) {
checkProps(objects[i],common,prop);
}
for (var commProp in common) {
checkProps(common,objects[i],commProp);
}
}
console.log(common); // this is all the matched key/value pairs
console.log(unmatchedProps); // this is all the unmatched keys
return { common: common, different: unmatchedProps };
function checkProps(source, target, prop) {
if (source.hasOwnProperty(prop)) {
var val = source[prop];
if (!target.hasOwnProperty(prop) || target[prop] !== val) {
unmatchedProps[prop] = true; // note: you could extend this to store values, or number of times you found this key, or whatever
delete common[prop];
}
}
}
}
http://jsfiddle.net/TwbPA/
So I copy the first object and use that to keep track of keys and values that are common. Then I iterate through all the other objects in your array and first look through all the key/values in the common object and compare to the current, deleting any missing properties from the common object if they are not in the current one, then I do the reverse to catch any properties in the current object that aren't in the common (or are in the current, but have the wrong value).
Edit
Sorry, i was in a hurry and didn't had enough time to think about it.
Indeed, there's no need for a sort. I was thinking using a binary algorithm or something..
Here, the updated code without the sort. Console.time() gave me '3ms'.
I'm doing similar to Bergi's solution, but instead of collecting all apparant properties i search for the element that has the fewest amount of properties. This reduces the amount of iterations on the second loop.
I've based the code on the following:
if object X has a property the selected object doesn't have, then it isn't a common property!
Thus the selected object has all common properties + extra's.
The selected object has the fewest properties, thus the iterations of validating is less.
http://jsfiddle.net/kychan/cF3ne/1/
// returns the common properties of given array.
function getCommonProps(objects)
{
// storage var for object with lowest properties.
var lowest = {obj:null, nProperties:1000};
// search for the object with lowest properties. O(n).
for (var j in objects)
{
var _nProp = Object.keys(objects[j]).length;
if (_nProp < lowest.nProperties)
lowest = {obj:objects[j], nProperties:_nProp};
}
// var that holds the common properties.
var retArr = [];
// The object with the fewest properties should contain common properties.
for (var i in lowest.obj)
if (isCommonProp(objects, i)) retArr.push(i);
return retArr;
}
// Checks if the prop exists in all objects of given array.
function isCommonProp(arr, prop)
{
for (var i in arr)
{
if (arr[i][prop]===undefined)
return false;
}
return true;
}
console.time('getCommonProps()_perf');
console.log(getCommonProps(objects));
console.timeEnd('getCommonProps()_perf');
Here's another approach that uses reduce() and transform():
_.reduce(objects, function(result, item) {
if (_.isEmpty(result)) {
return _.assign({}, item);
}
return _.transform(item, function(common, value, key) {
if (result[key] === value) {
common[key] = value;
}
}, {});
}, {});
I have something like this:
var input = [];
var result = {};
input.push({
key: 1,
value: Value
});
result[input[0].key] = input[0].value;
If I want to get the value from key i call result[key], but what if I want to get the Key from a certain value? Any ideas?
You could create an inverse mapping of values → keys; however, there is no guarantee that the values will be unique (whereas the keys must be), so you could map values → array of keys.
function inverseMap(obj) {
return Object.keys(obj).reduce(function(memo, key) {
var val = obj[key];
if (!memo[val]) { memo[val] = []; }
memo[val].push(key);
return memo;
}, {});
}
var a = {foo:'x', bar:'x', zip:'y'};
var b = inverseMap(a); // => {"x":["foo","bar"],"y":["zip"]}
[Update] If your values are definitely unique then you can simply do this:
function inverseMap(obj) {
return Object.keys(obj).reduce(function(memo, key) {
memo[obj[key]] = key;
return memo;
}, {});
}
inverseMap({a:1, b:2})); // => {"1":"a","2":"b"}
Of course, these solutions assume that the values are objects whose string representation makes sense (non-complex objects), since JavaScript objects can only use strings as keys.
A value might have several keys, so I'd recommend iterating over all the keys and adding those that have value into a list.
Do you plan to recurse for complex values?
You could use underscore.js's find method...
var key = _.find(result, function(val) {
return val === 'value';
}).key;
This will search your result for the value and return you the object, then you can grab the key off of it.
Underscore is one of my favorite tools for handling these kinds of things. There's lots of powerful methods in it.