1

I had an object type declared using keys from an enum all set as strings:

enum MyProperties {
  PROP_A = 'propA',
  PROP_B = 'propB',
  PROP_C = 'propC'
}

type Foo {
  [value in MyProperties]: string;
}

How can I declare one of the properties in Foo as a number while still ensuring all of its keys are in MyProperties?

i.e.

type Foo {
  propA: string;
  propB: string;
  propC: number;
}
2
  • You can use an utility type: Record<MyProperties, string | number>. type Foo = Record<MyProperties, string | number>; Commented Oct 29, 2020 at 19:01
  • I don't think this is quite what I'm looking for because I want to enforce string for propA and propB, number for propC. Commented Oct 29, 2020 at 19:32

4 Answers 4

1

If you want a specific property to be typed as a number then you can do:

type Foo<T extends MyProperties> = {
  [value in MyProperties]: T extends value ? number : string;
}

Or you can use more generic solution:

enum MyProperties {
  PROP_A = 'propA',
  PROP_B = 'propB',
  PROP_C = 'propC'
}

type OneOf<T extends {
    [key: string]: any;
}, R, P extends keyof T = keyof T> = P extends keyof T ? {
   [K in P]: R;
} & {
    [K in keyof Omit<T, P>]: T[K];
} : never

type Foo = OneOf<{
  [value in MyProperties]: string;
}, number>

const k: Foo = {
   propA: "",
   propB: 3,
   propC: "",
}
Sign up to request clarification or add additional context in comments.

Comments

1

Decided to just go with:

enum MyProperties {
  PROP_A = 'propA',
  PROP_B = 'propB',
  PROP_C = 'propC'
}

type Foo {
  [MyProperties.PROP_A]: string;
  [MyProperties.PROP_B]: string;
  [MyProperties.PROP_C]: number;
}

Was hoping to find a slicker yet simple way to do this. With such a small set of properties, though, just being explicit seems fine.

Comments

1

You want to define a type for an object which has keys matching every value in an enum and values that are different (string and number in your example). You want to enforce that every key matches the appropriate value, while also requiring that all keys have some value.

This can be a bit confusing since we are trying to enforce typings on a type rather than an object. But there are a few things we can do.

  1. We can separate the properties into groups by value type. Since we only have two value types here, we can define one as the leftovers from the other.
type NumberProperties = MyProperties.PROP_C;

type StringProperties = Exclude<MyProperties, NumberProperties>

type Foo = {
    [K in NumberProperties]: number;
} & {
    [K in StringProperties]: string;
}

Link

  1. We can use a helper type MissingKeys to check at compile time that we haven't forgotten anything. We do need to check it ourselves, as no error is thrown.
enum MyProperties {
  PROP_A = 'propA',
  PROP_B = 'propB',
  PROP_C = 'propC'
}

type Foo = {
  [MyProperties.PROP_B]: string;
  [MyProperties.PROP_C]: number;
}

type MissingKeys = Exclude<MyProperties, keyof Foo> // evaluates to MyProperties.PROP_A

Link

  1. My Favorite. We can add an declaration to our type that states that any missing properties must be set in objects implementing Foo. Let's give it an illogical value so that we can notice that something is missing from Foo and adjust Foo accordingly. I like this one because we get a big red underline so it's obvious that the type is wrong.
type DefinedFoo = {
  [MyProperties.PROP_B]: string;
  [MyProperties.PROP_C]: number;
}

type MissingKeys = Exclude<MyProperties, keyof DefinedFoo> // evaluates to MyProperties.PROP_A

type Foo = DefinedFoo & {
    [K in MissingKeys]-?: "missing property definition" 
}

const fooImplementation1: Foo = {
    [MyProperties.PROP_B]: "b",
    [MyProperties.PROP_C]: 0,
}
// gives an error: Property 'propA' is missing...

const fooImplementation2: Foo = {
    [MyProperties.PROP_A]: "a",
    [MyProperties.PROP_B]: "b",
    [MyProperties.PROP_C]: 0,
}
// gives an error: Type of computed property's value is '"a"', which is not assignable to type '"missing property definition"'

Link

Comments

1

As easy as that

export type = Foo {
  [value in keyof typeof MyProperties]: string;
}

4 Comments

That won't work because I want one of the properties to be a number, the rest strings. Sorry if that wasn't clear in the initial question.
Can you clarify your question in a description, its not particularly clear what exactly you want
so then replace string with string | number
I don't think this is quite what I'm looking for because I want to enforce string for propA and propB, number for propC. For example, I don't want to allow propA to be a number.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.