DEV Community

Dev Cookies
Dev Cookies

Posted on

The Complete TypeScript Type Checking Guide: One-Stop Solution for All Type Checking Techniques

Table of Contents

  1. Introduction
  2. Basic Type Checking
  3. Built-in Type Guards
  4. Custom Type Guards
  5. Utility Types for Type Checking
  6. Advanced Type Checking Techniques
  7. Runtime Type Checking
  8. Type Narrowing Strategies
  9. Generic Type Checking
  10. Union and Intersection Type Checking
  11. Object and Array Type Checking
  12. Function Type Checking
  13. Class and Interface Type Checking
  14. Error Handling and Type Safety
  15. Best Practices and Performance

Introduction

TypeScript provides powerful type checking capabilities that help catch errors at compile time and improve code reliability. This guide covers every aspect of type checking in TypeScript, from basic techniques to advanced patterns.


Basic Type Checking

Primitive Type Checking

// Basic typeof checks
function checkPrimitiveTypes(value: unknown): string {
  if (typeof value === 'string') {
    return `String: ${value.toUpperCase()}`; // value is narrowed to string
  }

  if (typeof value === 'number') {
    return `Number: ${value.toFixed(2)}`; // value is narrowed to number
  }

  if (typeof value === 'boolean') {
    return `Boolean: ${value ? 'true' : 'false'}`; // value is narrowed to boolean
  }

  if (typeof value === 'bigint') {
    return `BigInt: ${value.toString()}`; // value is narrowed to bigint
  }

  if (typeof value === 'symbol') {
    return `Symbol: ${value.toString()}`; // value is narrowed to symbol
  }

  return 'Unknown type';
}

// Usage examples
console.log(checkPrimitiveTypes("hello")); // String: HELLO
console.log(checkPrimitiveTypes(42)); // Number: 42.00
console.log(checkPrimitiveTypes(true)); // Boolean: true
Enter fullscreen mode Exit fullscreen mode

Null and Undefined Checking

// Checking for null and undefined
function handleNullish(value: string | null | undefined): string {
  // Method 1: Explicit checks
  if (value === null) {
    return 'Value is null';
  }

  if (value === undefined) {
    return 'Value is undefined';
  }

  // Method 2: Nullish coalescing
  const result = value ?? 'Default value';

  // Method 3: Combined nullish check
  if (value == null) { // checks both null and undefined
    return 'Value is nullish';
  }

  return value; // value is narrowed to string
}

// Non-null assertion operator (use with caution)
function useNonNullAssertion(value: string | null) {
  const definitelyString = value!; // Asserts value is not null
  return definitelyString.toUpperCase();
}
Enter fullscreen mode Exit fullscreen mode

Built-in Type Guards

Array.isArray()

function processArrayOrSingle(value: string | string[]): string[] {
  if (Array.isArray(value)) {
    return value; // value is narrowed to string[]
  }

  return [value]; // value is narrowed to string
}

// Advanced array type checking
function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(item => typeof item === 'string');
}

function processUnknownArray(value: unknown): string[] {
  if (isStringArray(value)) {
    return value; // value is narrowed to string[]
  }

  throw new Error('Not a string array');
}
Enter fullscreen mode Exit fullscreen mode

instanceof Operator

class User {
  constructor(public name: string) {}
  greet() { return `Hello, ${this.name}`; }
}

class Admin extends User {
  constructor(name: string, public permissions: string[]) {
    super(name);
  }

  hasPermission(perm: string) {
    return this.permissions.includes(perm);
  }
}

function handleUserType(user: User | Admin | string): string {
  if (user instanceof Admin) {
    return `Admin: ${user.name}, Permissions: ${user.permissions.join(', ')}`;
  }

  if (user instanceof User) {
    return `User: ${user.greet()}`;
  }

  if (typeof user === 'string') {
    return `String: ${user}`;
  }

  return 'Unknown type';
}

