Skip to content

No NAPI access to Javascript Map objects #3303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nadiasvertex opened this issue Apr 5, 2021 · 8 comments
Closed

No NAPI access to Javascript Map objects #3303

nadiasvertex opened this issue Apr 5, 2021 · 8 comments

Comments

@nadiasvertex
Copy link

  • Node.js Version: All
  • OS: All
  • Scope: NAPI

I am building a C++ plugin for nodejs, and we would like to be able to return a Javascript Map() object to the user. The version of nodejs that we are using clearly supports Map(), but I have been unable to find any support for creating, iterating, or indexing Map objects.

The documentation at https://nodejs.org/api/n-api.html contains no mentions of Map objects at all. The word "map" only appears once on this page and is not used in any context that applies to the collection.

@addaleax
Copy link
Member

addaleax commented Apr 6, 2021

Yes – at this moment, N-API does not provide any wrapper for Map or Set. You can still use them like regular JS objects though, i.e. use the Map constructor to create objects from N-API and call .set() for adding entries to it.

@nadiasvertex
Copy link
Author

I understand. It's awkward and less efficient to have JS compile code fragments every time I need to do something like this, though. Do you have any idea when this will be available? Is there a design spec that just needs implementing?

@addaleax
Copy link
Member

addaleax commented Apr 6, 2021

@nadiasvertex I guess you could open a ticket in https://github.com/nodejs/node/issues, if you’d like to see this implemented (or are interested in implementing it yourself).

That being said, an approach that I think might be a bit nicer and also be worth exploring would be to create C++ wrappers for Map and Set that use the existing set of N-API primitives, and cache methods like set/delete so they only need to be retrieved once (note that either way, compiling JS code is not required here at all). That could even be provided as a header-only library on npm, with one big advantage being that it would also work across all existing supported Node.js versions.

@nadiasvertex
Copy link
Author

I wouldn't be against contributing something. I'd have to clear it with my manager and see where our priorities are.

That being said, the C++ solution you mentioned is very interesting. However I don't see how it would work. First of all, the items in the Map() are not exposed as properties. (I've already tried that.) So I don't know how I would iterate (get_all_property_names doesn't work, I've tried that.)

I assume I'd use napi_new_instance() to create a new map, but I don't see how I would get the constructor for the Map. If you have some pointers, I would appreciate it. Thanks for your time!

@addaleax
Copy link
Member

addaleax commented Apr 6, 2021

Here’s an example addon that creates a wrapper for Map, sets an entry in the Map, then lists all entries and returns the first one. It’s obviously not very complete or pretty, but I think the idea might become clear. Things like the methods could also be cached in the constructor here.

#include <napi.h>

class NapiMap : public Napi::Object {
 public:
  NapiMap(napi_env env, napi_value value) : Object(env, value) {}

  static NapiMap Create(Napi::Env env) {
    return env.Global().Get("Map").As<Napi::Function>().New({}).As<NapiMap>();
  }

  std::vector<Napi::Array> Entries() {
    std::vector<Napi::Array> result;
    Napi::Object iterator = Get("entries").As<Napi::Function>().Call(*this, {}).As<Napi::Object>();
    Napi::Function next = iterator.Get("next").As<Napi::Function>();
    while (true) {
      Napi::Object entry = next.Call(iterator, {}).As<Napi::Object>();
      if (entry.Get("done").ToBoolean()) break;
      result.push_back(entry.Get("value").As<Napi::Array>());
    }
    return result;
  }

  void Set(Napi::Value key, Napi::Value value) {
    Get("set").As<Napi::Function>().Call(*this, {key, value});
  }
};

Napi::Value Method(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  NapiMap map = NapiMap::Create(env);
  map.Set(Napi::Number::New(env, 42), Napi::String::New(env, "foo"));
  return map.Entries()[0];
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(Napi::String::New(env, "hello"),
              Napi::Function::New(env, Method));
  return exports;
}

NODE_API_MODULE(hello, Init)

@nadiasvertex
Copy link
Author

That's very helpful, thanks!

@RaisinTen
Copy link
Member

@addaleax is there a similar way of getting a handle to the set of keys of an object? I'm trying to implement an iterator for Napi::Object at nodejs/node-addon-api#930 but currently, I'm keeping a full Napi::Array of keys inside the iterator, which feels incorrect. 😕

@addaleax
Copy link
Member

addaleax commented Apr 7, 2021

@RaisinTen I don’t think there’s any way to do that, really, no. You can only get a list of all keys at once, unless you use things like for (... in ...).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants