I want to achieve a type that extracts the inner-type of all properties being a Model<infer T>, but leaves others untouched; something like this:
MyType<{ a: number, b: Model<string> }> // => { a: number, b: string }
I thought it would be as simple as this:
type MyType<T> = {
[P in keyof T]: T[P] extends Model<infer R> ? R : T[P];
};
but when testing it like this:
const fnForTesting = <T>(obj: T): MyType<T> => {
return null!;
};
const result = fnForTesting({
name: "Ann",
age: new Model<number>(),
});
const a = result.name; // unknown :( -> should be string
const b = result.age; // number -> works as expected
Does anybody know why "normal" properties are not recognized correctly, while the Model properties are? And how do I fix this? Thanks in advance!
Since your Model class is empty,
Model<T>is the same as{}(the empty object type). So when you useT[P] extends Model<infer R>, TypeScript cannot inferRproperly, so it usesunknown. The reason why TypeScript doesn't back off and fallback toT[P]is because all types are assignable to{}except fornullandundefined. Basically,Now notice that when I add a property to your Model class, to make it non-empty, your original code works:
This is because there is now a clear, structural difference between a string (or date) and a
Model<T>, and TypeScript can now check ifT[P]is a model and infer the type.Playground (changed Model)
Filly's code works you're essentially checking if an empty object is assignable to a string (or date), which is invalid. This acts as a guard before trying to infer the inner type of the model.
That means
Model<unknown> extends T[P]only triggers ifT[P]is an empty object orModel<T>.Playground (Filly's solution)
(you don't need Filly's solution if your Model class is structurally different from the
{}type)