Say I have some interface:
export interface MyDocument {
id: string,
collection: string[];
}
I then create a new (well cast an existing to that type):
const workingDocument = <MyDocument>document;
And finally, I have this if statement block to check whether it actually contains everything I have specified in that interface:
if (!workingDocument.id) {
throw new Error("Document missing `id`");
} else if (!workingDocument.collection) {
throw new Error("Document missing `collection` array");
}
However I don't seem to like this as that if statement could grow forever and not very good to maintain.
Is there a better way?
Thanks.
If I understand correctly, you're asking for a runtime check that an object includes all the properties that an interface defines. That is not possible with an interface on its own, because the type information associated with an interface does not make it to runtime; in other words, the interface is only useful when we run the TypeScript compiler.
What you could do is to create an schema that contains all the properties of the interface. Then you could loop over that schema to check that all the properties exist on your object. Here is an example of how that might look. I have wrapped the example in a user-defined type guard.
export interface MyDocument {
id: string,
collection: string[];
}
const isMyDocument = (input: any): input is MyDocument => {
const schema: Record<keyof MyDocument, string> = {
id: 'string',
collection: 'array'
};
const missingProperties = Object.keys(schema)
.filter(key => input[key] === undefined)
.map(key => key as keyof MyDocument)
.map(key => new Error(`Document is missing ${key} ${schema[key]}`));
// throw the errors if you choose
return missingProperties.length === 0;
}
const obj = {};
if (isMyDocument(obj)) {
// the compiler now knows that obj has all of its properties
obj.collection;
}
Here is the above code in the TypeScript playground.
Here is how you might use the ...
operator to extend a schema.
interface ParentDocument {
id: string,
collection: [],
}
interface ChildDocument extends ParentDocument {
name: string;
}
const schemaParent: Record<keyof ParentDocument, string> = {
id: 'string',
collection: 'array'
};
const schemaChild: Record<keyof ChildDocument, string> = {
name: 'string',
...schemaParent,
};
If you're creating / using this Document type internally, you can use types/interfaces to assert their types - for yourself - without the need to cast.
However, if the documents are from outside of your typescript app, you'll need to result to some form of manual type guarding / checking (the thing you want to avoid).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With