Home > database >  Constructors as object keys
Constructors as object keys

Time:05-19

I'm trying to construct a map of types and callbacks so I started with an structure like this:

type Instantiable<T = unknown> = new (...args: any[]) => T;
type SingleParamFn<TInput = unknown, TOuput = unknown> = (arg: TInput) => TOuput;

type TypesRecord<T> = { type: Instantiable<T>, callback: SingleParamFn<T> };
type TypesMap = TypesRecord[];   // This is the structure

An example of this:

const myMap: TypesMap = [{ type: Date, callback: (value) => 'this is a Date' }];

This seems works as expected but I realised it is a bit inefficient as you need to loop the array to find the type you want.

I started to play with having the types as object keys and it seems to work at least in Google Chrome:

class MyFancyObject {}
class MyOtherObject {}

const map = {
   [Date]: () => 'this is a date',
   [MyFancyObject]: () => 'this is a fancy object',
   [MyOtherObject]: () => 'this is another object'
};

console.log(map[Date]());           // 'this is a date'
console.log(map[MyFancyObject]());  // 'this is a fancy object'
console.log(map[MyOtherObject]());  // 'this is another object'

const obj = new MyFancyObject();
console.log(map[obj.constructor]());           // 'this is a fancy object'
console.log(map[(new Date()).constructor]());  // 'this is a date'

I've been checking some documentation and it seems like an object's key can only be an string or a Symbol so I checked what constitutes a Symbol and it seems to be a bit vage as it is defined as a built-in object whose constructor returns a symbol primitive that's guaranteed to be unique and an object's constructor is (to the best of my knowledge), unique.

Given all of this, is using the constructors as object keys a reliable approach? If so, how can I define the typescript type for such object? If it isn't, are there other options (like the Map object)?

CodePudding user response:

Given all of this, is using the constructors as object keys a reliable approach?

No. As you said, an object's keys can only be strings or Symbols. A Symbol is a primitive you get from a property on Symbol (for well-known, specification-defined Symbols) or by calling Symbol() or Symbol.for("..."). Constructor functions are not symbols, they're functions.

Your map object's keys are strings. When you use a computed property name like [MyFancyObject], if the value you provide is not a string or a Symbol (in your case, it's a function), it's converted to string implicitly as though you did String(MyFancyObject). You can see that here:

class MyFancyObject {}
class MyOtherObject {}

const map = {
   [Date]: () => 'this is a date',
   [MyFancyObject]: () => 'this is a fancy object',
   [MyOtherObject]: () => 'this is another object'
};

console.log(Object.keys(map));
console.log(map[MyFancyObject]());            // "this is a fancy object"
console.log(map["class MyFancyObject {}"]()); // also "this is a fancy object"

As you can see, the key you're thinking of as MyFancyObject is actually the string "class MyFancyObject {}".

You can use constructor functions as keys in a Map, though:

class MyFancyObject {}
class MyOtherObject {}

const map = new Map([
   [Date, () => 'this is a date'],
   [MyFancyObject, () => 'this is a fancy object'],
   [MyOtherObject, () => 'this is another object'],
]);

console.log(map.get(MyFancyObject)());

In that, the keys really are the constructor functions, not strings derived from them.

  • Related