I have a class that take an object as a constructor argument, and I want to enforce a method on the class to accept a very similar object. The objects have arbitrary keys.
For example, constructing with this object:
{
foo: { type: 'A' },
bar: { type: 'B'}
}
I will want the method to only accept objects of a similar form, i.e. has the same keys and for each key the value type is compatible with the initial object. like:
{
foo: SomeARelatedThing,
bar: SomeBRelatedThing
}
I've got a workaround in place where I can at least enforce the same keys, and then do lots of type checking (good to do anyway!) to make sure that the values actually match up.
Here's a contrived example from my use case:
type TypeName = 'A' | 'B' | 'C' // ...
class Action<K extends TypeName> {
type: K
constructor(type: K) { this.type = type }
}
type AnyAction = Action<'A'> | Action<'B'> | Action<'C'> // ...
type AProp = { type: 'A' }
type BProp = { type: 'B' }
type CProp = { type: 'C' }
type AnyProp = AProp | BProp | CProp // ...
type PropMap<K extends string> = Record<K, AnyProp>
type ActionMap<K extends string> = Record<K, AnyAction>
class Thing<K extends string> {
props: PropMap<K>
constructor(props: PropMap<K>) { this.props = props }
myMethod<>(actions: ActionMap<K>) { /* ... */ }
}
// type = Thing<'foo' | 'bar'>
const thing = new Thing({
foo: { type: 'A' },
bar: { type: 'B'}
})
// the keys are enforced, but how can the values of foo and bar be enforced, too
thing.myMethod({
foo: new Action('A'),
bar: new Action('B'),
})
I think I would want something more like a type equal to Thing<{foo: 'A', bar: 'B'}>, but I don't know how to conditionally compute that from a PropMap-like input to the Thing constructor, or even if I did, then how would I compute the correct ActionMap-like type.
MyMethod actually accepts a Partial<ActionMap<K>> but I don't think that that should matter for what I am asking.
