You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
typeExampleModel={fieldA: number;fieldB: string;};/** * Generic type with two type parameters. * Represents a mapping structure for two types (T1 and T2). * In real use cases, 'any' would be replaced with converter functions for each type's field. */typeTypesMapper<T1,T2>={mapT1: {[KinkeyofT1]: any};//mapT1: { [K in keyof Partial<T1>]: any }; // Changing to Partial<T1> removes the error only if at least one mapper has a non-empty T1 (mapperWithEmptyT2 is uncommented)mapT2: {[KinkeyofT2]: any};//mapT2: { [K in keyof Partial<T2>]: any }; // Changing to Partial<T2> removes the error only if at least one mapper has a non-empty T2 (mapperWithEmptyT1 is uncommented)};// Declare mappingsdeclareconstmapperWithEmptyT1: TypesMapper<{},ExampleModel>;declareconstmapperWithEmptyT2: TypesMapper<ExampleModel,{}>;declareconstmapperWithBothTypes: TypesMapper<ExampleModel,ExampleModel>;declareconstmapperWithEmptyTypes: TypesMapper<{},{}>;constMAPPERS={// Uncomment different cases to observe behavior://mapperWithBothTypes, // ✅ Prevents error (because both T1 and T2 are non-empty)//mapperWithEmptyTypes, // ✅ Prevents error (because both T1 and T2 are empty)
mapperWithEmptyT1,// ❌ Causes error (because T1 is empty and T2 is not)
mapperWithEmptyT2,// ❌ Causes error (because T2 is empty and T1 is not)}satisfiesRecord<string,TypesMapper<any,any>>;// Extracting typestypeMapperKeys=keyoftypeofMAPPERS;typeInferT1<KeyextendsMapperKeys>=typeofMAPPERS[Key]extendsTypesMapper<infer T1, infer T2> ? T1 : never;typeInferT2<KeyextendsMapperKeys>=typeofMAPPERS[Key]extendsTypesMapper<infer T1, infer T2> ? T2 : never;exporttypeMapperFromKey<KeyextendsMapperKeys>=TypesMapper<InferT1<Key>,InferT2<Key>>;// Function to access a mapper by nameconstgetMapperByKey=<KeyextendsMapperKeys>(key: Key): MapperFromKey<Key>=>{// ❌ TypeScript error occurs here when MAPPERS contains only objects with one empty generic type and one non-empty generic type// ✅ The error disappears if Partial<T1> or Partial<T2> is used AND at least one mapper has a non-empty type for the partial parameter.returnMAPPERS[key]asMapperFromKey<Key>;};
🙁 Actual behavior
When creating a type with two generic types, where each generic type has a property that is an object containing all the type’s fields with some assigned type, an issue arises when using a Record<string, any> (as a map) that stores these types as values.
If all the mapped values have one generic type as an empty type ({}) and the other as a non-empty type, TypeScript produces a type conversion error when using a generic function to retrieve values from the map with inferred types, based on a key name type (as shown in the steps below).
The error disappears when:
Adding a type where both generic types are empty (mapperWithEmptyTypes).
Adding a type where both generic types are non-empty (mapperWithBothTypes).
Applying the workaround described below using Partial<T1> or Partial<T2>.
When performing the steps below, the error occurs on line 45:
returnMAPPERS[key]asMapperFromKey<Key>;
Error Message:
Conversion of type 'TypesMapper<{}, ExampleModel> | TypesMapper<ExampleModel, {}>' to type 'MapperFromKey' may be a mistake because neither type sufficiently overlaps with the other.
If this was intentional, convert the expression to 'unknown' first.
Type 'TypesMapper<ExampleModel, {}>' is not comparable to type 'MapperFromKey'.
Types of property 'mapT2' are incompatible.
Type '{}' is not comparable to type '{ [K in keyof InferT2]: any; }'.(2352)
Steps that Reproduce the Issue (with the Attached Code)
1. Using only mapperWithEmptyT1 and/or mapperWithEmptyT2 causes an error
Comment mapperWithBothTypes and mapperWithEmptyTypes.
Uncomment mapperWithEmptyT1 only.
Result: ❌ TypeScript error.
Comment mapperWithEmptyT1 and uncomment mapperWithEmptyT2 only.
Result: ❌ TypeScript error.
Uncomment both mapperWithEmptyT1 and mapperWithEmptyT2.
Result: ❌ TypeScript error.
2. Error does not occur when adding mapperWithBothTypes or mapperWithEmptyTypes
Comment all mappers in MAPPERS.
Uncomment mapperWithBothTypes, then uncomment any other combination of mappers (e.g., mapperWithEmptyT1, mapperWithEmptyT2).
Result: ✅ No TypeScript error, regardless of which other mappers are uncommented.
Comment mapperWithBothTypes and instead uncomment mapperWithEmptyTypes.
Result: ✅ No TypeScript error, regardless of which other mappers are uncommented.
3. Workaround: Preventing the error using Partial<T1> or Partial<T2> without mapperWithBothTypes or mapperWithEmptyTypes
Modify TypesMapper to change only mapT1 to Partial<T1>.
Comment all mappers in MAPPERS, except for mapperWithEmptyT1.
Result: ❌ TypeScript error.
Uncomment mapperWithEmptyT2, whether mapperWithEmptyT1 is uncommented or commented.
Result: ✅ No TypeScript error, because mapperWithEmptyT2 has a non-empty T1.
Reverse the change: restore mapT1 and instead change mapT2 to Partial<T2>.
Comment all mappers in MAPPERS, except for mapperWithEmptyT2.
Result: ❌ TypeScript error.
Uncomment mapperWithEmptyT1 while keeping mapperWithEmptyT2 uncommented or commented.
Result: ✅ No TypeScript error, because mapperWithEmptyT1 has a non-empty T2.
Limitations of The Workaround
Using Partial<T1> or Partial<T2> as a workaround does not help when we want to enforce explicit converters for all fields. Since Partial<T> allows missing fields, it weakens type safety and fails to enforce strict mapping of all fields.
Additional Observation
This issue does not occur when using the same generic type with only one type parameter instead of two. In this case, TypeScript correctly infers and applies the expected type without any conversion errors.
TypeScript should work consistently and always allow retrieving values from MAPPERS in the above generic function, without type conversion errors, regardless of the specific generic type combinations used.
The text was updated successfully, but these errors were encountered:
🔎 Search Terms
"empty object", "generic", "map", "infer", "two", "conversion error", "partial"
🕗 Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about generic type inference with empty object types.
⏯ Playground Link
example-two-types-error
example-single-type-no-error
💻 Code
🙁 Actual behavior
When creating a type with two generic types, where each generic type has a property that is an object containing all the type’s fields with some assigned type, an issue arises when using a
Record<string, any>
(as a map) that stores these types as values.If all the mapped values have one generic type as an empty type (
{}
) and the other as a non-empty type, TypeScript produces a type conversion error when using a generic function to retrieve values from the map with inferred types, based on a key name type (as shown in the steps below).The error disappears when:
mapperWithEmptyTypes
).mapperWithBothTypes
).Partial<T1>
orPartial<T2>
.When performing the steps below, the error occurs on line 45:
Steps that Reproduce the Issue (with the Attached Code)
1. Using only
mapperWithEmptyT1
and/ormapperWithEmptyT2
causes an errormapperWithBothTypes
andmapperWithEmptyTypes
.mapperWithEmptyT1
only.mapperWithEmptyT1
and uncommentmapperWithEmptyT2
only.mapperWithEmptyT1
andmapperWithEmptyT2
.2. Error does not occur when adding
mapperWithBothTypes
ormapperWithEmptyTypes
MAPPERS
.mapperWithBothTypes
, then uncomment any other combination of mappers (e.g.,mapperWithEmptyT1
,mapperWithEmptyT2
).mapperWithBothTypes
and instead uncommentmapperWithEmptyTypes
.3. Workaround: Preventing the error using
Partial<T1>
orPartial<T2>
withoutmapperWithBothTypes
ormapperWithEmptyTypes
TypesMapper
to change onlymapT1
toPartial<T1>
.MAPPERS
, except formapperWithEmptyT1
.mapperWithEmptyT2
, whethermapperWithEmptyT1
is uncommented or commented.mapperWithEmptyT2
has a non-emptyT1
.mapT1
and instead changemapT2
toPartial<T2>
.MAPPERS
, except formapperWithEmptyT2
.mapperWithEmptyT1
while keepingmapperWithEmptyT2
uncommented or commented.mapperWithEmptyT1
has a non-emptyT2
.Limitations of The Workaround
Using
Partial<T1>
orPartial<T2>
as a workaround does not help when we want to enforce explicit converters for all fields. SincePartial<T>
allows missing fields, it weakens type safety and fails to enforce strict mapping of all fields.Additional Observation
This issue does not occur when using the same generic type with only one type parameter instead of two. In this case, TypeScript correctly infers and applies the expected type without any conversion errors.
Playground Link: a working example with only one generic type
🙂 Expected behavior
TypeScript should work consistently and always allow retrieving values from MAPPERS in the above generic function, without type conversion errors, regardless of the specific generic type combinations used.
The text was updated successfully, but these errors were encountered: