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
#Default Type Parameter with Mapped Types
27 messages · Page 1 of 1 (latest)
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];```
bump
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
(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:
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']]:
...```
You can choose specific lines to embed by selecting them before copying the link.
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>
what about this? i suspect the answer is "no", but i don't want to make assumptions. also if you can share more info about the use case it might still help
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
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?
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:
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']>
};
...```
that may look like it should be equivalent to what you wrote originally, but it's not. when a conditional type checks against a naked type parameter on its left-hand side the condition is "distributed" over unions. you can read more about this in the handbook: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
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!
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?
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";
// }
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
that was a very good explanation, I completely understand now. Thank you very much once again!