Home > Back-end >  Passing property names to type properties
Passing property names to type properties

Time:10-13

I'm trying to type a configuration object whose properties have arbitrary names. Each arbitrary key name must have a value which is a typed object with some defined property that has, as a value, one of the key names of the parent object.

Here is what i came up with :

//parent type
type ParentType<KeyNames extends string> = {
  [k in KeyNames]: ChildType<KeyNames>;
};

//child type
interface ChildType<KeyNames extends string>  {
  oneOfKeys : KeyNames
}

The problem is that it's working in the opposite direction i was hoping for.


function testFunc<Keynames extends string>(foo:ParentType<Keynames>){
}

//testFunc is infered as testFunc<"bar" | "baz" | "foo">
//i would like for it to be infered as testFunc<"foo" | "bar" | "bal">
testFunc({
  foo: {
    oneOfKeys: "bar"
  },
  bar : {
    oneOfKeys: "baz"
  },
  bal : { //TS is complaining about the "bal" key
    oneOfKeys: "foo"
  }
})

I wanted the compiler to check that oneOfkeys what one of the key of the parent object, but instead it checks that the parent key is one of the values of oneOfKeys. It's like the generic's inference was working bottom-up when i want it to work top-down.

I have tried various other approaches which don't work at all.

CodePudding user response:

Passing the whole object to ParentType means the compiler won't erroneously infer the keys wrong:

type ParentType<O> = {
  [k in keyof O]: ChildType<O>;
};

//child type
interface ChildType<O>  {
  oneOfKeys : keyof O
}

function testFunc<O>(foo:ParentType<O>){
}


testFunc({
  foo: {
    oneOfKeys: "bar"
  },
  bar : {
    oneOfKeys: "baz" // Error
  },
  bal : {
    oneOfKeys: "foo"
  }
})

CodePudding user response:

FIrst of all you need to infer each key and value of function argument:

type Value<Prop extends PropertyKey> = { oneOfKeys: Prop }

function testFunc<
  ParentKey extends PropertyKey,
  Item extends Value<ParentKey>,
  Config extends Record<ParentKey, Item>
>(foo: Config) {}

ParentKey infers all parent keys.

Item infers all nested objects: {oneOfKeys: string}

Config infers whole config.

More about infering arguments you can find in my article

Then, you need iterate through infered Config and find disallowed values:

type Validation<Config extends Record<PropertyKey, Value<PropertyKey>>> = {
  [Prop in keyof Config]: Config[Prop]['oneOfKeys'] extends keyof Config ? Config[Prop] : Value<never>
}

type Test = Validation<{
  foo: {
    oneOfKeys: "bar"
  },
  bar: {
    oneOfKeys: "baz"
  },
  bal: { //TS is complaining about the "bal" key
    oneOfKeys: "foo"
  }
}>

Validation - iterates throught every Config prop and checks whether Config[Prop]['oneOfKeys'] extends any parent key. If yes - leavs value as it is, otherwise replace value with never.

WHole code:


type Validation<Config extends Record<PropertyKey, Value<PropertyKey>>> = {
  [Prop in keyof Config]: Config[Prop]['oneOfKeys'] extends keyof Config ? Config[Prop] : Value<never>
}

type Test = Validation<{
  foo: {
    oneOfKeys: "bar"
  },
  bar: {
    oneOfKeys: "baz"
  },
  bal: { 
    oneOfKeys: "foo"
  }
}>

type Value<Prop extends PropertyKey> = { oneOfKeys: Prop }

function testFunc<
  ParentKey extends PropertyKey,
  Item extends Value<ParentKey>,
  Config extends Record<ParentKey, Item>
>(foo: Validation<Config>) {}

testFunc({
  foo: {
    oneOfKeys: "bar"
  },
  bar: {
    oneOfKeys: "baz" // error
  },
  bal: { 
    oneOfKeys: "foo"
  }
})

Playground

  • Related