Skip to content

Commit a556db4

Browse files
node-api: add napi_create_object_with_properties
PR-URL: #59953 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Vladimir Morozov <[email protected]>
1 parent 515ca29 commit a556db4

File tree

10 files changed

+343
-2
lines changed

10 files changed

+343
-2
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#include <assert.h>
2+
#include <node_api.h>
3+
#include <string>
4+
5+
struct BenchmarkParams {
6+
napi_value count_val;
7+
napi_value bench_obj;
8+
napi_value start_fn;
9+
napi_value end_fn;
10+
uint32_t count;
11+
};
12+
13+
static BenchmarkParams ParseBenchmarkArgs(napi_env env,
14+
const napi_callback_info info) {
15+
BenchmarkParams params;
16+
size_t argc = 4;
17+
napi_value args[4];
18+
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
19+
20+
params.count_val = args[0];
21+
params.bench_obj = args[1];
22+
params.start_fn = args[2];
23+
params.end_fn = args[3];
24+
25+
napi_get_value_uint32(env, params.count_val, &params.count);
26+
return params;
27+
}
28+
29+
static napi_value global_names[20];
30+
static napi_value global_values[20];
31+
static bool global_properties_initialized = false;
32+
33+
// Creating with many options because complains are when ~20 properties
34+
static void InitializeTestProperties(napi_env env) {
35+
if (global_properties_initialized) return;
36+
37+
for (int i = 0; i < 20; i++) {
38+
std::string name = "foo" + std::to_string(i);
39+
napi_create_string_utf8(
40+
env, name.c_str(), NAPI_AUTO_LENGTH, &global_names[i]);
41+
napi_create_string_utf8(
42+
env, name.c_str(), NAPI_AUTO_LENGTH, &global_values[i]);
43+
}
44+
global_properties_initialized = true;
45+
}
46+
47+
static napi_value CreateObjectWithPropertiesNew(napi_env env,
48+
napi_callback_info info) {
49+
BenchmarkParams params = ParseBenchmarkArgs(env, info);
50+
51+
InitializeTestProperties(env);
52+
53+
napi_value null_prototype;
54+
napi_get_null(env, &null_prototype);
55+
56+
napi_call_function(
57+
env, params.bench_obj, params.start_fn, 0, nullptr, nullptr);
58+
59+
for (uint32_t i = 0; i < params.count; i++) {
60+
napi_value obj;
61+
napi_create_object_with_properties(
62+
env, null_prototype, global_names, global_values, 20, &obj);
63+
}
64+
65+
napi_call_function(
66+
env, params.bench_obj, params.end_fn, 1, &params.count_val, nullptr);
67+
68+
return nullptr;
69+
}
70+
71+
static napi_value CreateObjectWithPropertiesOld(napi_env env,
72+
napi_callback_info info) {
73+
BenchmarkParams params = ParseBenchmarkArgs(env, info);
74+
75+
InitializeTestProperties(env);
76+
77+
napi_call_function(
78+
env, params.bench_obj, params.start_fn, 0, nullptr, nullptr);
79+
80+
for (uint32_t i = 0; i < params.count; i++) {
81+
napi_value obj;
82+
napi_create_object(env, &obj);
83+
for (int j = 0; j < 20; j++) {
84+
napi_set_property(env, obj, global_names[j], global_values[j]);
85+
}
86+
}
87+
88+
napi_call_function(
89+
env, params.bench_obj, params.end_fn, 1, &params.count_val, nullptr);
90+
91+
return nullptr;
92+
}
93+
94+
NAPI_MODULE_INIT() {
95+
napi_property_descriptor desc[] = {
96+
{"createObjectWithPropertiesNew",
97+
0,
98+
CreateObjectWithPropertiesNew,
99+
0,
100+
0,
101+
0,
102+
napi_default,
103+
0},
104+
{"createObjectWithPropertiesOld",
105+
0,
106+
CreateObjectWithPropertiesOld,
107+
0,
108+
0,
109+
0,
110+
napi_default,
111+
0},
112+
};
113+
114+
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
115+
return exports;
116+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'defines': ['NAPI_EXPERIMENTAL']
7+
}
8+
]
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const common = require('../../common.js');
4+
5+
let binding;
6+
try {
7+
binding = require(`./build/${common.buildType}/binding`);
8+
} catch {
9+
console.error(`${__filename}: Binding failed to load`);
10+
process.exit(0);
11+
}
12+
13+
const bench = common.createBenchmark(main, {
14+
n: [1e2, 1e3, 1e4, 1e5, 1e6],
15+
method: ['new', 'old'],
16+
});
17+
18+
function main({ n, method }) {
19+
if (method === 'new') {
20+
binding.createObjectWithPropertiesNew(n, bench, bench.start, bench.end);
21+
} else {
22+
binding.createObjectWithPropertiesOld(n, bench, bench.start, bench.end);
23+
}
24+
}

