node-api: add napi_create_object_with_properties

PR-URL: https://github.com/nodejs/node/pull/59953
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com>
This commit is contained in:
Miguel Marcondes Filho 2025-10-30 11:10:44 -03:00 committed by GitHub
parent 515ca295fb
commit a556db4782
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 343 additions and 2 deletions

View File

@ -0,0 +1 @@
build/

View File

@ -0,0 +1,116 @@
#include <assert.h>
#include <node_api.h>
#include <string>
struct BenchmarkParams {
napi_value count_val;
napi_value bench_obj;
napi_value start_fn;
napi_value end_fn;
uint32_t count;
};
static BenchmarkParams ParseBenchmarkArgs(napi_env env,
const napi_callback_info info) {
BenchmarkParams params;
size_t argc = 4;
napi_value args[4];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
params.count_val = args[0];
params.bench_obj = args[1];
params.start_fn = args[2];
params.end_fn = args[3];
napi_get_value_uint32(env, params.count_val, &params.count);
return params;
}
static napi_value global_names[20];
static napi_value global_values[20];
static bool global_properties_initialized = false;
// Creating with many options because complains are when ~20 properties
static void InitializeTestProperties(napi_env env) {
if (global_properties_initialized) return;
for (int i = 0; i < 20; i++) {
std::string name = "foo" + std::to_string(i);
napi_create_string_utf8(
env, name.c_str(), NAPI_AUTO_LENGTH, &global_names[i]);
napi_create_string_utf8(
env, name.c_str(), NAPI_AUTO_LENGTH, &global_values[i]);
}
global_properties_initialized = true;
}
static napi_value CreateObjectWithPropertiesNew(napi_env env,
napi_callback_info info) {
BenchmarkParams params = ParseBenchmarkArgs(env, info);
InitializeTestProperties(env);
napi_value null_prototype;
napi_get_null(env, &null_prototype);
napi_call_function(
env, params.bench_obj, params.start_fn, 0, nullptr, nullptr);
for (uint32_t i = 0; i < params.count; i++) {
napi_value obj;
napi_create_object_with_properties(
env, null_prototype, global_names, global_values, 20, &obj);
}
napi_call_function(
env, params.bench_obj, params.end_fn, 1, &params.count_val, nullptr);
return nullptr;
}
static napi_value CreateObjectWithPropertiesOld(napi_env env,
napi_callback_info info) {
BenchmarkParams params = ParseBenchmarkArgs(env, info);
InitializeTestProperties(env);
napi_call_function(
env, params.bench_obj, params.start_fn, 0, nullptr, nullptr);
for (uint32_t i = 0; i < params.count; i++) {
napi_value obj;
napi_create_object(env, &obj);
for (int j = 0; j < 20; j++) {
napi_set_property(env, obj, global_names[j], global_values[j]);
}
}
napi_call_function(
env, params.bench_obj, params.end_fn, 1, &params.count_val, nullptr);
return nullptr;
}
NAPI_MODULE_INIT() {
napi_property_descriptor desc[] = {
{"createObjectWithPropertiesNew",
0,
CreateObjectWithPropertiesNew,
0,
0,
0,
napi_default,
0},
{"createObjectWithPropertiesOld",
0,
CreateObjectWithPropertiesOld,
0,
0,
0,
napi_default,
0},
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}

View File

@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'defines': ['NAPI_EXPERIMENTAL']
}
]
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../../common.js');
let binding;
try {
binding = require(`./build/${common.buildType}/binding`);
} catch {
console.error(`${__filename}: Binding failed to load`);
process.exit(0);
}
const bench = common.createBenchmark(main, {
n: [1e2, 1e3, 1e4, 1e5, 1e6],
method: ['new', 'old'],
});
function main({ n, method }) {
if (method === 'new') {
binding.createObjectWithPropertiesNew(n, bench, bench.start, bench.end);
} else {
binding.createObjectWithPropertiesOld(n, bench, bench.start, bench.end);
}
}

View File

