I need a true iterator that will work like this:
var haystackObj = {
'needle': 'abc',
'prop2': {
'prop1': 'def',
'prop2': {
'needle': 'ghi',
},
'needle': 'jkl',
},
};
var needleKey = 'needle';
var iterator = {
next: function () {
/*
* WHAT CODE GOES HERE?
*
* Should return the next property, recursively, with the name
* equal to needleKey, of haystackObj.
*
*/
}
};
var value = iterator.next();
console.log(value); // -> 'abc'
value = iterator.next();
console.log(value); // -> 'ghi'
value = iterator.next();
console.log(value); // -> 'jkl'
I think this would be trivial with a for(k in o) and first-class continuations, but JS doesn't have those.
EDIT: I can only scan haystackObj once.
EDIT2: I am not looking for "a way to iterate through object properties." I am looking for an iterator of object properties. That is a huge difference. The problem is not as trivial as it may look at first glance.
Properties order is not guaranteed in JS. Different engines behave differently. (Some engines based on alphabetical order, other based on last added order.)
Your requirements are thus impossible to fulfill.
If you just wanted an iterator without minding the order, you could take a look at this question/answers: How to simulate JavaScript yield?
This is what the spec says about the properties order:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration. A property name must not be visited more than once in any enumeration.
In reality however, you can expect a certain order from most browsers: Elements order in a "for (… in …)" loop
The only way I see to implement a fake generator (according to the fact that the order in reality suits you) would be to copy your object, and delete the scanned properties of the copy when needed. This'd mean you wouldn't rescan twice the same properties. Some code example:
var Iterator = function() {
var copy = $.extend(haystackObj, true);
// ^ using jQuery's extend for a quick function, but use w/e you want.
// Anyway keep it in a closure. This copy will have its properties deleted
// after each iteration.
return {
next: function next() {
var found = false,
needle;
for (var prop in copy) {
if (typeof copy[prop] === 'object') {
// Since next() doesn't take any argument...
// That's a bad solution. You should use an inner function
// to recurse. But I'm going to bed right now!
var copyCopy = $.extend(copy, true);
copy = copy[prop];
found = next();
copy = copyCopy;
}
else {
if (prop === needleKey) {
found = true;
}
}
if (found) {
needle = copy[prop];
}
// Delete the current property to simulate a real generator.
delete copy[prop];
if (found) {
return needle;
}
}
}
};
};
// Usage:
var iterator = Iterator();
iterator.next(); // "abc"
This code doesn't work (see jsfiddle), and I'm going to sleep. But you can see where it's going and how you could make something.
Assuming I understand you correctly, and bearing in mind that this is not a 'true yield', and putting all the code where you seem to want it,
var iterator = {
next: function () {
/*
* WHAT CODE GOES HERE?
*
* Should return the next property, recursively, with the name
* equal to needleKey, of haystackObj.
*
*/
var values=[], findneedles;
findneedles = function(o){
var k;
for(k in o){
if(k === needleKey){
values.push(o[k]);
}else if(typeof o[k] === 'object'){
findneedles(o[k]);
}
}
};
findneedles(haystackObj);
this.next = function(){
return values.shift();
};
return values.shift();
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With