Skip to content

[Feature Request] Allow Class Index Signatures to be mapped #49509

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

Open
5 tasks done
eliasm307 opened this issue Jun 12, 2022 · 5 comments
Open
5 tasks done

[Feature Request] Allow Class Index Signatures to be mapped #49509

eliasm307 opened this issue Jun 12, 2022 · 5 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@eliasm307
Copy link

eliasm307 commented Jun 12, 2022

Suggestion

🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

"mapped class members", "mapped class index signature"

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

We can define index signatures for classes, ie https://www.typescriptlang.org/docs/handbook/2/classes.html#index-signatures, however, these signatures can not be mapped from an existing type, as can be done for object type aliases. It would be useful if similar behavior existed for classes to allow members of a class to be defined based on an existing type.

📃 Motivating Example

I am working on typing some existing JS code (can't be changed) that has classes that take in a config in the constructor and then spreads the config into the instance, which means I need to define a type for the config, then define each property of the config as a declare member on the class to get the typing to work, eg:

type Config = {
    func1?: () => void,
    string1?: string
}

class Foo {
    declare func1?: () => void;
    declare string1?: string;

    constructor(config: Config) {
        Object.assign(this, config)
    }
}

However, this is quite tedious and if the configs are significant or have external dependencies then it can be easy to have the Config type out of sync with the manually declared members.

I have made some code examples of what I think would be good here

ie the following should work in terms of showing intellisense for and allowing usage of the mapped members from Config with correct types on class instances:

type Config = {
    func1?: () => void,
    string1?: string
}

class Foo {
    [key in keyof Config]: Config[key]

    constructor(config: Config) {
        Object.assign(this, config)
    }

    method1() {}
} 

new Foo().func1.?() // should work

Related to

@RyanCavanaugh
Copy link
Member

This is possible today, it just requires a weirder syntax

type Tmp = { [key in keyof Config]: Config[key]; }
interface Foo extends Tmp { }

// OK
new Foo(null as any).func1?.() 

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Jun 13, 2022
@eliasm307
Copy link
Author

@RyanCavanaugh cool didnt know about that, the syntax does feel a bit hacky but atleast I think I can use this for now

@VanillaMaster
Copy link

VanillaMaster commented Mar 26, 2024

its only partially possible, if class uses generics there is no viable soultion

class A<const T extends {[key: PropertyKey]: any}> {
    constructor(props: T) {
        Object.assign(this, props);
    }

    [K in keyof T]: T[K]
}

const a = new A({
    key1: 42
});

console.log(a.key1)

there is solution:

class __A {
    constructor(props: {[key: PropertyKey]: any}) {
        Object.assign(this, props);
    }

}

interface __AConstructor {
    new <const T extends {[key: PropertyKey]: any}>(props: T): __A & T;
}

const A = __A as __AConstructor;

const a = new A({
    key1: 42
});

console.log(a.key1)

@MrOxMasTer
Copy link

It's nice to see the topic for 2022, to a problem that has not been solved. I can't just call Object.assign(this, ...) and get the types in the class, because the class can't be extended from the object. Although it's very funny, considering that in js - literally everything is an object...

@jcalz jcalz mentioned this issue Mar 1, 2025
6 tasks
@thesoftwarephilosopher
Copy link

Would this be difficult to implement?

class Foo {

  // this breaks for two reasons:
  // 1. mapped fields doesn't exist yet
  // 2. afaik this[K] doesn't work even in non-mapped fields like: qux = {} as { [K as keyof this]: this[K] }
  { [K in keyof this as `$${K & string}`]: this[K] };

  bar = 1;

  constructor() {
    for (const [key, value] of Object.entries(this)) {
      Object.defineProperty(this, '$' + key, { value });
    }

    this.$bar // now we can use this in constructors
  }

  qux() {
    this.$bar // and methods
  }

}

const foo = new Foo();
foo.$bar // and externally

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants