#Default Type Parameter with Mapped Types

27 messages · Page 1 of 1 (latest)

near aspen
#

I have some mapped types that I'm using to create example objects and am having trouble creating an array of examples and have a hunch that it's because of the default type parameter. I'm not sure exactly how to ask a better question than "how do I resolve the error?", hopefully the code is straightforward enough. I've tried to simplify it as much as I could

lethal mistBOT
#
rjlee0#0

Preview:```ts
...
type ObjExample<T extends Obj = Obj> = {
[P in T as P['name']]: P['type'] extends 'string'
? string
: P['type'] extends A
? AMap<P['type']>
: never;
};

const example: ObjExample<typeof obj> = {
example: {
a: 'a',
},
};

const example2: ObjExample<typeof obj2> = {
example2: 'somestring',
};

const examples: Array<ObjExample> = [example, example2];```

near aspen
#

bump

faint jackal
#

it is in fact because of the default type parameter. const examples: Array<ObjExample> = ... is exactly the same as const examples: Array<ObjExample<Obj>> = ...

#

can you describe your use case/higher-level goal? e.g. i'm wondering why you need to explicitly annotate the type of examples in the first place

faint jackal
# near aspen bump

(for future reference you can use the !helpers command to ping folks if a help thread hasn't gotten any action in a while)

#

is the fact that this is an error intended?

const a: ObjExample<Obj> = example
#

in case it helps, here's a slightly-more-simplified version of your code which i think still reflects the core problem:

lethal mistBOT
#
mkantor#0

Preview:```ts
type Obj = {
name: string;
type: 'string' | 'number';
};

const obj = {
name: 'example',
type: 'number',
} as const satisfies Obj;

const obj2 = {
name: 'example2',
type: 'string',
} as const satisfies Obj;

type ObjExample<T extends Obj> = {
[P in T as P['name']]:
...```

near aspen
#

thanks! to answer the question about explicitly annotating examples, it is because I am passing it elsewhere in the code to functions that accept Array<ObjExample>

faint jackal
#

if the answer is in fact "no", consider something like this. the difference here is the behavior when P['type'] is a union. with your previous conditional type it would fall through to the never case. with this version each member of the union is handled individually (resulting in a union type in the output). if you don't like the TypesByName setup you could alternatively tweak things to use a distributive conditional type inside ObjExample for the same effect

near aspen
#

Your assumption is correct. Do you think my original AMap type might complicate the TypesByName set up a little bit because I want to have a more complex type?

faint jackal
#

yeah, the TypesByName approach assumes your types are always assignable to PropertyKey. if that's not the case you can use the distributive conditional type approach i mentioned instead. give me a minute to attend to something else and then i'll show you an example

#

here you go:

lethal mistBOT
#
mkantor#0

Preview:```ts
...
type TypeFromSpecification<T extends Obj['type']> =
T extends 'string' ? string
: T extends A ? AMap<T>
: never;

type ObjExample<T extends Obj = Obj> = {
[P in T as P['name']]: TypeFromSpecification<P['type']>
};
...```

faint jackal
near aspen
#

that looks so close to what i originally had! I think this is most likely going to resolve my issue. I'll digest the information and the docs you linked and come back and resolve this in a bit. Thanks so much!

near aspen
#

could you help me understand how creating a new type with a type parameter changes it? Was it previously distributing when we don't want it to, or the other way around?

faint jackal
#

other way around

#

maybe this simplified example will help:

lethal mistBOT
#
type Example1<T> = T extends unknown ? { value: T } : never
type Example2<T> = (T & {}) extends unknown ? { value: T } : never

type Output1 = Example1<'a' | 'b'>
//   ^? - type Output1 = {
//       value: "a";
//   } | {
//       value: "b";
//   }
type Output2 = Example2<'a' | 'b'>
//   ^? - type Output2 = {
//       value: "a" | "b";
//   }
faint jackal
#

distribution only happens when the first operand of a conditional type is a bare type parameter like T extends …. not in any other case

#

in your code it was P['type'], which won't distribute

#

which meant you were checking if 'string' | A extends 'string' (which it doesn't) and 'string' | A extends A (which it doesn't) and were falling through to the never case. you can add something like type X = ObjExample<Obj> to your original code and hover over X to see this happening

#

with the distributive setup, it ends up separately checking if 'string' extends 'string' (it does) and A extends A (it does) and then the results get unioned back together

near aspen
#

that was a very good explanation, I completely understand now. Thank you very much once again!