// Error handling with instanceof
function safeInstanceCheck(obj: unknown): string {
  try {
    if (obj instanceof Date) {
      return obj.toISOString();
    }

    if (obj instanceof RegExp) {
      return obj.source;
    }

    if (obj instanceof Error) {
      return obj.message;
    }

    return 'Not a recognized instance';
  } catch (error) {
    return 'Error during instance check';
  }
}
Enter fullscreen mode Exit fullscreen mode

Custom Type Guards

Basic Type Guards

// Simple type guard functions
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isNumber(value: unknown): value is number {
  return typeof value === 'number' && !isNaN(value);
}

function isBoolean(value: unknown): value is boolean {
  return typeof value === 'boolean';
}

// Object type guards
interface Person {
  name: string;
  age: number;
}

function isPerson(obj: unknown): obj is Person {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'name' in obj &&
    'age' in obj &&
    typeof (obj as Person).name === 'string' &&
    typeof (obj as Person).age === 'number'
  );
}

// Usage
function processPerson(data: unknown): string {
  if (isPerson(data)) {
    return `${data.name} is ${data.age} years old`; // data is narrowed to Person
  }

  throw new Error('Invalid person data');
}
Enter fullscreen mode Exit fullscreen mode

Advanced Type Guards

// Generic type guard
function isArrayOf<T>(
  value: unknown,
  guard: (item: unknown) => item is T
): value is T[] {
  return Array.isArray(value) && value.every(guard);
}

// Union type guards
type Status = 'pending' | 'completed' | 'failed';

function isStatus(value: unknown): value is Status {
  return typeof value === 'string' && 
         ['pending', 'completed', 'failed'].includes(value);
}

// Complex object type guard
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

function isApiResponse<T>(
  obj: unknown,
  dataGuard: (data: unknown) => data is T
): obj is ApiResponse<T> {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'data' in obj &&
    'status' in obj &&
    'message' in obj &&
    typeof (obj as any).status === 'number' &&
    typeof (obj as any).message === 'string' &&
    dataGuard((obj as any).data)
  );
}

// Discriminated union type guard
interface Circle {
  kind: 'circle';
  radius: number;
}

interface Square {
  kind: 'square';
  size: number;
}

type Shape = Circle | Square;

function isCircle(shape: Shape): shape is Circle {
  return shape.kind === 'circle';
}

function isSquare(shape: Shape): shape is Square {
  return shape.kind === 'square';
}

function calculateArea(shape: Shape): number {
  if (isCircle(shape)) {
    return Math.PI * shape.radius ** 2;
  }

  if (isSquare(shape)) {
    return shape.size ** 2;
  }

  // TypeScript knows this is unreachable
  const _exhaustive: never = shape;
  throw new Error('Unhandled shape type');
}
Enter fullscreen mode Exit fullscreen mode

Utility Types for Type Checking

Built-in Utility Types

// Partial type checking
interface Config {
  host: string;
  port: number;
  secure: boolean;
}

function isPartialConfig(obj: unknown): obj is Partial<Config> {
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  const config = obj as Partial<Config>;

  return (
    (config.host === undefined || typeof config.host === 'string') &&
    (config.port === undefined || typeof config.port === 'number') &&
    (config.secure === undefined || typeof config.secure === 'boolean')
  );
}

// Required type checking
function isRequiredConfig(obj: unknown): obj is Required<Config> {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'host' in obj &&
    'port' in obj &&
    'secure' in obj &&
    typeof (obj as Config).host === 'string' &&
    typeof (obj as Config).port === 'number' &&
    typeof (obj as Config).secure === 'boolean'
  );
}

// Pick type checking
type ConfigCredentials = Pick<Config, 'host' | 'port'>;

function isConfigCredentials(obj: unknown): obj is ConfigCredentials {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'host' in obj &&
    'port' in obj &&
    typeof (obj as ConfigCredentials).host === 'string' &&
    typeof (obj as ConfigCredentials).port === 'number'
  );
}

// Omit type checking
type PublicConfig = Omit<Config, 'secure'>;

