Skip to content

Commit bbf03d9

Browse files
committed
feat: add Object::New overload utilizing node_api_create_object_with_properties
1 parent e56a8dd commit bbf03d9

5 files changed

Lines changed: 150 additions & 0 deletions

File tree

doc/object.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,33 @@ Napi::Object Napi::Object::New(napi_env env);
6969

7070
Creates a new `Napi::Object` value.
7171

72+
### New()
73+
74+
```cpp
75+
Napi::Object Napi::Object::New(
76+
napi_env env,
77+
napi_value prototypeOrNull,
78+
std::vector<napi_value>& propertyNames,
79+
std::vector<napi_value>& propertyValues);
80+
```
81+
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Value`
82+
object.
83+
- `[in] prototypeOrNull`: The prototype object for the new object. Can be an
84+
`Napi::Value` representing a JavaScript object to use as the prototype, an
85+
`Napi::Value` representing JavaScript `null`, or an empty `Napi::Value` that
86+
will be converted to `null`.
87+
- `[in] propertyNames`: Array of `napi::Value` representing the property names.
88+
- `[in] propertyValues`: Array of `napi::Value` representing the property
89+
values.
90+
91+
Creates a new `Napi::Object` with the specified prototype and properties. This
92+
is more efficient than calling `Napi::Object::New()` followed by multiple
93+
`Set()` calls, as it can create the object with all properties atomically.
94+
95+
**NOTE**: The support for this overload of `Napi::Object::New()` is only
96+
available when using `NAPI_EXPERIMENTAL` and building against Node.js headers
97+
that supports this feature.
98+
7299
### Set()
73100
74101
```cpp

napi-inl.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,31 @@ inline Object Object::New(napi_env env) {
16351635
return Object(env, value);
16361636
}
16371637

1638+
#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
1639+
inline Object Object::New(napi_env env,
1640+
napi_value prototypeOrNull,
1641+
std::vector<napi_value>& propertyNames,
1642+
std::vector<napi_value>& propertyValues) {
1643+
if (propertyNames.size() != propertyValues.size()) {
1644+
Napi::Error::New(env, "Mismatch in size of property names and values")
1645+
.ThrowAsJavaScriptException();
1646+
return Object();
1647+
}
1648+
1649+
napi_value value;
1650+
napi_status status =
1651+
node_api_create_object_with_properties(env,
1652+
prototypeOrNull,
1653+
propertyNames.data(),
1654+
propertyValues.data(),
1655+
propertyNames.size(),
1656+
&value);
1657+
1658+
NAPI_THROW_IF_FAILED(env, status, Object());
1659+
return Object(env, value);
1660+
}
1661+
#endif
1662+
16381663
inline void Object::CheckCast(napi_env env, napi_value value) {
16391664
NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value");
16401665

napi.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,16 @@ class Object : public TypeTaggable {
903903
static Object New(napi_env env ///< Node-API environment
904904
);
905905

906+
#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
907+
/// Creates a new Object value with the given property names and values.
908+
static Object New(
909+
napi_env env, ///< Node-API environment
910+
napi_value prototypeOrNull, ///< Prototype (Object) or null / empty Value
911+
std::vector<napi_value>& propertyNames, ///< Property names
912+
std::vector<napi_value>& propertyValues ///< Property values
913+
);
914+
#endif
915+
906916
static void CheckCast(napi_env env, napi_value value);
907917

908918
Object(); ///< Creates a new _empty_ Object instance.

test/object/object.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,40 @@ Value SetPrototype(const CallbackInfo& info) {
356356
}
357357
#endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE
358358

359+
#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
360+
Value CreateObjectWithProperties(const CallbackInfo& info) {
361+
Env env = info.Env();
362+
Value prototype = info.Length() > 0 ? info[0] : Value();
363+
Array propertyNames =
364+
info.Length() > 1 ? info[1].As<Array>() : Array::New(env);
365+
Array propertyValues =
366+
info.Length() > 2 ? info[2].As<Array>() : Array::New(env);
367+
368+
std::vector<napi_value> names;
369+
std::vector<napi_value> values;
370+
371+
#ifdef NODE_ADDON_API_ENABLE_MAYBE
372+
for (uint32_t i = 0; i < propertyNames.Length(); ++i) {
373+
names.push_back(propertyNames.Get(i).Unwrap());
374+
}
375+
376+
for (uint32_t i = 0; i < propertyValues.Length(); ++i) {
377+
values.push_back(propertyValues.Get(i).Unwrap());
378+
}
379+
#else
380+
for (uint32_t i = 0; i < propertyNames.Length(); ++i) {
381+
names.push_back(propertyNames.Get(i));
382+
}
383+
384+
for (uint32_t i = 0; i < propertyValues.Length(); ++i) {
385+
values.push_back(propertyValues.Get(i));
386+
}
387+
#endif
388+
389+
return Object::New(env, prototype, names, values);
390+
}
391+
#endif
392+
359393
Object InitObject(Env env) {
360394
Object exports = Object::New(env);
361395

@@ -444,5 +478,10 @@ Object InitObject(Env env) {
444478
exports["setPrototype"] = Function::New(env, SetPrototype);
445479
#endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE
446480

481+
#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
482+
exports["createObjectWithProperties"] =
483+
Function::New(env, CreateObjectWithProperties);
484+
#endif // NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
485+
447486
return exports;
448487
}

test/object/object.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,53 @@ function test (binding) {
227227
assert.strictEqual(binding.object.setPrototype(obj, prototype), true);
228228
assert.strictEqual(Object.getPrototypeOf(obj), prototype);
229229
}
230+
231+
if ('createObjectWithProperties' in binding.object) {
232+
{
233+
const prototype = {};
234+
const names = ['name', 'age', 'active', Symbol.for('id')];
235+
const values = ['Foo', 42, true, 12345];
236+
const obj = binding.object.createObjectWithProperties(prototype, names, values);
237+
const descriptors = Object.getOwnPropertyDescriptors(obj);
238+
239+
assert.strictEqual(Object.getPrototypeOf(obj), prototype);
240+
241+
assert.equal(Reflect.ownKeys(descriptors).length, names.length);
242+
243+
for (let i = 0; i < names.length; i++) {
244+
const expectedName = names[i];
245+
const expectedValue = values[i];
246+
247+
assert.ok(expectedName in descriptors);
248+
assert.strictEqual(descriptors[expectedName].value, expectedValue);
249+
}
250+
}
251+
252+
{
253+
// Test `null` Napi::Value passed as prototype
254+
const prototype = null;
255+
const obj = binding.object.createObjectWithProperties(prototype);
256+
assert.strictEqual(Object.getPrototypeOf(obj), prototype);
257+
}
258+
259+
{
260+
// Test empty Napi::Value passed as prototype
261+
const obj = binding.object.createObjectWithProperties();
262+
assert.strictEqual(Object.getPrototypeOf(obj), null);
263+
}
264+
265+
{
266+
// Test mismatch in length between property names and values
267+
const expectedErrorMessage = 'Mismatch in size of property names and values';
268+
269+
try {
270+
binding.object.createObjectWithProperties(null, ['foo'], []);
271+
throw new Error(`Expected error "${expectedErrorMessage}" was not thrown`);
272+
} catch (e) {
273+
if (e.message !== expectedErrorMessage) {
274+
throw e;
275+
}
276+
}
277+
}
278+
}
230279
}

0 commit comments

Comments
 (0)