3

Basically what I'm trying to achieve is prevention of certain properties of an existing object to be passed as arguments to an interface. I believe this is possible through some combination of conditional types, but having a hard time working out the right combination.

export interface Dictionary<T> {
  [index: string]: T;
}

type StandardContext = {
    myKeyOne?: string;
    myKeyTwo?: number;
}

type NonStandardContext = Exclude<Dictionary<any>, StandardContext>

const standard: StandardContext = {
    myKeyOne: '2', // ok!
}

const nonStandardError: NonStandardContext = {
    myKeyOne: '2' // should error (good so far)
}

const nonStandardGood: NonStandardContext = {
    foo: '2' // should not error, but it does!
}

Playground link

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Zak Henry
  • 2,075
  • 2
  • 25
  • 36

1 Answers1

3

A union between Dictionary<any> and {[P in keyof StandardContext]?: never} does it:

type NonStandardContext = Dictionary<any> & { [P in keyof StandardContext]?: never };

On the playground

You don't need the ? on that second part with your example StandardContext, but that's only because your example StandardContext doesn't have any required properties. If it did have some, without the ? the type above would be impossible to satisfy since it would require you to have a property with type never! :-) (Example)

(Thank you kaya3 for pointing out the need for ?!)


I have to admit not fully understanding why Exclude<Dictionary<any>, StandardContext> doesn't work. :-) But I think Exclude only works for types that don't use index signatures. I could be wrong about why. I suspect that from reading this answer which says that Exclude is, roughly speaking, the following (except this is only for string keys, not string and number):

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^

Notice that last part excluding index keys.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    I'd suggest `{ [P in keyof StandardContext]?: never }` for this, so that any properties required in `StandardContext` aren't required to exist (with values of type `never`) in `NonStandardContext`. – kaya3 Feb 18 '20 at 12:30
  • 1
    The OP's example doesn't have any required properties; both properties of `StandardContext` are optional, so the mapped type's properties are optional too. If `StandardContext` has any required properties, then they will be required in the mapped type too. See [Playground Link](http://www.typescriptlang.org/play/#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgEQWxgQiUygE8AeAFQD44BvAKDnbgG1kATUALjgBnGFGQBzALqDaAbhYBfFixiUweAMoxMSHhR4BhMqhDwAvMzYcowAI4BXBDZ6CRYpOPlKVavADkyLR09KENjUHMCIhIyChodSkYAMmYuAAVEJDgAa2BKCHQ4IN19IxQI6TgkYAA3NDgFeRYAG2B4TEEApGKQsPLTOAsmOHQICEEAcgAjCgmG+SA) for an example. – kaya3 Feb 18 '20 at 12:39
  • @kaya3 - Thank you, I get it now! – T.J. Crowder Feb 18 '20 at 12:41
  • @kaya3 - Fixed, and thank you again not only for the comment, but the explanation. – T.J. Crowder Feb 18 '20 at 12:44