@ -2637,6 +2637,43 @@ It is the equivalent of doing `new Object()` in JavaScript.
The JavaScript `Object` type is described in [Section object type][] of the
ECMAScript Language Specification.
#### `napi_create_object_with_properties`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
```cpp
napi_status napi_create_object_with_properties(napi_env env,
napi_value prototype_or_null,
const napi_value* property_names,
const napi_value* property_values,
size_t property_count,
napi_value* result)
```
* `[in] env`: The environment that the API is invoked under.
* `[in] prototype_or_null`: The prototype object for the new object. Can be a
`napi_value` representing a JavaScript object to use as the prototype, a
`napi_value` representing JavaScript `null`, or a `nullptr` that will be converted to `null`.
* `[in] property_names`: Array of `napi_value` representing the property names.
* `[in] property_values`: Array of `napi_value` representing the property values.
* `[in] property_count`: Number of properties in the arrays.
* `[out] result`: A `napi_value` representing a JavaScript `Object`.
Returns `napi_ok` if the API succeeded.
This API creates a JavaScript `Object` with the specified prototype and
properties. This is more efficient than calling `napi_create_object` followed
by multiple `napi_set_property` calls, as it can create the object with all
properties atomically, avoiding potential V8 map transitions.
The arrays `property_names` and `property_values` must have the same length
specified by `property_count`. The properties are added to the object in the
order they appear in the arrays.
#### `napi_create_symbol`
<!-- YAML

View File

@ -78,6 +78,17 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env,
// Methods to create Primitive types/Objects
NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env,
napi_value* result);
#ifdef NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
NAPI_EXTERN napi_status NAPI_CDECL
napi_create_object_with_properties(napi_env env,
napi_value prototype_or_null,
napi_value* property_names,
napi_value* property_values,
size_t property_count,
napi_value* result);
#endif // NAPI_EXPERIMENTAL
NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env,
napi_value* result);
NAPI_EXTERN napi_status NAPI_CDECL

View File

