Let's assume I have an array as following:
[
{
"name": "list",
"text": "SomeText1"
},
{
"name": "complex",
"text": "SomeText2",
"config": {
"name": "configItem",
"text": "SomeText3",
"anotherObject": {
"name": "anotherObject1",
"text": "SomeText4"
}
}
}
]
I am using this awesome code to get all Objects with a certain key (http://techslides.com/how-to-parse-and-search-json-in-javascript). In my example it is getObjects(data,'text','') which will return all nodes as Object due to the appearance of text as key.
My only problem is, that I need to know the location of the returned Object in the whole array.
Is there any way to get it? Or at least the depth of the object in conjunction to the array?
getObjects(r,'text','')[0] (name = list) -> depth 1
getObjects(r,'text','')[1] (name = complex) -> depth 1
getObjects(r,'text','')[2] (name = configItem) -> depth 2
You will need something along these lines:
function getObjectsDepth(obj, key, val, depth) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjectsDepth(obj[i], key, val,++depth));
} else
//if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not)
if (i == key && obj[i] == val || i == key && val == '') { //
objects.push(depth);
} else if (obj[i] == val && key == ''){
//only add if the object is not already in the array
if (objects.lastIndexOf(obj) == -1){
objects.push(depth);
}
}
}
return objects;
}
This will return the depth of the object, but not the object itself, just pass a 0 or 1 as last param, based on how you want to count.
If you want both the object and the depth at the same time you need to obj.push({'obj':obj,'depth':depth}).
Change the getObjects function by this one:
function getObjects(obj, key, val, depth) {
var objects = [];
depth = typeof depth !== 'undefined' ? depth : 0;
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
depth ++;
objects = objects.concat(getObjects(obj[i], key, val, depth));
} else
//if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not)
if (i == key && obj[i] == val || i == key && val == '') { //
objects.push({"obj":obj,"depth": depth});
} else if (obj[i] == val && key == ''){
//only add if the object is not already in the array
if (objects.lastIndexOf(obj) == -1){
objects.push({"obj":obj,"depth": depth});
}
}
}
return objects;
}
Now you get depth and the object.
Related
I'm trying to ensure that any existing, non-null or empty values are not overwritten with empty or null values from an API call.
For example, assume that,
originalReference['data']['articleTitle'] = 'Something or other';
and
reference['data']['articleTitle'] = '';,
where reference is the object that came back from the API and originalReference is the object that existed before the API call (was loaded from MySQL database).
I want to ensure that this function cycles through these complex objects (both should always be of the same length and have the same property names), and reassigns the old value to the new object.
So, in the use case above, after the function is done, the articleTitle in the object reference will be:
reference['data']['articleTitle'] = 'Something or other';
Here is what I have so far:
if (referenceLength == originalReferenceLength) {
try {
for (var prop in reference) {
// First check for undefined or null
if (reference[prop] != undefined) {
if (reference[prop] != null) {
if (typeof reference[prop] == 'string' && reference[prop].trim() == '') {
// Assign original value to new object if new value is empty string
reference[prop] = originalReference[prop];
}
// Check if current prop in both objects is an object
if (typeof reference[prop] == 'object' && typeof originalReference[prop] == 'object') {
for (var property in reference[prop]) {
// Check for undefined or null value in original
if (originalReference[prop][property] != undefined) {
if (originalReference[prop][property] != null) {
if (reference[prop][property] == null || reference[prop][property] == '') {
// Assign old non-null value to new object if new value is empty or null
reference[prop][property] = originalReference[prop][property];
}
}
}
}
}
if (Array.isArray(reference[prop]) && typeof Array.isArray(originalReference[prop])) {
// Recurse if both are arrays
reference[prop].forEach((item, index) => vm.evaluateEmptyValues(item, originalReference[prop][index]));
}
} else {
if (originalReference[prop] != undefined) {
if (originalReference[prop] != null) {
// Assign original value to new object
reference[prop] = originalReference[prop];
}
}
}
} else {
if (originalReference[prop] != undefined) {
if (originalReference[prop] != null) {
// Assign original value to new object
reference[prop] = originalReference[prop];
}
}
}
}
} catch(err) {
console.log(err);
}
}
Don't think there's anything wrong with it but it could be a lot [DRYer][1] and more reusable.
Specially the iteration of a prop that is an object itself just screams recursion, so here's possible refactoring:
function mergeNewReference(origRef, newRef){
if(!newRef){
console.log('newRef is empty, not sure what should happen')
}else{
for(prop in newRef){
var newVal = newRef[prop];
if(!isNullOrUndefined(newVal)){
var origVal = origRef[prop];
if(typeof newVal == typeof origVal && typeof newVal === 'object'){
mergeNewReference(origVal, newVal)
}else if(isNullOrUndefined(origVal) || isEmptyString(origVal)){
origRef[prop] = newVal;
}
}
}
}
return origRef;
}
//These helper methods could be better encapsulated inside the merge method.
//I'll leave them here for better readability
function isNullOrUndefined(val){
return val === null || val === undefined;
}
function isEmptyString(str){
return typeof str == 'string' && str.trim() == ''
}
//----------- TEST ----------
var a = {a: null, b: {ba: 1, bb: {bba: '', bbb: null, bbx: 1, bbd: [9,null,9,9]}}};
var b = {a: 2, b: {ba: 2, bb: {bba: 'Not null', bbb: 2, bbc: 2, bbd: [1,2,3,4,5]}}};
var c = mergeNewReference(a,b);
console.log(c);
NOTICE: this will return a different result from you algorithm if you have nested objects with more than one level as it does a deep merge.
[1]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
I have an object whose keys I don't know but structure is basically the same. Value can be a string or another object of strings/objects. Here is an example:
d = {
"name": "Sam",
"grade": 9,
"classes": {
"a": 1,
"b": 2
},
"age": null
}
What I want now is if value is not another object, get the key name and its value. If value is null, return empty string. From the above the expected output is:
name=Sam, grade=9, a=1, b=2, age=''
Here since classes is object, it has to be looped again to get keys (a,b) and values (1,2).
I tried the following but it gives if any of the values is null, it returns an error:
Cannot convert undefined or null to object
It works well if there is no null value:
function getKeyValues(data) {
var q = '';
f(data);
function f(s) {
Object.keys(s).forEach(function(key) {
if (typeof s[key] === 'object') {
f(s[key]);
} else {
q = q + key + '=' + (s[key] == null) ? "" : s[key] + '&';
}
});
}
return q;
}
d = {
"name": "Sam",
"grade": 9,
"classes": {
"a": 1,
"b": 2
},
"age": null
}
console.log(getKeyValues(d));
Try this one:
function getKeyValues(data) {
var q = [];
var keys = Object.keys(data);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data[key];
if (value == null) {
q.push(key + "=''");
} else if (typeof value == "object") {
q.push(getKeyValues(value));
} else {
q.push(key + "=" + value);
}
}
return q.join(",");
}
Another approach is to use the reduce method. Personally I find it a little bit cleaner.
function getKeyValues(d) {
return Object.keys(d).reduce((memo, key) => {
if (!d[key]) {
memo[key] = '';
} else if (typeof d[key] === 'object') {
Object.keys(d[key]).forEach((subKey) => {
memo[subKey] = d[key][subKey];
})
} else {
memo[key] = d[key];
}
return memo;
}, {})
}
Also, while your question is very clear, I must say that it also makes me a little bit wary. You could find yourself in some difficult debugging situations if property names are ever repeated in nested objects. For example, if
d={"name":"Sam","grade":9,"buddy":{"name":"Jeff","age":12}}
would you expect name be "Sam" or "Jeff"? A function that answers your question could return either, so that is something to be aware of going forward.
Here is my fiddle : DEMO
By recursive iterations, I am able to find the key in object2 object3 and replace its value by the values from data2 data3 objects.
However, I am unable to replace the value if it is an array. (key called 'coordinates' in this case)
How could this be fixed?
function update(object, data) {
function getAllKeys(o) {
Object.keys(o).forEach(function(k) {
if (typeof o[k] === 'object') {
return getAllKeys(o[k]);
}
keys[k] = o;
});
}
var keys = Object.create(null);
getAllKeys(object);
Object.keys(data).forEach(function(k) {
if (keys[k] && k in keys[k]) { // check if key for update exist
keys[k][k] = data[k];
}
});
}
Update the getAllKeys method with:
function getAllKeys(o) {
Object.keys(o).forEach(function(k) {
contains_object = Array.isArray(o[k]) && o[k].some(val=> { return typeof val == "object" && !Array.isArray(val); });
if ((Array.isArray(o[k]) && !contains_object) || typeof o[k] !== 'object') {
keys[k] = o;
} else {
return getAllKeys(o[k]);
}
keys[k] = o;
});
}
Note: !(o[k] instanceof Array) - http://jsfiddle.net/08pnu7rx/1/
The problem is that typeof also returns object for arrays.
You want to change your function to still assign the key when the object is an array.
function getAllKeys(o) {
Object.keys(o).forEach(function(k) {
if (Array.isArray(o[k]) || typeof o[k] !== 'object') {
keys[k] = o;
} else {
return getAllKeys(o[k]);
}
});
}
Notice I swapped around the logic, so you first check for either an array or another non-object type. If that check passes, you assign the value. If not, you recurse.
You should note that this is not at all specific to arrays. You will have similar problems if you have a nested property that is a Date, for example.
The problem in your code is that typeof o[k] === 'object' returns true even it o[k] is an array which is the case for coordinated, You need a negative check for array too like
Object.keys(o).forEach(function(k) {
if (typeof o[k] === 'object'&& !Array.isArray(o[k])) {
return getAllKeys(o[k]);
}
keys[k] = o;
});
Working fiddle
According to the docs or typeof:
// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays
typeof [1, 2, 4] === 'object';
I have an object. I want to check if a specific property exists in it or not.
The issue is: the property that I am looking for, could be anywhere, i.e: the structure of the object is undefiend.
ex:
obj1 = { "propIWant": "xyz" }
obj2 = { "prop1": [ {"key": "value"}, {"key":"value"}, 1, {"key": { "propIWant": "xyz"}}]
I've tried the following, but it seems to fail:
var lastTry = function(entry){
// if entry is an array
if(typeof entry === 'object' && entry instanceof Array){
for(var i in entry)
entry[i] = this.lastTry(entry[i]);
}
// if entry is a normal object
else if(typeof entry === 'object'){
// iterate through the properties of the entry
for(var key in entry){
console.log('key is: ', entry[key])
// in case the entry itself is an array
if(typeof entry[key] === 'object' && entry[key] instanceof Array){
for(var i in entry[key]){
entry[key][i] = this.lastTry(entry[key][i]);
}
}
// in case the entry is a simple object
else if(typeof entry[key] === 'object') {
console.log('entry[key] is an object', entry[key], key)
// if we directely find the property.. modify it
if(entry[key].hasOwnProperty('_internal_url')){
**entry[key]['_internal_url'] = "http://localhost:4000"+entry[key]['_internal_url'];** <-- My objective
}
else{
// call this method again on that part
// for(var i in entry[key]){
// if(typeof entry[key][i] === 'object')
// entry[key][i] = this.lastTry(entry[key][i]);
// }
}
}else{
console.log('not found')
}
}
}
}
Can someone please help me out with it?I found the following: Find by key deep in a nested object but, instead of returning the found part, I want to edit the property and return the entire object with the modified property, not just the subset of the object that has that property.
Have you tried :
obj1.hasOwnProperty('propIWant')
If you wish to just check if property exists or not, you can stringify the object and then check if value exists in string or not.
var obj2 = {
"prop1": [{
"key": "value"
}, {
"key": "value"
},
1, {
"key": {
"propIWant": "xyz"
}
}
]
}
var key = 'propIWant';
var key2 = 'propIWant1';
function isPropInObject(obj, prop) {
var r = new RegExp(prop + "\":")
var match = JSON.stringify(obj).match(r);
return match && match.length > 0 ? true : false
}
console.log(isPropInObject(obj2, key))
console.log(isPropInObject(obj2, key2))
Arrays and objects data can be access in a common way:
obj[key] and arr[pos];
This way you can simplify your code. Please note that key can be a string in case of a object or a number in case of an array.
The version below only searches in the element and eventual children elements(both arrays and objects) in a depth-first logic.
var found = 0;
var findProp = function(entry) {
if(typeof entry === 'object')
for(var key in entry) {
findProp(entry[key]);
if( entry[key].hasOwnProperty('_internal_url')) {
found++;
entry[key]['_internal_url'] = "http://localhost:4000" + entry[key]['_internal_url'];
}
}
}
console.log('found ' + found + 'occurrences');
Well, check if the props is objects themselves and use a recursive function to find the "deep" property you are looking for?
function findProp(obj, lookFor) {
for (var prop in obj) {
if (prop == lookFor) return obj[prop]
if (typeof obj[prop] == 'object') {
var checkNested = findProp(obj[prop], lookFor)
if (checkNested) return checkNested
}
}
return false
}
It works with console.log(findProp(obj2, 'propIWant'))
demo -> http://jsfiddle.net/zqcurg70/
This question already has answers here:
Test for existence of nested JavaScript object key
(64 answers)
Closed 5 years ago.
I'm trying to find an elegant way to check the if certain deep properties exist in an object. So practically trying to avoid monstrous protective checks for undefined eg.
if ((typeof error !== 'undefined') &&
(typeof error.responseJSON !== 'undefined') &&
(typeof error.responseJSON.error) &&
(typeof error.responseJSON.error.message)) {
errorMessage = error.responseJSON.error.message;
}
What I'm thinking about is a convenience-function like
if (exists(error.responseJSON.error.message)) { ... }
Any ideas? For convenience, the use of underscore-library is ok for the solution.
There are several possiblities:
Try-catch
try {
errorMessage = error.responseJSON.error.message;
} catch(e) { /* ignore the error */}
Fails for:
Object.defineProperty(error, 'responseJSON', {
get: function() { throw new Error('This will not be shown')
});
&&
errorMessage = error && error.responseJSON && error.responseJSON.error && error.responseJSON.error.message;
Fails for:
error.responseJSON = 0;
// errorMessage === 0 instead of undefined
function
function getDeepProperty(obj,propstr) {
var prop = propstr.split('.');
for (var i=0; i<prop.length; i++) {
if (typeof obj === 'object')
obj = obj[prop[i]];
}
return obj;
}
errorMessage = getDeepProperty(error, 'responseJSON.error.message');
// you could put it all in a string, if the object is defined in the window scope
Fails for:
// It's hard(er) to use
function alternative - see comment by #Olical
function getDeepProperty(obj) {
for (var i=1; i<arguments.length; i++) {
if (typeof obj === 'object')
obj = obj[arguments[i]];
}
return obj;
}
errorMessage = getDeepProperty(error, 'responseJSON', 'error', 'message');
Try this underscore mixin to look up a variable with a path. It takes an object and string and t
_.mixin({
lookup: function (obj, key) {
var type = typeof key;
if (type == 'string' || type == "number")
key = ("" + key).replace(/\[(.*?)\]/, function (m, key) { //handle case where [1] may occur
return '.' + key.replace(/["']/g, ""); //strip quotes
}).split('.');
for (var i = 0, l = key.length; i < l; i++) {
if (_.has(obj, key[i]))
obj = obj[key[i]];
else
return undefined;
}
return obj;
}
});
Now call in your example:
_.lookup(error, 'responseJSON.error.message') // returns responseJSON.error.message if it exists otherwise `undefined`