function isPublicConfig(obj: unknown): obj is PublicConfig {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'host' in obj &&
    'port' in obj &&
    !('secure' in obj) &&
    typeof (obj as PublicConfig).host === 'string' &&
    typeof (obj as PublicConfig).port === 'number'
  );
}
Enter fullscreen mode Exit fullscreen mode

Custom Utility Types

// DeepPartial utility type
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// NonNullable utility
type NonNullable<T> = T extends null | undefined ? never : T;

function isNonNullable<T>(value: T): value is NonNullable<T> {
  return value !== null && value !== undefined;
}

// KeyOf utility for runtime checking
function hasKey<T extends object>(
  obj: T,
  key: string | number | symbol
): key is keyof T {
  return key in obj;
}

// Safe property access
function getProperty<T extends object, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  if (hasKey(obj, key)) {
    return obj[key];
  }

  throw new Error(`Property ${String(key)} not found`);
}
Enter fullscreen mode Exit fullscreen mode

Advanced Type Checking Techniques

Conditional Types

// Conditional type checking
type IsArray<T> = T extends any[] ? true : false;
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type IsPromise<T> = T extends Promise<any> ? true : false;

// Runtime conditional type checking
function checkArrayType<T>(value: T): value is T extends any[] ? T : never {
  return Array.isArray(value) as any;
}

function checkFunctionType<T>(value: T): value is T extends Function ? T : never {
  return typeof value === 'function' as any;
}

// Extract and Exclude utilities
type StringKeys<T> = Extract<keyof T, string>;
type NonStringKeys<T> = Exclude<keyof T, string>;

interface MixedKeys {
  [key: string]: any;
  [key: number]: any;
  symbol: symbol;
}

function getStringKeys<T extends object>(obj: T): StringKeys<T>[] {
  return Object.keys(obj) as StringKeys<T>[];
}
Enter fullscreen mode Exit fullscreen mode

Mapped Types

// Readonly type checking
type ReadonlyVersion<T> = {
  readonly [P in keyof T]: T[P];
};

function isReadonly<T extends object>(
  obj: T | ReadonlyVersion<T>
): obj is ReadonlyVersion<T> {
  // This is a logical check - in runtime, readonly is not enforceable
  return Object.isFrozen(obj);
}

// Mutable type checking
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

function makeMutable<T extends object>(obj: T): Mutable<T> {
  return { ...obj } as Mutable<T>;
}

// Optional to Required conversion
type RequiredKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;

function hasRequiredKeys<T extends object, K extends keyof T>(
  obj: T,
  keys: K[]
): obj is RequiredKeys<T, K> {
  return keys.every(key => obj[key] !== undefined);
}
Enter fullscreen mode Exit fullscreen mode

Runtime Type Checking

JSON Schema Validation

// Simple JSON schema validator
interface Schema {
  type: 'string' | 'number' | 'boolean' | 'object' | 'array';
  properties?: Record<string, Schema>;
  items?: Schema;
  required?: string[];
}

function validateSchema(value: unknown, schema: Schema): boolean {
  switch (schema.type) {
    case 'string':
      return typeof value === 'string';

    case 'number':
      return typeof value === 'number';

    case 'boolean':
      return typeof value === 'boolean';

    case 'array':
      if (!Array.isArray(value)) return false;
      if (schema.items) {
        return value.every(item => validateSchema(item, schema.items!));
      }
      return true;

    case 'object':
      if (typeof value !== 'object' || value === null || Array.isArray(value)) {
        return false;
      }

      if (schema.properties) {
        const obj = value as Record<string, unknown>;

        // Check required properties
        if (schema.required) {
          for (const key of schema.required) {
            if (!(key in obj)) return false;
          }
        }

        // Validate properties
        for (const [key, propSchema] of Object.entries(schema.properties)) {
          if (key in obj) {
            if (!validateSchema(obj[key], propSchema)) {
              return false;
            }
          }
        }
      }

      return true;

    default:
      return false;
  }
}

// Usage example
const userSchema: Schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'number' },
    active: { type: 'boolean' }
  },
  required: ['name', 'age']
};