@ -1593,6 +1593,50 @@ napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value* result) {
return napi_clear_last_error(env);
}
napi_status NAPI_CDECL
napi_create_object_with_properties(napi_env env,
napi_value prototype_or_null,
napi_value* property_names,
napi_value* property_values,
size_t property_count,
napi_value* result) {
CHECK_ENV_NOT_IN_GC(env);
CHECK_ARG(env, result);
if (property_count > 0) {
CHECK_ARG(env, property_names);
CHECK_ARG(env, property_values);
}
v8::Local<v8::Value> v8_prototype_or_null;
if (prototype_or_null == nullptr) {
v8_prototype_or_null = v8::Null(env->isolate);
} else {
v8_prototype_or_null = v8impl::V8LocalValueFromJsValue(prototype_or_null);
}
v8::LocalVector<v8::Name> v8_names(env->isolate, property_count);
v8::LocalVector<v8::Value> v8_values(env->isolate, property_count);
for (size_t i = 0; i < property_count; i++) {
v8::Local<v8::Value> name_value =
v8impl::V8LocalValueFromJsValue(property_names[i]);
RETURN_STATUS_IF_FALSE(env, name_value->IsName(), napi_name_expected);
v8_names[i] = name_value.As<v8::Name>();
v8_values[i] = v8impl::V8LocalValueFromJsValue(property_values[i]);
}
v8::Local<v8::Object> obj = v8::Object::New(env->isolate,
v8_prototype_or_null,
v8_names.data(),
v8_values.data(),
property_count);
RETURN_STATUS_IF_FALSE(env, !obj.IsEmpty(), napi_generic_failure);
*result = v8impl::JsValueFromV8LocalValue(obj);
return napi_clear_last_error(env);
}
napi_status NAPI_CDECL napi_create_array(napi_env env, napi_value* result) {
CHECK_ENV_NOT_IN_GC(env);
CHECK_ARG(env, result);

View File

@ -5,7 +5,10 @@
"sources": [
"test_null.c",
"test_object.c"
]
],
"defines": [
"NAPI_EXPERIMENTAL"
],
},
{
"target_name": "test_exceptions",

View File

@ -5,7 +5,6 @@ const assert = require('assert');
// Testing api calls for objects
const test_object = require(`./build/${common.buildType}/test_object`);
const object = {
hello: 'world',
array: [
@ -391,3 +390,21 @@ assert.deepStrictEqual(test_object.TestGetProperty(), {
delete obj.x;
}, /Cannot delete property 'x' of #<Object>/);
}
{
const objectWithProperties = test_object.TestCreateObjectWithProperties();
assert.strictEqual(typeof objectWithProperties, 'object');
assert.strictEqual(objectWithProperties.name, 'Foo');
assert.strictEqual(objectWithProperties.age, 42);
assert.strictEqual(objectWithProperties.active, true);
const emptyObject = test_object.TestCreateObjectWithPropertiesEmpty();
assert.strictEqual(typeof emptyObject, 'object');
assert.strictEqual(Object.keys(emptyObject).length, 0);
const objectWithCustomPrototype = test_object.TestCreateObjectWithCustomPrototype();
assert.strictEqual(typeof objectWithCustomPrototype, 'object');
assert.deepStrictEqual(Object.getOwnPropertyNames(objectWithCustomPrototype), ['value']);
assert.strictEqual(objectWithCustomPrototype.value, 42);
assert.strictEqual(typeof objectWithCustomPrototype.test, 'function');
}

View File

@ -710,6 +710,79 @@ CheckTypeTag(napi_env env, napi_callback_info info) {
return js_result;
}
static napi_value TestCreateObjectWithProperties(napi_env env,
napi_callback_info info) {
napi_value names[3];
napi_value values[3];
napi_value result;
NODE_API_CALL(
env, napi_create_string_utf8(env, "name", NAPI_AUTO_LENGTH, &names[0]));
NODE_API_CALL(
env, napi_create_string_utf8(env, "Foo", NAPI_AUTO_LENGTH, &values[0]));
NODE_API_CALL(
env, napi_create_string_utf8(env, "age", NAPI_AUTO_LENGTH, &names[1]));
NODE_API_CALL(env, napi_create_int32(env, 42, &values[1]));
NODE_API_CALL(
env, napi_create_string_utf8(env, "active", NAPI_AUTO_LENGTH, &names[2]));
NODE_API_CALL(env, napi_get_boolean(env, true, &values[2]));
napi_value null_prototype;
NODE_API_CALL(env, napi_get_null(env, &null_prototype));
NODE_API_CALL(env,
napi_create_object_with_properties(
env, null_prototype, names, values, 3, &result));
return result;
}
static napi_value TestCreateObjectWithPropertiesEmpty(napi_env env,
napi_callback_info info) {
napi_value result;
NODE_API_CALL(
env,
napi_create_object_with_properties(env, NULL, NULL, NULL, 0, &result));
return result;
}
static napi_value TestCreateObjectWithCustomPrototype(napi_env env,
napi_callback_info info) {
napi_value prototype;
napi_value method_name;
napi_value method_func;
napi_value names[1];
napi_value values[1];
napi_value result;
NODE_API_CALL(env, napi_create_object(env, &prototype));
NODE_API_CALL(
env,
napi_create_string_utf8(env, "test", NAPI_AUTO_LENGTH, &method_name));
NODE_API_CALL(env,
napi_create_function(env,
"test",
NAPI_AUTO_LENGTH,
TestCreateObjectWithProperties,
NULL,
&method_func));
NODE_API_CALL(env,
napi_set_property(env, prototype, method_name, method_func));
NODE_API_CALL(
env, napi_create_string_utf8(env, "value", NAPI_AUTO_LENGTH, &names[0]));
NODE_API_CALL(env, napi_create_int32(env, 42, &values[0]));
NODE_API_CALL(env,
napi_create_object_with_properties(
env, prototype, names, values, 1, &result));
return result;
}
EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
@ -743,6 +816,12 @@ napi_value Init(napi_env env, napi_value exports) {
DECLARE_NODE_API_PROPERTY("TestGetProperty", TestGetProperty),
DECLARE_NODE_API_PROPERTY("TestFreeze", TestFreeze),
DECLARE_NODE_API_PROPERTY("TestSeal", TestSeal),
DECLARE_NODE_API_PROPERTY("TestCreateObjectWithProperties",
TestCreateObjectWithProperties),
DECLARE_NODE_API_PROPERTY("TestCreateObjectWithPropertiesEmpty",
TestCreateObjectWithPropertiesEmpty),
DECLARE_NODE_API_PROPERTY("TestCreateObjectWithCustomPrototype",
TestCreateObjectWithCustomPrototype),
};
init_test_null(env, exports);