19

I have the following problem. Let's say I have a type:

type A = {
    prop1: string,
    prop2: {
        prop3: string
    }
}

I am receiving some json object from an external service and I want to validate if that json matches type A:

function isA(obj:any): boolean {
    // What should be here?
}

So if my obj is something like:

{
 prop1: "Hello",
 prop2: {
    prop3: "World"
 }
}

or

{
     prop1: "Hello",
     prop2: {
        prop3: "World"
     },
     moreProps: "I don't care about"
}

The function would return true, but false for something like

{
     foo: "Hello",
     bar: {
        prop3: "World"
     }
}

What's the easiest way to achieve this?

Thanks.

2
  • Would Javascript typeof work for you? Commented May 17, 2019 at 15:43
  • I recommend using Zod for runtime type checking. dev.to/diballesteros/… Commented Oct 12, 2023 at 19:22

2 Answers 2

10
  1. Use a type-guard so that Typescript will also narrow the type at type-checking time for you

To use a type-guard, you should change the return type of your isA function to be obj is A

Overall that should make your type validation function look like:

function isA(obj: unknown): obj is A {
    // return boolean here
}
  1. Use the typeof operator to check properties

typeof will return a string value telling you what the type of a variable is. (docs)

In this case, for A you can do something like:

function isA(obj: unknown): obj is A {
    return (
        obj &&
        typeof obj === 'object' &&
        typeof obj['prop1'] === 'string' &&
        obj['prop2'] &&
        typeof obj['prop2'] === 'object' &&
        typeof obj['prop2']['prop3'] === 'string'
    );
}

It's not the most readable thing in the world, and you could always break it down to it's component pieces and comment each check if you'd like.

One important thing to note, however, is that typeof null is actually 'object' so you can't simply check if typeof obj['prop2'] === 'object' and then move on, you'll need to also check if it exists since it still could be null.

At this point, not only will you be validating correctly at runtime, but TypeScript will now be able to improve its type-checking by narrowing the type of obj to A when isA returns true.

Sign up to request clarification or add additional context in comments.

2 Comments

I find it weird there is no better way than this...Every time A changes this needs to change too. Easy to forget adding a property too. Isn't there a more generic way? Given a type definition like: type A = { prop1: string, prop2: { prop3: string } } I would assume the language would provide an easy way to check if something meets that definition or not. Anyway, thanks for your answer.
Totally get what you mean. The fact of the matter is that one of Typescript's explicit design goals is to use a "fully erasable" type system. That is in conflict with what you are asking for, where the runtime functionality is entirely dependent on the types.
3

Not a perfect solution either, but another option is to define a "template object" against which you can perform a runtime comparison like this:

// A const object used to define a type and to serve as a template for runtime comparison.
const myTemplateObject = {
  prop1: "",
  prop2: 12,
  prop3: 14 as string | number,
  prop4: {
    potatoes: "",
    carrots: 0,
  },
};

// So you can use the type in the rest of your code. Or just define it explicitly and make the object above an instance of it.
export type myType = typeof myTemplateObject;

export function matchesType(
  object: Record<string, unknown>,
  templateObject: Record<string, unknown>,
) {
  for (const key in templateObject) {
    const templatePropValue = templateObject[key];
    const templatePropType = templatePropValue;
    switch (templatePropType) {
      case "function":
      // fall-through
      case "symbol":
      // fall-through
      case "undefined":
        throw new Error(
          `matchesType function does not support template objects with ${templatePropType} fields`,
        );
      // or return false if you prefer
      case "bigint":
      case "boolean":
      case "number":
      case "string":
        return templatePropType === typeof object[key];
      case "object":
        const objectPropValue = object[key];
        if (typeof objectPropValue === "object" && objectPropValue !== null) {
          return matchesType(
            objectPropValue as Record<string, unknown>,
            templatePropValue as Record<string, unknown>,
          );
        } else {
          return false;
        }
    }
  }
  return true;
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.