Skip to content

src: add iterator for Object #930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions doc/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,5 +288,47 @@ Napi::Value Napi::Object::operator[] (uint32_t index) const;

Returns an indexed property or array element as a [`Napi::Value`](value.md).

## Iterator

Iterators expose an `std::pair<...>`, where the `first` property is a
[`Napi::Value`](value.md) that holds the currently iterated key and the
`second` property is a [`Napi::Object::PropertyLValue`](propertylvalue.md) that
holds the currently iterated value. Iterators are only available if C++
exceptions are enabled (by defining `NAPI_CPP_EXCEPTIONS` during the build).

### Constant Iterator

In constant iterators, the iterated values are immutable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you should document the interface exposed by Napi::Object::const_iterator.

```cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you should add title Example

Value Sum(const CallbackInfo& info) {
Object object = info[0].As<Object>();
int64_t sum = 0;

for (const auto& e : object) {
sum += static_cast<Value>(e.second).As<Number>().Int64Value();
}

return Number::New(info.Env(), sum);
}
```

### Non Constant Iterator

In non constant iterators, the iterated values are mutable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you should document the interface exposed by Napi::Object::terator

```cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you should add title Example.

void Increment(const CallbackInfo& info) {
Env env = info.Env();
Object object = info[0].As<Object>();

for (auto e : object) {
int64_t value = static_cast<Value>(e.second).As<Number>().Int64Value();
++value;
e.second = Napi::Number::New(env, value);
}
}
```

[`Napi::Value`]: ./value.md
[`Napi::Value::From`]: ./value.md#from
85 changes: 85 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,14 @@ inline Object::PropertyLValue<uint32_t> Object::operator [](uint32_t index) {
return PropertyLValue<uint32_t>(*this, index);
}

inline Object::PropertyLValue<Value> Object::operator[](Value index) {
return PropertyLValue<Value>(*this, index);
}

inline Object::PropertyLValue<Value> Object::operator[](Value index) const {
return PropertyLValue<Value>(*this, index);
}

inline MaybeOrValue<Value> Object::operator[](const char* utf8name) const {
return Get(utf8name);
}
Expand Down Expand Up @@ -1529,6 +1537,83 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback,
}
}

#ifdef NAPI_CPP_EXCEPTIONS
inline Object::const_iterator::const_iterator(const Object* object,
const Type type) {
_object = object;
_keys = object->GetPropertyNames();
_index = type == Type::BEGIN ? 0 : _keys.Length();
}

inline Object::const_iterator Napi::Object::begin() const {
const_iterator it(this, Object::const_iterator::Type::BEGIN);
return it;
}

inline Object::const_iterator Napi::Object::end() const {
const_iterator it(this, Object::const_iterator::Type::END);
return it;
}

inline Object::const_iterator& Object::const_iterator::operator++() {
++_index;
return *this;
}

inline bool Object::const_iterator::operator==(
const const_iterator& other) const {
return _index == other._index;
}

inline bool Object::const_iterator::operator!=(
const const_iterator& other) const {
return _index != other._index;
}

inline const std::pair<Value, Object::PropertyLValue<Value>>
Object::const_iterator::operator*() const {
const Value key = _keys[_index];
const PropertyLValue<Value> value = (*_object)[key];
return {key, value};
}

inline Object::iterator::iterator(Object* object, const Type type) {
_object = object;
_keys = object->GetPropertyNames();
_index = type == Type::BEGIN ? 0 : _keys.Length();
}

inline Object::iterator Napi::Object::begin() {
iterator it(this, Object::iterator::Type::BEGIN);
return it;
}

inline Object::iterator Napi::Object::end() {
iterator it(this, Object::iterator::Type::END);
return it;
}

inline Object::iterator& Object::iterator::operator++() {
++_index;
return *this;
}

inline bool Object::iterator::operator==(const iterator& other) const {
return _index == other._index;
}

inline bool Object::iterator::operator!=(const iterator& other) const {
return _index != other._index;
}

inline std::pair<Value, Object::PropertyLValue<Value>>
Object::iterator::operator*() {
Value key = _keys[_index];
PropertyLValue<Value> value = (*_object)[key];
return {key, value};
}
#endif // NAPI_CPP_EXCEPTIONS

