The rule ban-types of newest @typescript-eslint/ban-types disallows object
type as default.
I need to refactor my type analyzing functions according this rule.
I understand that TypeScript-ESLint is not source of truth, but wherever I follow to ban-types
or violate it, I need to comprehend my decision.
function isNonNullObject(potentialObject: unknown): potentialObject is object {
return typeof potentialObject === "object" && potentialObject !== null;
}
function isNonEmptyObject(potentialObject: unknown): potentialObject is object {
if (typeof potentialObject !== "object" || potentialObject === null) {
return false;
}
return Object.entries(potentialObject as {[key: string]: unknown}).length > 0;
}
function isEmptyObject(potentialObject: unknown): potentialObject is object {
if (typeof potentialObject !== "object" || potentialObject === null) {
return false;
}
return Object.entries(potentialObject as {[key: string]: unknown}).length === 0;
}
The basic usage of this function external data analysis (from API or files):
if (isNonNullObject(data)) {
throw new Error("The data is invalid; object expected.");
}
Should I replace object
to other type in this case, or exclusively here object
is allowable?
In real projects, data analyzing functionality is being wrapped to special utility, but it's the concept as below:
type ValidResponseData = {
products: Array<Products>;
productsCount: number;
};
@Component
class ProductsListPage extends Vue {
private products: Array<Products> = [];
private productsCount: number = 0;
private async created(): Promise<void> {
try {
// We must not trust to external data, so it's 'unknown'
const responseData: unknown = await ProductFetchingAPI.fetchAllProducts();
if (!isNonNullObject(responseData)) {
throw new Error(
`The response data data is invalid: non-null object expected, real type: ${typeof responseData},` +
`, value: ${responseData}.`
);
}
// Below checks are meaningless if "responseData" is not object.
if (!Object.prototype.hasOwnProperty.call(responseData, "products")) {
throw new Error(
"Expected that response data has 'products' property but it's missing".
);
}
if (!Object.prototype.hasOwnProperty.call(responseData, "productsCount")) {
throw new Error(
"Expected that response data has 'productsCount' property but it's missing".
);
}
// 'products' and 'productsCount' analysis ....
const validResponseData: ValidResponseData = responseData as ValidResponseData;
this.products = validResponseData.products;
this.productsCount = validResponseData.productsCount;
} catch (error) {
NotificationBarService.displayNotificationBar({
type: NotificationBarService.NotificationsTypes.error,
originalError: error,
text: "Failed to fetch products."
});
}
}
}
const rawData: unknown = /* parse data from the file by appropriate library ... */;
if (!isNonNullObject(rawData)) {
throw new Error(
`The file content is invalid: non-null object expected, real type: ${typeof rawData},` +
`, value: ${rawData}.`
);
}
// Without isNonNullObject(rawData), we can not execute below loop
for (const value of Object.entires(rawData)) {
// check each property
}
TypeScript object type is type of any non-primitive values. TypeScript has introduced new type called as object with version 2.2. It represents all non-primitive types. There are some Primitive types such as string, number, bigint, null, boolean, symbol, undefined. All other types are considered to be non-primitive types.
Introduction to TypeScript object type. The TypeScript object type represents all values that are not in primitive types. The following are primitive types in TypeScript: number. bigint. string. boolean. null.
In TypeScript, we represent those through object types. As we’ve seen, they can be anonymous: or a type alias. In all three examples above, we’ve written functions that take objects that contain the property name (which must be a string) and age (which must be a number ).
interface s can also extend from multiple types. interface s allowed us to build up new types from other types by extending them. TypeScript provides another construct called intersection types that is mainly used to combine existing object types. An intersection type is defined using the & operator.
Rather than isNonNullObject
, it should be sufficient to simply check:
// the `ban-types` rule should allow `Object` here because it's
// the value `Object`, not the type `Object`. Note the capital "O".
if (!(responseData instanceof Object)) {
throw new Error(...);
}
// `responseData`'s type is now narrowed to `Object`, so you can now call `hasOwnProperty`
if (!responseData.hasOwnProperty("products")) {
throw new Error(...);
}
...
This article also might serve as a nice refresher: https://mariusschulz.com/blog/the-object-type-in-typescript
As for the replacement type for object: Record<string, unknown>
is a good start - basically it is saying the data is an object with string properties of unknown values.
As for the type checks - I recently made a TypeScript transformer that can create type guards from your types automatically, it is called ts-type-checked
and it is available on NPM, type-check it out! :D
With ts-type-checked
you no longer need to manually check whether an unknown
object matches a certain type (or interface), you can just write:
import { isA } from 'ts-type-checked';
// ...
if (isA<ValidResponseData>(value) {
// ...
}
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