Takeshi Tokugawa YD
Takeshi Tokugawa YD

Reputation: 923

Which alternative for type "object" could be in TypeScript?

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?

Usage example: data fetching

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."
      });           
    }
  }
}

Usage example: data analyze from file

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
}

Upvotes: 9

Views: 8317

Answers (3)

J&#225;n Jakub Naništa
J&#225;n Jakub Naništa

Reputation: 1916

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) {
  // ...
}

Upvotes: 1

Daniele Ricci
Daniele Ricci

Reputation: 15797

Isn't returning a boolean enough for your case?

function isNonNullObject(potentialObject: unknown): boolean {
  return typeof potentialObject === "object" && potentialObject !== null;
}

function isNonEmptyObject(potentialObject: unknown): boolean {
  if (typeof potentialObject !== "object" || potentialObject === null) {
    return false;
  }
  return Object.entries(potentialObject as {[key: string]: unknown}).length > 0;
}

function isEmptyObject(potentialObject: unknown): boolean {
  if (typeof potentialObject !== "object" || potentialObject === null) {
    return false;
  }
  return Object.entries(potentialObject as {[key: string]: unknown}).length === 0;
}

Hope this helps.

Upvotes: -1

lazytype
lazytype

Reputation: 925

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

Upvotes: 4

Related Questions