TS branded string as key of the object
Asked Answered
V

1

8

Imagine, that I have class Foo with string identifier.

class Foo {
    id = '123' as FooId;
}

I try to ensure static typing of it using a brand enum.

enum FooIdBranding {}
type FooId = string & FooIdBranding;

So now, my goal is specific object, where the key is FooId and the value is Foo.

type fooCache = { [key: FooId]: Foo };

Unfortunately, it doesn't work:

TS1023: An index signature parameter type must be 'string' or 'number'

I thought, that the Record is my solution, but is doesn't work too.

type FooCache = Record<FooId, Foo>;

({} as FooCache)['123' as FooId] = new Foo();

TS 7017: Element implicitly has an 'any' type because type Record<FooId, Foo> has no index signature

Is there a correct way in TypeScript to resolve this problem?

Voracious answered 7/5, 2019 at 10:17 Comment(7)
Not possible at the moment, typescript forces idnex parameter to be number or string not even a union of that will do. Work is beeign done in this area, not sure if this exact scenario will be supported: github.com/microsoft/TypeScript/pull/26797Cottonweed
Are there any workarounds?Voracious
Depends how far you want to go with lying to the compiler.. this will enforce the restrictions you want: typescript-play.js.org/#code/…Cottonweed
Looks dynamically =) It feels, like using Map<FooId, Foo> is now the best wayVoracious
It's still using strings .. it just lies to the compiler that the object is only indexable by the unique symbol .. it does the job but is a bit hackish ..Cottonweed
No, it doesn't use string and it allows you to use even real objectsVoracious
And here is with brandingVoracious
T
3

"Symbol and Template String Pattern Index Signatures" were added to TypeScript 4.4, so this is now possible.

type FooId = string & {brand: 'FooId'};
const id1 = '1' as FooId
const id2 = '2' as FooId

class Foo {
  constructor(public id: FooId) {}
}

type fooCache = { [key: FooId]: Foo };

const foo: fooCache = {
  // Type '{ key: Foo; }' is not assignable to type 'fooCache'.
  // Object literal may only specify known properties, and 'key' does not exist in type 'fooCache'.(2322)
  // (property) key: Foo
  key: new Foo(id1)
}

// All good
const bar: fooCache = {
  [id1]: new Foo(id1)
}

Example in the TS playground.

Thanh answered 16/9, 2021 at 7:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.