doc/api/n-api.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,6 +2637,43 @@ It is the equivalent of doing `new Object()` in JavaScript.
26372637
The JavaScript `Object` type is described in [Section object type][] of the
26382638
ECMAScript Language Specification.
26392639

2640+
#### `napi_create_object_with_properties`
2641+
2642+
<!-- YAML
2643+
added: REPLACEME
2644+
-->
2645+
2646+
> Stability: 1 - Experimental
2647+
2648+
```cpp
2649+
napi_status napi_create_object_with_properties(napi_env env,
2650+
napi_value prototype_or_null,
2651+
const napi_value* property_names,
2652+
const napi_value* property_values,
2653+
size_t property_count,
2654+
napi_value* result)
2655+
```
2656+
2657+
* `[in] env`: The environment that the API is invoked under.
2658+
* `[in] prototype_or_null`: The prototype object for the new object. Can be a
2659+
`napi_value` representing a JavaScript object to use as the prototype, a
2660+
`napi_value` representing JavaScript `null`, or a `nullptr` that will be converted to `null`.
2661+
* `[in] property_names`: Array of `napi_value` representing the property names.
2662+
* `[in] property_values`: Array of `napi_value` representing the property values.
2663+
* `[in] property_count`: Number of properties in the arrays.
2664+
* `[out] result`: A `napi_value` representing a JavaScript `Object`.
2665+
2666+
Returns `napi_ok` if the API succeeded.
2667+
2668+
This API creates a JavaScript `Object` with the specified prototype and
2669+
properties. This is more efficient than calling `napi_create_object` followed
2670+
by multiple `napi_set_property` calls, as it can create the object with all
2671+
properties atomically, avoiding potential V8 map transitions.
2672+
2673+
The arrays `property_names` and `property_values` must have the same length
2674+
specified by `property_count`. The properties are added to the object in the
2675+
order they appear in the arrays.
2676+
26402677
#### `napi_create_symbol`
26412678

26422679
<!-- YAML

src/js_native_api.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env,
7878
// Methods to create Primitive types/Objects
7979
NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env,
8080
napi_value* result);
81+
#ifdef NAPI_EXPERIMENTAL
82+
#define NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES
83+
NAPI_EXTERN napi_status NAPI_CDECL
84+
napi_create_object_with_properties(napi_env env,
85+
napi_value prototype_or_null,
86+
napi_value* property_names,
87+
napi_value* property_values,
88+
size_t property_count,
89+
napi_value* result);
90+
#endif // NAPI_EXPERIMENTAL
91+
8192
NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env,
8293
napi_value* result);
8394
NAPI_EXTERN napi_status NAPI_CDECL

src/js_native_api_v8.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,50 @@ napi_status NAPI_CDECL napi_create_object(napi_env env, napi_value* result) {
15931593
return napi_clear_last_error(env);
15941594
}
15951595

