Home > OS >  Convert unknown type to object with property
Convert unknown type to object with property

Time:12-27

I'm trying to parse JSON with TypeScript strict mode.

So basically I have value of unknown type. And I have something like

type Auth = {
  accessToken: string;
};

Value is likely to contain an object of this shape, but I want to have a converter which is both runtime and compile-time correct.

Here's code I came up with:

type Auth = {
  accessToken: string;
};

function convert(value: unknown): Auth {
  if (typeof value != "object") {
    throw new Error(`Unexpected type: ${typeof value}`);
  }
  if (value == null) {
    throw new Error("Unexpected value: null");
  }
  if (!("accessToken" in value)) {
    throw new Error("Missing property: accessToken");
  }
  if (typeof value.accessToken != "string") {
    throw new Error(`Unexpected type: ${typeof value.accessToken}`);
  }
  return value;
}

I expected this code to fully validate shape of unknown object, but I'm still getting the following error:

Type 'object & Record<"accessToken", unknown>' is not assignable to type 'Auth'.
  Types of property 'accessToken' are incompatible.
    Type 'unknown' is not assignable to type 'string'.ts(2322)

I do understand that I can just cast value to Auth type, but that would prevent compiler to assist me if I'd add new field in the future but forget to add corresponding checks to the converter, so I would like to have a converter which works without overriding type system.

CodePudding user response:

A possible solution exploits a type predicate:

interface Auth {
  accessToken: string;
};

function isAuth(value: unknown): value is Auth {
  if (typeof value != "object") {
    throw new Error(`Unexpected type: ${typeof value}`);
  }
  if (value == null) {
    throw new Error("Unexpected value: null");
  }
  if (!("accessToken" in value)) {
    throw new Error("Missing property: accessToken");
  }
  if (typeof value.accessToken != "string") {
    throw new Error(`Unexpected type: ${typeof value.accessToken}`);
  }
  return true;
}



// then, say you have an unknown value and you want to check if it is an Auth
declare const x: unknown;

try {
  if(isAuth(x)) {
    // x type is refined as Auth in here
    x.accessToken;
  }
} catch(e) {
  // ...
}
  • Related