Skip to content

Commit

Permalink
src: add iterator for Object
Browse files Browse the repository at this point in the history
Refs: #830
Signed-off-by: Darshan Sen <[email protected]>
  • Loading branch information
RaisinTen authored and mhdawson committed Sep 24, 2021
1 parent d8fc7b8 commit bd8f6e6
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 0 deletions.
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.

```cpp
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.
```cpp
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 @@ -1306,6 +1306,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 @@ -1530,6 +1538,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
);

/// 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

#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,
});
}
}

0 comments on commit bd8f6e6

Please sign in to comment.