Skip to content

[Node-API] Symbol.toStringTag support #41358

Description

@motla

What is the problem this feature will solve?

Using Node-API, I would like my C/C++ addon to generate a JavaScript object decorated with a tag name, like this:

const obj = {
  name: "john",
  [Symbol.toStringTag]: "MyTag"
};

So I can get:
image

I believe this is not possible to set the Symbol.toStringTag property to an object created with the current Node-API.

What is the feature you are proposing to solve the problem?

I guess we need access to the global Symbol.toStringTag and other well-known Symbols defined in JavaScript.
Below is the Node.js example to create a new Symbol and set it as a property for an object (along other properties):

napi_value symbol_description;
napi_value name_symbol;
NODE_API_CALL(env,
napi_create_string_utf8(
env, "NameKeySymbol", NAPI_AUTO_LENGTH, &symbol_description));
NODE_API_CALL(env,
napi_create_symbol(env, symbol_description, &name_symbol));
napi_property_descriptor properties[] = {
{ "echo", 0, Echo, 0, 0, 0, napi_enumerable, 0 },
{ "readwriteValue", 0, 0, 0, 0, number, napi_enumerable | napi_writable, 0 },
{ "readonlyValue", 0, 0, 0, 0, number, napi_enumerable, 0},
{ "hiddenValue", 0, 0, 0, 0, number, napi_default, 0},
{ NULL, name_value, 0, 0, 0, number, napi_enumerable, 0},
{ NULL, name_symbol, 0, 0, 0, number, napi_enumerable, 0},
{ "readwriteAccessor1", 0, 0, GetValue, SetValue, 0, napi_default, 0},
{ "readwriteAccessor2", 0, 0, GetValue, SetValue, 0, napi_writable, 0},
{ "readonlyAccessor1", 0, 0, GetValue, NULL, 0, napi_default, 0},
{ "readonlyAccessor2", 0, 0, GetValue, NULL, 0, napi_writable, 0},
{ "hasNamedProperty", 0, HasNamedProperty, 0, 0, 0, napi_default, 0 },
};
NODE_API_CALL(env, napi_define_properties(
env, exports, sizeof(properties) / sizeof(*properties), properties));

Instead of creating a new Symbol we would have to use the existing Symbol.toStringTag.
Maybe you could consider to have a method like napi_get_symbol_tostringtag(env, &name_symbol) to replace napi_create_symbol(.. used on line 78.

What alternatives have you considered?

Explored the Node-API but I have no clue for a workaround in the C/C++ code.

For a root object returned by the add-on, one may use the add-on main.js file to add the property dynamically in JavaScript before the export, but it's not pretty.

Activity

  1. changed the title [-][Node-API] Symbol.toStringTag support in created objects[/-] [+][Node-API] Symbol.toStringTag support[/+] on Dec 30, 2021
  2. RaisinTen commented on Jan 1, 2022

    @RaisinTen
    Member

    PR: #41370

  3. addaleax commented on Jan 1, 2022

    @addaleax
    Member

    You can use napi_get_global to access the global variable of the current context; and then use napi_get_property to access Symbol.toStringTag.

    Using the C++ wrapper API, this is a one-liner: env.Global().Get("Symbol").As<Napi::Object>().Get("toStringTag").

    I would strongly recommend doing that instead of adding a new functionality to get a small, specific, not very well-defined JS built-in values like #41370 would suggest. Using the existing mechanisms also has the advantage that it already works just fine, and you don’t need to wait for a new Node-API version in order to use this feature.

  4. motla commented on Jan 2, 2022

    @motla
    Author

    Well that works perfectly. Thanks @addaleax.
    Indeed, no need for change on the Node-API with your solution.

    Actually I printed global in Node but global.Symbol was not enumerated so I thought it was not available there.

    Maybe we can write an example in the docs or somewhere? Because I never came across any solution to do this.

    Here is another example that works in C (no C++ wrapper API):

    static napi_value init_module(napi_env env, napi_value exports) {
      napi_value global, symbol, symbol_to_string_tag, tag_name;
    
      // Get JavaScript global Symbol.toStringTag
      if(napi_get_global(env, &global) != napi_ok) return NULL;
      if(napi_get_named_property(env, global, "Symbol", &symbol) != napi_ok) return NULL;
      if(napi_get_named_property(env, symbol, "toStringTag", &symbol_to_string_tag) != napi_ok) return NULL;
    
      // Declare JavaScript tag name
      if(napi_create_string_utf8(env, "My Module", NAPI_AUTO_LENGTH, &tag_name) != napi_ok) return NULL;
    
      // Declare properties for JavaScript `exports` object
      napi_property_descriptor props[] = {
        { NULL, symbol_to_string_tag, NULL, NULL, NULL, tag_name, napi_enumerable, NULL },
        // ...
      };
    
      // Write properties to JavaScript `exports` object
      size_t nb_props = sizeof(props) / sizeof(napi_property_descriptor);
      if(napi_define_properties(env, exports, nb_props, props) != napi_ok) return NULL;
      
      return exports;
    }
    
    NAPI_MODULE(my_module, init_module)
  5. RaisinTen commented on Jan 2, 2022

    @RaisinTen
    Member

    Using the C++ wrapper API, this is a one-liner: env.Global().Get("Symbol").AsNapi::Object().Get("toStringTag").

    This already takes care of getting the Symbol object from the global object:

    Napi::Symbol::WellKnown(env, "toStringTag");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestIssues requesting new Node.js features.node-apiIssues and PRs related to Node-API.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions