How would I implement this type in Typescript (from Flow?) [duplicate] - javascript

Is there a way to extend the built-in Record (or a { [key:string]: string } interface) where you also define some fixed keys and their types?
Let's say we have this:
const otherValues = {
some: 'some',
other: 'other',
values: 'values',
}
const composedDictionary = {
id: 1,
...otherValues
}
I want to define an interface for composedDictionary where id is typed as number (and number only) and everything else as string.
I've tried this:
interface ExtendedRecord extends Record<string, string> {
id: number;
}
and this:
interface MyDictionary {
[key: string]: string;
id: number;
}
Both fail with:
Property 'id' of type 'number' is not assignable to string index type 'string'
Any ideas?

Ideally the index signature should reflect any possible indexing operation result. If you access composedDictionary with an uncheckable string key the result might be number if that string is actually 'id' (eg: composedDictionary['id' as string], the typings would say this is string but at runtime it turns out to be number). This is why the type system is fighting you on this, this is an inconsistent type.
You can define your index to be consistent will all properties:
interface MyDictionary {
[key: string]: string | number;
id: number;
}
There is a loophole to the checks typescript does for index and property consistency. That loop hole is intersection types:
type MyDictionary = Record<string, string> & {
id: number
}
const composedDictionary: MyDictionary = Object.assign({
id: 1,
}, {
...otherValues
});
The compiler will still fight you on assignment, and the only way to create such an inconsistent object in the type system is by using Object.assign

As the other answer said, TypeScript does not support a type in which some properties are exceptions to the index signature... and thus there is no way to represent your MyDictionary as a consistent concrete type. The inconsistent-intersection solution ({[k: string]: string]} & {id: number}) happens to work on property reads, but is difficult to use with property writes.
There was an old suggestion to allow "rest" index signatures where you can say that an index signature is supposed to represent all properties except for those specified.
There's also a more recent (but possibly shelved) pair of enhancements implementing negated types and arbitrary key types for index signatures, which would allow you to represent such exception/default index signature properties as something like { id: number; [k: string & not "id"]: string }. But that doesn't compile yet (TS3.5) and may never compile, so this is just a dream for now.
So you can't represent MyDictionary as a concrete type. You can, however, represent it as a generic constraint. Using it suddenly requires that all your previously concrete functions must become generic functions, and your previously concrete values must become outputs of generic functions. So it might be too much machinery than it's worth. Still, let's see how to do it:
type MyDictionary<T extends object> = { id: number } & {
[K in keyof T]: K extends "id" ? number : string
};
In this case, MyDictionary<T> takes a candidate type T and transforms it into a version which matches your desired MyDictionary type. Then we use the following helper function to check if something matches:
const asMyDictionary = <T extends MyDictionary<T>>(dict: T) => dict;
Notice the self-referencing constraint T extends MyDictionary<T>. So here's your use case, and how it works:
const otherValues = {
some: "some",
other: "other",
values: "values"
};
const composedDictionary = asMyDictionary({
id: 1,
...otherValues
}); // okay
That compiles with no errors, because the parameter to asMyDictionary() is a valid MyDictionary<T>. Now let's see some failures:
const invalidDictionary = asMyDictionary({
id: 1,
oops: 2 // error! number is not a string
})
const invalidDictionary2 = asMyDictionary({
some: "some" // error! property id is missing
})
const invalidDictionary3 = asMyDictionary({
id: "oops", // error! string is not a number
some: "some"
})
The compiler catches each of those mistakes and tells you where the problem is.
So, this is the closest I can get to what you want in as of TS3.5. Okay, hope that helps; good luck!
Link to code

Related

Get Javascript interface keys as string (like an Enum)