#if NAPI_VERSION >= 8
inline MaybeOrValue<bool> Object::Freeze() {
napi_status status = napi_object_freeze(_env, _value);
Expand Down
72 changes: 72 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,14 @@ namespace Napi {
uint32_t index /// Property / element index
);

/// Gets or sets an indexed property or array element.
PropertyLValue<Value> operator[](Value index /// Property / element index
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whitespacing is strange. Please close the bracket immediately after index! Please use // for comments!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabrielschulhof I agree, it does look strange. It was done by the linter though. I used /// for the comments to make it consistent with the rest of the comments nearby. Perhaps we can change all comments to use // in a separate PR?

);

/// Gets or sets an indexed property or array element.
PropertyLValue<Value> operator[](Value index /// Property / element index
) const;

/// Gets a named property.
MaybeOrValue<Value> operator[](
const char* utf8name ///< UTF-8 encoded null-terminated property name
Expand Down Expand Up @@ -928,6 +936,21 @@ namespace Napi {
inline void AddFinalizer(Finalizer finalizeCallback,
T* data,
Hint* finalizeHint);

#ifdef NAPI_CPP_EXCEPTIONS
class const_iterator;

inline const_iterator begin() const;

inline const_iterator end() const;

class iterator;

inline iterator begin();

inline iterator end();
#endif // NAPI_CPP_EXCEPTIONS

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need these forward declarations? Why not place the const_iterator and iterator class definitions in directly here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabrielschulhof The iterator classes require the definition of the Napi::Array class to be completed but it is not completed here yet. That's why I decided to move the actual class definitions after the definition of Napi::Array.

#if NAPI_VERSION >= 8
/// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into
/// JavaScript.
Expand Down Expand Up @@ -976,6 +999,55 @@ namespace Napi {
uint32_t Length() const;
};

#ifdef NAPI_CPP_EXCEPTIONS
class Object::const_iterator {
private:
enum class Type { BEGIN, END };

inline const_iterator(const Object* object, const Type type);

public:
inline const_iterator& operator++();

inline bool operator==(const const_iterator& other) const;

inline bool operator!=(const const_iterator& other) const;

inline const std::pair<Value, Object::PropertyLValue<Value>> operator*()
const;

private:
const Napi::Object* _object;
Array _keys;
uint32_t _index;

friend class Object;
};

class Object::iterator {
private:
enum class Type { BEGIN, END };

inline iterator(Object* object, const Type type);

public:
inline iterator& operator++();

inline bool operator==(const iterator& other) const;

inline bool operator!=(const iterator& other) const;

inline std::pair<Value, Object::PropertyLValue<Value>> operator*();

private:
Napi::Object* _object;
Array _keys;
uint32_t _index;

friend class Object;
};
#endif // NAPI_CPP_EXCEPTIONS

/// A JavaScript array buffer value.
class ArrayBuffer : public Object {
public:
Expand Down
28 changes: 28 additions & 0 deletions test/object/object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,30 @@ Value CreateObjectUsingMagic(const CallbackInfo& info) {
return obj;
}

#ifdef NAPI_CPP_EXCEPTIONS
Value Sum(const CallbackInfo& info) {
Object object = info[0].As<Object>();
int64_t sum = 0;

for (const auto& e : object) {
sum += static_cast<Value>(e.second).As<Number>().Int64Value();
}

return Number::New(info.Env(), sum);
}

void Increment(const CallbackInfo& info) {
Env env = info.Env();
Object object = info[0].As<Object>();

for (auto e : object) {
int64_t value = static_cast<Value>(e.second).As<Number>().Int64Value();
++value;
e.second = Napi::Number::New(env, value);
}
}
#endif // NAPI_CPP_EXCEPTIONS

Value InstanceOf(const CallbackInfo& info) {
Object obj = info[0].As<Object>();
Function constructor = info[1].As<Function>();
Expand Down Expand Up @@ -299,6 +323,10 @@ Object InitObject(Env env) {
exports["hasPropertyWithCppStyleString"] = Function::New(env, HasPropertyWithCppStyleString);

exports["createObjectUsingMagic"] = Function::New(env, CreateObjectUsingMagic);
#ifdef NAPI_CPP_EXCEPTIONS
exports["sum"] = Function::New(env, Sum);
exports["increment"] = Function::New(env, Increment);
#endif // NAPI_CPP_EXCEPTIONS

exports["addFinalizer"] = Function::New(env, AddFinalizer);
exports["addFinalizerWithHint"] = Function::New(env, AddFinalizerWithHint);
Expand Down
58 changes: 58 additions & 0 deletions test/object/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,62 @@ function test(binding) {
assert.strictEqual(binding.object.instanceOf({}, Ctor), false);
assert.strictEqual(binding.object.instanceOf(null, Ctor), false);
}

if ('sum' in binding.object) {
{
const obj = {
'-forbid': -0x4B1D,
'-feedcode': -0xFEEDC0DE,
'+office': +0x0FF1CE,
'+forbid': +0x4B1D,
'+deadbeef': +0xDEADBEEF,
'+feedcode': +0xFEEDC0DE,
};

let sum = 0;
for (const key in obj) {
sum += obj[key];
}

assert.strictEqual(binding.object.sum(obj), sum);
}

{
const obj = new Proxy({
'-forbid': -0x4B1D,
'-feedcode': -0xFEEDC0DE,
'+office': +0x0FF1CE,
'+forbid': +0x4B1D,
'+deadbeef': +0xDEADBEEF,
'+feedcode': +0xFEEDC0DE,
}, {
getOwnPropertyDescriptor(target, p) {
throw new Error("getOwnPropertyDescriptor error");
},
ownKeys(target) {
throw new Error("ownKeys error");
},
});

assert.throws(() => {
binding.object.sum(obj);
}, /ownKeys error/);
}
}

if ('increment' in binding.object) {
const obj = {
'a': 0,
'b': 1,
'c': 2,
};

binding.object.increment(obj);

assert.deepStrictEqual(obj, {
'a': 1,
'b': 2,
'c': 3,
});
}
}