function validateUser(data: unknown): data is { name: string; age: number; active?: boolean } {
  return validateSchema(data, userSchema);
}
Enter fullscreen mode Exit fullscreen mode

Runtime Type Guards with Error Details

// Enhanced type checking with detailed errors
class TypeCheckError extends Error {
  constructor(
    public path: string,
    public expected: string,
    public received: string
  ) {
    super(`Type check failed at ${path}: expected ${expected}, received ${received}`);
  }
}

function assertType<T>(
  value: unknown,
  guard: (v: unknown) => v is T,
  path = 'root'
): asserts value is T {
  if (!guard(value)) {
    throw new TypeCheckError(path, 'valid type', typeof value);
  }
}

// Deep object validation
function validateObjectDeep(
  obj: unknown,
  validators: Record<string, (v: unknown) => boolean>,
  path = 'root'
): void {
  if (typeof obj !== 'object' || obj === null) {
    throw new TypeCheckError(path, 'object', typeof obj);
  }

  const target = obj as Record<string, unknown>;

  for (const [key, validator] of Object.entries(validators)) {
    const currentPath = `${path}.${key}`;

    if (!(key in target)) {
      throw new TypeCheckError(currentPath, 'property to exist', 'undefined');
    }

    if (!validator(target[key])) {
      throw new TypeCheckError(
        currentPath,
        'valid value',
        String(target[key])
      );
    }
  }
}

// Usage
try {
  validateObjectDeep(
    { name: 'John', age: '30' }, // age should be number
    {
      name: (v): v is string => typeof v === 'string',
      age: (v): v is number => typeof v === 'number'
    }
  );
} catch (error) {
  if (error instanceof TypeCheckError) {
    console.error(error.message); // Type check failed at root.age: expected valid value, received 30
  }
}
Enter fullscreen mode Exit fullscreen mode

Type Narrowing Strategies

Control Flow Analysis

// Type narrowing with control flow
function processValue(value: string | number | null): string {
  // Early return pattern
  if (value === null) {
    return 'null value';
  }

  if (typeof value === 'string') {
    return value.toUpperCase(); // value is narrowed to string
  }

  // At this point, value is narrowed to number
  return value.toFixed(2);
}

// Complex narrowing with multiple conditions
function complexNarrowing(
  input: string | number | boolean | null | undefined
): string {
  if (input == null) { // null or undefined
    return 'nullish';
  }

  if (typeof input === 'boolean') {
    return input ? 'true' : 'false';
  }

  if (typeof input === 'string') {
    if (input.length === 0) {
      return 'empty string';
    }
    return input;
  }

  // input is now narrowed to number
  return input.toString();
}
Enter fullscreen mode Exit fullscreen mode

Discriminated Unions

// Tagged union types
interface LoadingState {
  status: 'loading';
}

interface SuccessState {
  status: 'success';
  data: any;
}

interface ErrorState {
  status: 'error';
  error: string;
}

type AsyncState = LoadingState | SuccessState | ErrorState;

function handleAsyncState(state: AsyncState): string {
  switch (state.status) {
    case 'loading':
      return 'Loading...';

    case 'success':
      return `Success: ${JSON.stringify(state.data)}`;

    case 'error':
      return `Error: ${state.error}`;

    default:
      // Exhaustiveness checking
      const _exhaustive: never = state;
      throw new Error('Unhandled state');
  }
}

// Pattern matching with type narrowing
function isLoadingState(state: AsyncState): state is LoadingState {
  return state.status === 'loading';
}

function isSuccessState(state: AsyncState): state is SuccessState {
  return state.status === 'success';
}

function isErrorState(state: AsyncState): state is ErrorState {
  return state.status === 'error';
}
Enter fullscreen mode Exit fullscreen mode

in Operator

// Using 'in' operator for type narrowing
interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

type Animal = Bird | Fish;

function moveAnimal(animal: Animal): void {
  if ('fly' in animal) {
    animal.fly(); // animal is narrowed to Bird
  } else {
    animal.swim(); // animal is narrowed to Fish
  }
}

// Complex property checking
interface Rectangle {
  width: number;
  height: number;
}