My request is actually really simple but I have found almost nothing of the sort online. Picture this, you have an interface like so:
interface MyInterface: {
propertyA: number;
propertyB: string;
propertyC: boolean;
}
Now you want to use the name of a field to access its value dynamically so you do something like object['propertyA'], where object is of type MyInterface.
However, passing 'propertyA' as a raw string is impractical and dangerous because if you rename it or change the interface the code won't work. (And it doesn't get picked up by typescript but I'm not sure this is possible anyway).
Ideally, I would like to do something like this MyInterface.propertyA, I was saddened that this doesn't seem to exist, I don't see any reason why it shouldn't.
And now before you say that's what object.propertyA is for, I have simplified the concept to make it easy to understand but the use case is not exactly this. I might pass fields of an interface as strings from react components within the JSX to make use of a reusable method or I could be checking whether a string matches a subset of keys in an interface to display errors accordingly.
Autogenerating an enum from the interface/class is not a bad solution although not ideal as it still incurs an overhead but as long as it doesn't need to be updated each time the interface changes and it doesn't depend on the type of the interface's properties then that's not bad.
Interfaces don't exist at runtime, so there's no way to get the property names of an interface as runtime values. What you can do, though, is go the other way: Have a "model" object:
const myInterfaceModel = {
propertyA: 42,
propertyB: "x",
propertyC: true,
};
Then define the interface using that object:
type MyInterface = typeof myInterfaceModel;
That defines MyInterface just as it is in your question.
Then you can create an object that has the property names as strings, like this:
const myInterfaceKeys = Object.fromEntries(
Object.keys(myInterfaceModel).map((key) => [key, key])
);
Now, if we just did that, myInterfaceKeys would be (effectively) Record<string, string>, which isn't useful for devex. But we can do better by using a mapped type on it:
type KeyNames<T> = {
[Key in keyof T]: Key;
};
// ...
const myInterfaceKeys = Object.fromEntries(
Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;
Now, its type matches its runtime value, and we get useful autocompletion:
Example usage (just a basic function, but could as easily be a React component):
function example(obj: MyInterface, key: keyof MyInterface) {
console.log(obj[key]);
}
declare let exampleObject: MyInterface;
example(exampleObject, myInterfaceKeys.propertyA);
Here's that all together (playground link):
type KeyNames<T> = {
[Key in keyof T]: Key;
};
const myInterfaceModel = {
propertyA: 42,
propertyB: "x",
propertyC: true,
};
type MyInterface = typeof myInterfaceModel;
// ^? type MyInterface = { propertyA: number, propertyB: string; propertyC : boolean }
const myInterfaceKeys = Object.fromEntries(
Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;
myInterfaceKeys
// ^−−−− Type a period after this to see the autocompletion
Usually I'd just use string literals (example(exampleObject, "propertyA")) since the IDE will offer the appropriate choices, and since keyof MyInterface is restrictive, if you remove a property, any code that referenced it with a string literal would stop compiling. But if you want that object with the keys on it, you can do the above to achieve that. (Also, I just tried, and if you use your object of keys, renaming a property works throughout, whereas the string literals didn't.)
Are you looking for something like this?
enum Direction {
Up = "u",
Down = "d",
Left = "l",
Right = "r",
}
interface MyDirection {
[Direction.Up]:string;
[Direction.Down]:number;
}
const obj:MyDirection = {
[Direction.Up]: 'up',
[Direction.Down]:12,
}
console.log(obj[Direction.Down], Direction.Down)

Is there a way to show a TypeScript error when an unknown property is passed in an object to function? [duplicate]

Here is my code
async getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
class User {
id: number;
name: string;
}
class GetAllUserData{
id: number;
}
getAll function returns User[], and each element of array has the name property, even if its return type is GetAllUserData[].
I want to know if it is possible "out of the box" in TypeScript to restrict an object only to properties specified by its type.
I figured out a way, using built-in types available since TypeScript version 3, to ensure that an object passed to a function does not contain any properties beyond those in a specified (object) type.
// First, define a type that, when passed a union of keys, creates an object which
// cannot have those properties. I couldn't find a way to use this type directly,
// but it can be used with the below type.
type Impossible<K extends keyof any> = {
[P in K]: never;
};
// The secret sauce! Provide it the type that contains only the properties you want,
// and then a type that extends that type, based on what the caller provided
// using generics.
type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;
// Now let's try it out!
// A simple type to work with
interface Animal {
name: string;
noise: string;
}
// This works, but I agree the type is pretty gross. But it might make it easier
// to see how this works.
//
// Whatever is passed to the function has to at least satisfy the Animal contract
// (the <T extends Animal> part), but then we intersect whatever type that is
// with an Impossible type which has only the keys on it that don't exist on Animal.
// The result is that the keys that don't exist on Animal have a type of `never`,
// so if they exist, they get flagged as an error!
function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
// This is the best I could reduce it to, using the NoExtraProperties<> type above.
// Functions which use this technique will need to all follow this formula.
function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
// It works for variables defined as the type
const okay: NoExtraProperties<Animal> = {
name: 'Dog',
noise: 'bark',
};
const wrong1: NoExtraProperties<Animal> = {
name: 'Cat',
noise: 'meow'
betterThanDogs: false, // look, an error!
};
// What happens if we try to bypass the "Excess Properties Check" done on object literals
// by assigning it to a variable with no explicit type?
const wrong2 = {
name: 'Rat',
noise: 'squeak',
idealScenarios: ['labs', 'storehouses'],
invalid: true,
};
thisWorks(okay);
thisWorks(wrong1); // doesn't flag it as an error here, but does flag it above
thisWorks(wrong2); // yay, an error!
thisIsAsGoodAsICanGetIt(okay);
thisIsAsGoodAsICanGetIt(wrong1); // no error, but error above, so okay
thisIsAsGoodAsICanGetIt(wrong2); // yay, an error!
Typescript can't restrict extra properties
Unfortunately this isn't currently possible in Typescript, and somewhat contradicts the shape nature of TS type checking.
Answers in this thread that relay on the generic NoExtraProperties are very elegant, but unfortunately they are unreliable, and can result in difficult to detect bugs.
I'll demonstrate with GregL's answer.
// From GregL's answer
type Impossible<K extends keyof any> = {
[P in K]: never;
};
type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;
interface Animal {
name: string;
noise: string;
}
function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
const wrong2 = {
name: 'Rat',
noise: 'squeak',
idealScenarios: ['labs', 'storehouses'],
invalid: true,
};
thisWorks(wrong2); // yay, an error!
thisIsAsGoodAsICanGetIt(wrong2); // yay, an error!
This works if at the time of passing an object to thisWorks/thisIsAsGoodAsICanGet TS recognizes that the object has extra properties. But in TS if it's not an object literal, a value can always have extra properties:
const fun = (animal:Animal) =>{
thisWorks(animal) // No Error
thisIsAsGoodAsICanGetIt(animal) // No Error
}
fun(wrong2) // No Error
So, inside thisWorks/thisIsAsGoodAsICanGetIt you can't trust that the animal param doesn't have extra properties.
Solution
Simply use pick (Lodash, Ramda, Underscore).
interface Narrow {
a: "alpha"
}
interface Wide extends Narrow{
b: "beta"
}
const fun = (obj: Narrow) => {
const narrowKeys = ["a"]
const narrow = pick(obj, narrowKeys)
// Even if obj has extra properties, we know for sure that narrow doesn't
...
}
Typescript uses structural typing instead of nominal typing to determine type equality. This means that a type definition is really just the "shape" of a object of that type. It also means that any types which shares a subset of another type's "shape" is implicitly a subclass of that type.
In your example, because a User has all of the properties of GetAllUserData, User is implicitly a subtype of GetAllUserData.
To solve this problem, you can add a dummy property specifically to make your two classes different from one another. This type of property is called a discriminator. (Search for discriminated union here).
Your code might look like this. The name of the discriminator property is not important. Doing this will produce a type check error like you want.
async function getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
class User {
discriminator: 'User';
id: number;
name: string;
}
class GetAllUserData {
discriminator: 'GetAllUserData';
id: number;
}
I don't think it's possible with the code structure you have. Typescript does have excess property checks, which sounds like what you're after, but they only work for object literals. From those docs:
Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments.
But returned variables will not undergo that check. So while
function returnUserData(): GetAllUserData {
return {id: 1, name: "John Doe"};
}
Will produce an error "Object literal may only specify known properties", the code:
function returnUserData(): GetAllUserData {
const user = {id: 1, name: "John Doe"};
return user;
}
Will not produce any errors, since it returns a variable and not the object literal itself.
So for your case, since getAll isn't returning a literal, typescript won't do the excess property check.
Final Note: There is an issue for "Exact Types" which if ever implemented would allow for the kind of check you want here.
Following up on GregL's answer, I'd like to add support for arrays and make sure that if you've got one, all the objects in the array have no extra props:
type Impossible<K extends keyof any> = {
[P in K]: never;
};
export type NoExtraProperties<T, U extends T = T> = U extends Array<infer V>
? NoExtraProperties<V>[]
: U & Impossible<Exclude<keyof U, keyof T>>;
Note: The type recursion is only possible if you've got TS 3.7 (included) or above.
The accepted answer, with a discriminator, is right. TypeScript uses structural typing instead of nominal typing. It means that the transpiler will check to see if the structure match. Since both classes (could be interface or type) has id of type number it matches, hence interchangeable (this is true one side since User is having more properties.
While this might be good enough, the issue is that at runtime the returned data from your method getAll will contains the name property. Returning more might not be an issue, but could be if you are sending back the information somewhere else.
If you want to restrict the data to only what is defined in the class (interface or type), you have to build or spread a new object manually. Here is how it can look for your example:
function dbQuery(): User[] {
return [];
}
function getAll(): GetAllUserData[] {
const users: User[] = dbQuery();
const usersIDs: GetAllUserData[] = users.map(({id}) => ({id}));
return usersIDs;
}
class User {
id: number;
name: string;
}
class GetAllUserData {
id: number;
}
Without going with the runtime approach of pruning the fields, you could indicate to TypeScript that both classes are different with a private field. The code below won't let you return a User when the return type is set to GetAllUserData
class User {
id: number;
name: string;
}
class GetAllUserData {
private _unique: void;
id: number;
}
function getAll(): GetAllUserData[] {
return dbQuery(); // Doesn't compile here!
}
I found this another workaround:
function exactMatch<A extends C, B extends A, C = B>() { }
const a = { a: "", b: "", c: "" }
const b = { a: "", b: "", c: "", e: "" }
exactMatch<typeof a, typeof b>() //invalid
const c = { e: "", }
exactMatch<typeof a, typeof c>() //invalid
const d = { a: "", b: "", c: "" }
exactMatch<typeof a, typeof d>() //valid
const e = {...a,...c}
exactMatch<typeof b, typeof e>() //valid
const f = {...a,...d}
exactMatch<typeof b, typeof f>() //invalid
See the original Post
Link to Playground
As an option, you can go with a hack:
const dbQuery = () => [ { name: '', id: 1}];
async function getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
type Exact<T> = {[k: string | number | symbol]: never} & T
type User = {
id: number;
name: string;
}
type GetAllUserData = Exact<{
id: number;
}>
Error this produces:
Type '{ name: string; id: number; }[]' is not assignable to type '({ [k: string]: never; [k: number]: never; [k: symbol]: never; } & { id: number; })[]'.
Type '{ name: string; id: number; }' is not assignable to type '{ [k: string]: never; [k: number]: never; [k: symbol]: never; } & { id: number; }'.
Type '{ name: string; id: number; }' is not assignable to type '{ [k: string]: never; [k: number]: never; [k: symbol]: never; }'.
Property 'name' is incompatible with index signature.
Type 'string' is not assignable to type 'never'.
When using types instead of interfaces, the property are restricted. At least in the IDE (no runtime check).
Example
type Point = {
x: number;
y: number;
}
const somePoint: Point = {
x: 10,
y: 22,
z: 32
}
It throws :
Type '{ x: number; y: number; z: number; }' is not assignable to type 'Point'. Object literal may only specify known properties, and 'z' does not exist in type 'Point'.
I think types are good for defining closed data structures, compared to interfaces. Having the IDE yelling (actually the compiler) when the data does not match exactly the shape is already a great type guardian when developping

Best way to declare string structure with TypesScript?

Let say I have this JavaScript object:
const myObject = {
id: 'my_id', // Base value that following value's structures will be depended on (always pascal case).
upperID: 'MY_ID', // Uppercase of `id`.
camelID: 'myId', // Camel case of `id`.
}
I want to use TypeScript to ensure that id is always pascal lower case. The upperID and camelID has same "value" with different string structure as above. What would be the best way to declare this myObject type in TypeScript?
The upper case one is pretty easy with the provided Uppercase<T> utility type.
type CasedIds<ID extends string> = {
id: ID,
upperID: Uppercase<ID>
}
const myObject: CasedIds<'my_id'> = {
id: 'my_id',
upperID: 'MY_ID',
} as const
See playground
The camel case one gets tricky. So first you need a type that can do this to a string.
There's probably a few ways to do this. Here's one.
type CamelCase<T extends string> =
T extends `${infer Pre}_${infer Letter}${infer Post}`
? `${Pre}${Capitalize<Letter>}${CamelCase<Post>}`
: T
type TestCamel = CamelCase<'abc_def_ghi'> // 'abcDefGhi'
See playground
Let's walk through this.
This generic type takes a string type as the generic parameter T. It then check to see if T extends a string of a certain pattern that contains an underscore followed by some characters.
If it does, then infer some substrings from that pattern. Infer the part before the underscore as Pre, the character after the underscore as Letter and the rest of the string as Post. Else, just use the string type as is.
Then we can make a new string from the Pre the Letter capitalized, and the Post. But there maybe more underscores, so we do the whole thing again on Post. This is a recursive type that stops recursing when there are no underscores left.
Using that the rest is easy:
type CamelCase<T extends string> =
T extends `${infer Pre}_${infer Letter}${infer Post}`
? `${Pre}${Capitalize<Letter>}${CamelCase<Post>}`
: T
type CasedIds<ID extends string> = {
id: ID,
upperID: Uppercase<ID>
camelID: CamelCase<ID>
}
const myObject: CasedIds<'my_id'> = {
id: 'my_id',
upperID: 'MY_ID',
camelID: 'myId',
} as const
See playground
Although you'll probably want a function build these for you:
function makeId<T extends string>(id: T): CasedIds<T> {
return {} as unknown as CasedIds<T> // mock a real implementation
}
const myId = makeId('my_id')
myId.id // type: 'my_id'
myId.upperID // type: 'MY_ID'
myId.camelID // type: 'myId'
See playground

typescript template literal in interface key error

Typescript v4.4.3
Reproducible Playground Example
--
interface IDocument {
[added_: `added_${string}`]: number[] | undefined;
}
const id = 'id';
const document: IDocument = {
[`added_${id}`]: [1970]
}
What i've tried:
I've confirmed that id in my code is a string.
This happens when running tsc not just in VSCode warnings
[`added_abc`]: [1] // no error
[`added_${'abc'}`]: [1] // errors
[`added_${stringVariable}`] // errors
Is there some restrictions of using template literals or anything else I can investigate to diagnose this?
'string' and '`added_${string}`' index signatures are incompatible.
Type 'string | string[] | number[]' is not assignable to type 'number[] | undefined'.
Type 'string' is not assignable to type 'number[] | undefined'.ts(2322)
The issue is that computed keys of types that are not single literal types are widened to string, and such object literals that use them will end up being given a full string index signature instead of anything narrower. So something like {[k]: 123} will be given a narrow key if k is of type "foo" ({foo: number}), but if k is of a union type type "foo" | "bar" or a pattern template literal type (as implemented in ms/TS#40598) like `foo${string}`, then it will get a full string index signature ({[x: string]: number}).
There is an open issue at microsoft/TypeScript#13948 asking for something better here; it's been around a long time and originally was asking only about unions of literals. Now that pattern template literals exist this behavior is even more noticeable. For now there is no built-in support in the language to deal with this.
In your code, tech1.uuid is of type string... not a string literal type, because the compiler infers string property types as string and not more narrowly. If you want a narrower literal type there, you might want to give tech's initializer a const assertion:
const tech1 = {
uuid: '70b26275-5096-4e4b-9d50-3c965c9e5073',
} as const;
/* const tech1: {
readonly uuid: "70b26275-5096-4e4b-9d50-3c965c9e5073";
} */
Then to get the computed key to be a single literal, you will need another const assertion to tell the compiler that is should actually process the template literal value `added_${tech1.uuid}` as a template literal type:
const doc: IDocument = {
name: "",
[`added_${tech1.uuid}` as const]: [19700101], // <-- const assert in there
}; // okay
(They almost made such things happen automatically without a const assertion, but it broke too much code and was reverted in microsoft/TypeScript#42588).
If you need tech1.uuid to remain string and want more strongly-typed computed keys, then you will need to work around it with a helper function. Here's one which takes a key of type K and a value pf type V and returns an object whose type is a type whose keys are in K and whose values are in V. (It distributes over unions, since kv(Math.random()<0.5 ? "a" : "b", 123) should have type {a: number} | {b: number} and not {a: number, b: number}:
function kv<K extends PropertyKey, V>(k: K, v: V):
{ [P in K]: { [Q in P]: V } }[K] {
return { [k]: v } as any;
}
You can see that it behaves as desired with a pattern template literal key:
const test = kv(`added_${tech1.uuid}` as const, [19700101]);
/* const test: { [x: `added_${string}`]: number[]; } */
And so you can use it along with Object.assign() to build the object you want as an IDocument:
const doc: IDocument = Object.assign(
{ name: "" },
kv(`added_${tech1.uuid}` as const, [19700101])
)
(Note that while you should be able to write {name: "", ...kv(`added_${tech1.uuid}` as const, [19700101])}, this isn't really working safely because the index signature is removed. See microsoft/TypeScript#42021 for more information.)
This may or may not be worth it to you; probably you can just write a type assertion and move on:
const doc = {
name: "",
[`added_${tech1.uuid}`]: [19700101],
} as IDocument;
This is less safe than the prior solutions but it's very easy.
Playground link to code
You need to assure TypeScript that tech1.uuid is a constant value.
interface IDocument {
name: string;
[added_: `added_${string}`]: number[] | undefined;
}
const tech1 = {
uuid: '70b26275-5096-4e4b-9d50-3c965c9e5073',
} as const;
const doc: IDocument = {
name: "",
[`added_${ tech1.uuid }` as const]: [19700101],
};
Playground

Typescript reject properties not conforming to an Interface

I have an API controller that takes a body parameter like so
insertUser(#Body() user: IUser) {}
However the issue is that I can post an object containing more properties than what IUser defines.
What is the simplest cleanest way to either ensure that user strictly respects the type, or clone it keeping only whats defined in the interface IUser` ?
My answer will be framework agnostic. You ask about how to force TS to not allow to pass values of types which have additional fields, it means we want to have exclusive behavior of the type, you have the same properties or you are not allowed.
First of all such behavior isn't beneficial at the type level, as if the given value has fields we require to have, then other fields should not matter for us. Another thing is even though we can do compile time checker of types, if your data comes from API then you need to make validation at the runtime level.
First of all type level exclusive type behavior implementation:
type IUser = {
name: string;
lastname: string;
}
// utility type to enforce exlusive type
type OnlyExistingFields<Champion, Pretendent> = {
[K in keyof Pretendent]: K extends keyof Champion ? Pretendent[K] : never
}
// example type with more fields
type IMoreUser = IUser & {
age: number;
}
// instance of correct type
const u1: IUser = {
lastname: '',
name: ''
}
// instance of type with more fields
const u2: IMoreUser = {
lastname: '',
name: '',
age: 12
}
// below type declaration of exlusive function
declare function insertUser<U extends OnlyExistingFields<IUser, U>>(user: U): void
// usage:
insertUser(u1) // 👍 correct type is the same
insertUser(u2) // 🔴 error type has more fields
Above is fully type level check that we can pass only argument of the specific type, any superset of this type will be not allowed.
As I said above solution is only type level, in reality there is no way to enforce at type level if the object has or not wanted interface at the runtime, everything is only an assumption.
That said we need to have also runtime converter in order to remove all not wanted fields. Consider such converter:
// converts object with probably more fields to object with only wanted fields
function toIUser<T extends IUser>(a: T): IUser {
return {
name: a.name,
lastname: a.lastname
}
}
const u3 = toIUser(u2); // u3 is IUser and also in runtime has only IUser fields

Categories