1596+
napi_status NAPI_CDECL
1597+
napi_create_object_with_properties(napi_env env,
1598+
napi_value prototype_or_null,
1599+
napi_value* property_names,
1600+
napi_value* property_values,
1601+
size_t property_count,
1602+
napi_value* result) {
1603+
CHECK_ENV_NOT_IN_GC(env);
1604+
CHECK_ARG(env, result);
1605+
1606+
if (property_count > 0) {
1607+
CHECK_ARG(env, property_names);
1608+
CHECK_ARG(env, property_values);
1609+
}
1610+
1611+
v8::Local<v8::Value> v8_prototype_or_null;
1612+
if (prototype_or_null == nullptr) {
1613+
v8_prototype_or_null = v8::Null(env->isolate);
1614+
} else {
1615+
v8_prototype_or_null = v8impl::V8LocalValueFromJsValue(prototype_or_null);
1616+
}
1617+
1618+
v8::LocalVector<v8::Name> v8_names(env->isolate, property_count);
1619+
v8::LocalVector<v8::Value> v8_values(env->isolate, property_count);
1620+
1621+
for (size_t i = 0; i < property_count; i++) {
1622+
v8::Local<v8::Value> name_value =
1623+
v8impl::V8LocalValueFromJsValue(property_names[i]);
1624+
RETURN_STATUS_IF_FALSE(env, name_value->IsName(), napi_name_expected);
1625+
v8_names[i] = name_value.As<v8::Name>();
1626+
v8_values[i] = v8impl::V8LocalValueFromJsValue(property_values[i]);
1627+
}
1628+
1629+
v8::Local<v8::Object> obj = v8::Object::New(env->isolate,
1630+
v8_prototype_or_null,
1631+
v8_names.data(),
1632+
v8_values.data(),
1633+
property_count);
1634+
1635+
RETURN_STATUS_IF_FALSE(env, !obj.IsEmpty(), napi_generic_failure);
1636+
*result = v8impl::JsValueFromV8LocalValue(obj);
1637+
return napi_clear_last_error(env);
1638+
}
1639+
15961640
napi_status NAPI_CDECL napi_create_array(napi_env env, napi_value* result) {
15971641
CHECK_ENV_NOT_IN_GC(env);
15981642
CHECK_ARG(env, result);

test/js-native-api/test_object/binding.gyp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"sources": [
66
"test_null.c",
77
"test_object.c"
8-
]
8+
],
9+
"defines": [
10+
"NAPI_EXPERIMENTAL"
11+
],
912
},
1013
{
1114
"target_name": "test_exceptions",

test/js-native-api/test_object/test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const assert = require('assert');
55
// Testing api calls for objects
66
const test_object = require(`./build/${common.buildType}/test_object`);
77

8-
98
const object = {
109
hello: 'world',
1110
array: [
@@ -391,3 +390,21 @@ assert.deepStrictEqual(test_object.TestGetProperty(), {
391390
delete obj.x;
392391
}, /Cannot delete property 'x' of #<Object>/);
393392
}
393+
394+
{
395+
const objectWithProperties = test_object.TestCreateObjectWithProperties();
396+
assert.strictEqual(typeof objectWithProperties, 'object');
397+
assert.strictEqual(objectWithProperties.name, 'Foo');
398+
assert.strictEqual(objectWithProperties.age, 42);
399+
assert.strictEqual(objectWithProperties.active, true);
400+
401+
const emptyObject = test_object.TestCreateObjectWithPropertiesEmpty();
402+
assert.strictEqual(typeof emptyObject, 'object');
403+
assert.strictEqual(Object.keys(emptyObject).length, 0);
404+
405+
const objectWithCustomPrototype = test_object.TestCreateObjectWithCustomPrototype();
406+
assert.strictEqual(typeof objectWithCustomPrototype, 'object');
407+
assert.deepStrictEqual(Object.getOwnPropertyNames(objectWithCustomPrototype), ['value']);
408+
assert.strictEqual(objectWithCustomPrototype.value, 42);
409+
assert.strictEqual(typeof objectWithCustomPrototype.test, 'function');
410+
}

0 commit comments

Comments
 (0)