interface Circle {
  radius: number;
}

type Shape2D = Rectangle | Circle;

function getArea(shape: Shape2D): number {
  if ('width' in shape && 'height' in shape) {
    return shape.width * shape.height; // shape is Rectangle
  }

  if ('radius' in shape) {
    return Math.PI * shape.radius ** 2; // shape is Circle
  }

  // This should never happen with proper types
  throw new Error('Unknown shape');
}

// Defensive property checking
function safePropertyAccess(obj: unknown): string {
  if (
    typeof obj === 'object' &&
    obj !== null &&
    'toString' in obj &&
    typeof obj.toString === 'function'
  ) {
    return obj.toString();
  }

  return 'Cannot convert to string';
}
Enter fullscreen mode Exit fullscreen mode

Generic Type Checking

Generic Type Guards

// Generic type guard functions
function isType<T>(
  value: unknown,
  validator: (v: unknown) => v is T
): value is T {
  return validator(value);
}

// Generic array type guard
function isArrayOfType<T>(
  value: unknown,
  itemValidator: (item: unknown) => item is T
): value is T[] {
  return Array.isArray(value) && value.every(itemValidator);
}

// Generic object type guard
function isObjectWithShape<T extends Record<string, unknown>>(
  value: unknown,
  shape: { [K in keyof T]: (v: unknown) => v is T[K] }
): value is T {
  if (typeof value !== 'object' || value === null) {
    return false;
  }

  const obj = value as Record<string, unknown>;

  for (const [key, validator] of Object.entries(shape)) {
    if (!(key in obj) || !validator(obj[key])) {
      return false;
    }
  }

  return true;
}

// Usage examples
interface User {
  id: number;
  name: string;
  email: string;
}

const userValidator = {
  id: (v: unknown): v is number => typeof v === 'number',
  name: (v: unknown): v is string => typeof v === 'string',
  email: (v: unknown): v is string => typeof v === 'string' && v.includes('@')
};

function validateUser(data: unknown): data is User {
  return isObjectWithShape(data, userValidator);
}

function validateUsers(data: unknown): data is User[] {
  return isArrayOfType(data, validateUser);
}
Enter fullscreen mode Exit fullscreen mode

Constrained Generics

// Constrained generic type checking
interface Identifiable {
  id: string | number;
}

function isIdentifiable<T extends Identifiable>(
  value: unknown
): value is T {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    (typeof (value as any).id === 'string' || typeof (value as any).id === 'number')
  );
}

// Generic type checking with multiple constraints
interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Named {
  name: string;
}

function isTimestampedAndNamed<T extends Timestamped & Named>(
  value: unknown
): value is T {
  return (
    typeof value === 'object' &&
    value !== null &&
    'name' in value &&
    'createdAt' in value &&
    'updatedAt' in value &&
    typeof (value as any).name === 'string' &&
    (value as any).createdAt instanceof Date &&
    (value as any).updatedAt instanceof Date
  );
}

// Conditional generic type checking
type ApiResult<T> = T extends string
  ? { message: T }
  : T extends number
  ? { code: T }
  : { data: T };

function isApiResult<T>(
  value: unknown,
  originalType: T
): value is ApiResult<T> {
  if (typeof value !== 'object' || value === null) {
    return false;
  }

  if (typeof originalType === 'string') {
    return 'message' in value;
  }

  if (typeof originalType === 'number') {
    return 'code' in value;
  }

  return 'data' in value;
}
Enter fullscreen mode Exit fullscreen mode

Union and Intersection Type Checking

Union Types

// Union type checking strategies
type StringOrNumber = string | number;
type StringOrNumberOrBoolean = string | number | boolean;

function processUnion(value: StringOrNumber): string {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }

  // TypeScript knows value is number here
  return value.toFixed(2);
}

// Complex union type checking
type Success<T> = { success: true; data: T };
type Failure = { success: false; error: string };
type Result<T> = Success<T> | Failure;

function isSuccess<T>(result: Result<T>): result is Success<T> {
  return result.success === true;
}

