diff --git a/doc/object.md b/doc/object.md index fb7d53ad1..487dced9b 100644 --- a/doc/object.md +++ b/doc/object.md @@ -69,6 +69,33 @@ Napi::Object Napi::Object::New(napi_env env); Creates a new `Napi::Object` value. +### New() + +```cpp +Napi::Object Napi::Object::New( + napi_env env, + napi_value prototypeOrNull, + std::vector& propertyNames, + std::vector& propertyValues); +``` +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Value` + object. +- `[in] prototypeOrNull`: The prototype object for the new object. Can be an + `napi_value` representing a JavaScript object to use as the prototype, an + `napi_value` representing JavaScript `null`, or `nullptr` that will be + converted to `null`. +- `[in] propertyNames`: Array of `napi_value`s representing the property names. +- `[in] propertyValues`: Array of `napi_value`s representing the property + values. + +Creates a new `Napi::Object` with the specified prototype and properties. This +is more efficient than calling `Napi::Object::New()` followed by multiple +`Set()` calls, as it can create the object with all properties atomically. + +**NOTE**: The support for this overload of `Napi::Object::New()` is only +available when using `NAPI_EXPERIMENTAL` and building against Node.js headers +that supports this feature. + ### Set() ```cpp diff --git a/napi-inl.h b/napi-inl.h index 9c0c47859..490ded065 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1635,6 +1635,31 @@ inline Object Object::New(napi_env env) { return Object(env, value); } +#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES +inline Object Object::New(napi_env env, + napi_value prototypeOrNull, + std::vector& propertyNames, + std::vector& propertyValues) { + if (propertyNames.size() != propertyValues.size()) { + Napi::Error::New(env, "Mismatch in size of property names and values") + .ThrowAsJavaScriptException(); + return Object(); + } + + napi_value value; + napi_status status = + node_api_create_object_with_properties(env, + prototypeOrNull, + propertyNames.data(), + propertyValues.data(), + propertyNames.size(), + &value); + + NAPI_THROW_IF_FAILED(env, status, Object()); + return Object(env, value); +} +#endif + inline void Object::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); diff --git a/napi.h b/napi.h index 870a5c290..7724a461b 100644 --- a/napi.h +++ b/napi.h @@ -903,6 +903,16 @@ class Object : public TypeTaggable { static Object New(napi_env env ///< Node-API environment ); +#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES + /// Creates a new Object value with the given property names and values. + static Object New( + napi_env env, ///< Node-API environment + napi_value prototypeOrNull, ///< Prototype (Object) or null / empty Value + std::vector& propertyNames, ///< Property names + std::vector& propertyValues ///< Property values + ); +#endif + static void CheckCast(napi_env env, napi_value value); Object(); ///< Creates a new _empty_ Object instance. diff --git a/test/object/object.cc b/test/object/object.cc index 60aae768f..401346563 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -356,6 +356,40 @@ Value SetPrototype(const CallbackInfo& info) { } #endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES +Value CreateObjectWithProperties(const CallbackInfo& info) { + Env env = info.Env(); + Value prototype = info.Length() > 0 ? info[0] : Value(); + Array propertyNames = + info.Length() > 1 ? info[1].As() : Array::New(env); + Array propertyValues = + info.Length() > 2 ? info[2].As() : Array::New(env); + + std::vector names; + std::vector values; + +#ifdef NODE_ADDON_API_ENABLE_MAYBE + for (uint32_t i = 0; i < propertyNames.Length(); ++i) { + names.push_back(propertyNames.Get(i).Unwrap()); + } + + for (uint32_t i = 0; i < propertyValues.Length(); ++i) { + values.push_back(propertyValues.Get(i).Unwrap()); + } +#else + for (uint32_t i = 0; i < propertyNames.Length(); ++i) { + names.push_back(propertyNames.Get(i)); + } + + for (uint32_t i = 0; i < propertyValues.Length(); ++i) { + values.push_back(propertyValues.Get(i)); + } +#endif + + return Object::New(env, prototype, names, values); +} +#endif + Object InitObject(Env env) { Object exports = Object::New(env); @@ -444,5 +478,10 @@ Object InitObject(Env env) { exports["setPrototype"] = Function::New(env, SetPrototype); #endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +#ifdef NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES + exports["createObjectWithProperties"] = + Function::New(env, CreateObjectWithProperties); +#endif // NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES + return exports; } diff --git a/test/object/object.js b/test/object/object.js index 13488940c..f3acfba4b 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -227,4 +227,53 @@ function test (binding) { assert.strictEqual(binding.object.setPrototype(obj, prototype), true); assert.strictEqual(Object.getPrototypeOf(obj), prototype); } + + if ('createObjectWithProperties' in binding.object) { + { + const prototype = {}; + const names = ['name', 'age', 'active', Symbol.for('id')]; + const values = ['Foo', 42, true, 12345]; + const obj = binding.object.createObjectWithProperties(prototype, names, values); + const descriptors = Object.getOwnPropertyDescriptors(obj); + + assert.strictEqual(Object.getPrototypeOf(obj), prototype); + + assert.equal(Reflect.ownKeys(descriptors).length, names.length); + + for (let i = 0; i < names.length; i++) { + const expectedName = names[i]; + const expectedValue = values[i]; + + assert.ok(expectedName in descriptors); + assert.strictEqual(descriptors[expectedName].value, expectedValue); + } + } + + { + // Test `null` Napi::Value passed as prototype + const prototype = null; + const obj = binding.object.createObjectWithProperties(prototype); + assert.strictEqual(Object.getPrototypeOf(obj), prototype); + } + + { + // Test empty Napi::Value passed as prototype + const obj = binding.object.createObjectWithProperties(); + assert.strictEqual(Object.getPrototypeOf(obj), null); + } + + { + // Test mismatch in length between property names and values + const expectedErrorMessage = 'Mismatch in size of property names and values'; + + try { + binding.object.createObjectWithProperties(null, ['foo'], []); + throw new Error(`Expected error "${expectedErrorMessage}" was not thrown`); + } catch (e) { + if (e.message !== expectedErrorMessage) { + throw e; + } + } + } + } }