function isFailure<T>(result: Result<T>): result is Failure {
  return result.success === false;
}

function handleResult<T>(result: Result<T>): string {
  if (isSuccess(result)) {
    return `Success: ${JSON.stringify(result.data)}`;
  }

  return `Error: ${result.error}`;
}

// Union with null checking
type NullableString = string | null;
type OptionalString = string | undefined;
type NullishString = string | null | undefined;

function handleNullableString(value: NullableString): string {
  if (value === null) {
    return 'null';
  }

  return value; // value is narrowed to string
}

function handleOptionalString(value: OptionalString): string {
  if (value === undefined) {
    return 'undefined';
  }

  return value; // value is narrowed to string
}

function handleNullishString(value: NullishString): string {
  if (value == null) { // checks both null and undefined
    return 'nullish';
  }

  return value; // value is narrowed to string
}
Enter fullscreen mode Exit fullscreen mode

Intersection Types

// Intersection type checking
interface Readable {
  read(): string;
}

interface Writable {
  write(data: string): void;
}

type ReadWrite = Readable & Writable;

function isReadable(obj: unknown): obj is Readable {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'read' in obj &&
    typeof (obj as any).read === 'function'
  );
}

function isWritable(obj: unknown): obj is Writable {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'write' in obj &&
    typeof (obj as any).write === 'function'
  );
}

function isReadWrite(obj: unknown): obj is ReadWrite {
  return isReadable(obj) && isWritable(obj);
}

// Complex intersection types
interface Person {
  name: string;
  age: number;
}

interface Employee {
  employeeId: string;
  department: string;
}

interface Manager {
  managerId: string;
  teamSize: number;
}

type EmployeePerson = Person & Employee;
type ManagerPerson = Person & Employee & Manager;

function isEmployeePerson(obj: unknown): obj is EmployeePerson {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'name' in obj &&
    'age' in obj &&
    'employeeId' in obj &&
    'department' in obj &&
    typeof (obj as any).name === 'string' &&
    typeof (obj as any).age === 'number' &&
    typeof (obj as any).employeeId === 'string' &&
    typeof (obj as any).department === 'string'
  );
}

function isManagerPerson(obj: unknown): obj is ManagerPerson {
  return (
    isEmployeePerson(obj) &&
    'managerId' in obj &&
    'teamSize' in obj &&
    typeof (obj as any).managerId === 'string' &&
    typeof (obj as any).teamSize === 'number'
  );
}
Enter fullscreen mode Exit fullscreen mode

Object and Array Type Checking

Object Type Checking


typescript
// Deep object type checking
interface NestedObject {
  level1: {
    level2: {
      value: string;
      items: number[];
    };
  };
}

function isNestedObject(obj: unknown): obj is NestedObject {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'level1' in obj &&
    typeof (obj as any).level1 === 'object' &&
    (obj as any).level1 !== null &&
    'level2' in (obj as any).level1 &&
    typeof (obj as any).level1.level2 === 'object' &&
    (obj as any).level1.level2 !== null &&
    'value' in (obj as any).level1.level2 &&
    'items' in (obj as any).level1.level2 &&
    typeof (obj as any).level1.level2.value === 'string' &&
    Array.isArray((obj as any).level1.level2.items) &&
    (obj as any).level1.level2.items.every((item: unknown) => typeof item === 'number')
  );
}

// Record type checking
type StringRecord = Record<string, string>;
type NumberRecord = Record<string, number>;
type MixedRecord = Record<string, string | number>;

function isStringRecord(obj: unknown): obj is StringRecord {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    !Array.isArray(obj) &&
    Object.values(obj).every(value => typeof value === 'string')
  );
}

function isNumberRecord(obj: unknown): obj is NumberRecord {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    !Array.isArray(obj) &&
    Object.values(obj).every(value => typeof value === 'number')
  );
}

function isMixedRecord(obj: unknown): obj is MixedRecord {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    !Array.isArray(obj) &&
    Object.values(obj).every(value
Enter fullscreen mode Exit fullscreen mode

Top comments (0)