简体   繁体   中英

Typescript: ensure object literal extends interface but return real object type

I'm trying to create a function in typescript that returns an object which must extend a given interface, but i want the function to return the real type of the created object. The reason is that the object in the function might change in the future and i want to make sure it will always have the minimum props required in the interface.

example:

interface MustExtend {
  a: string;
}

function myFunc() {
  // I want to enforce res to extend type MustExtend
  // right now it can be of type {something: 3} and compiler will allow it
  const res = {a: 'hello', b: 2} 
  return res;
}

const c = myFunc(); // c should be of type {a: string, b: number}, or the more concrete type generated by method

EDIT:

I will try to clarify my question. I want the result type of the function to be inferred from the returned object without specifying the type of res , since it's generated by many computations:

interface MustExtend {
  a: string;
}

function myFunc() {
  // i want to enforce res to extend type MustExtend
  // right now it can be of type {something: 3} (no 'a' at all)
  // and i want to make sure it exists
  const res = {
    a: 'hello', 
    b: 2, 
    // a million more properties here that can change over time
  } 
  return res;
}

const c = myFunc(); // c should be of type {a: string, b: number, ...other props}

You can use a trick. Force Typescript to convert res into MustExtend , but then you convert it back to preserve the original type:

interface MustExtend {
  a: string;
}

function myFunc() {
  const res  = {
    a: 'hello', // will cause TS error if you remove this line
    b: 2,
    1: '1',
  } 

  return res as MustExtend as typeof res; // convert and convert back
}

const c = myFunc(); // will have full type

After some tinkering i found how to do it:

interface MustExtend {
  a: string;
}

function myFunc(): typeof res extends MustExtend ? typeof res : never {
  // i want to enforce res to extend type MustExtend
  // right now it can be of type {something: 3} (no 'a' at all)
  // and i want to make sure it exists
  const res = {
    a: 'hello', 
    b: 2, 
    // a million more properties here that can change over time
  } 
  return res;
}

const c = myFunc(); // c should be of type {a: string, b: number, ...other props}

Apparently typescript can get the type of the object in method during compile time and use it in the method types. Using it this way i don't have to specify the type of res in advance and also ensure that it has all the properties from the interface

Your function already returns the most general type possible. Enforcing that this type matches the interface is done when you use the return value. For instance you can have a check like this:

const check: () => MustExtend = () => myFunc()
// or
const check1: MustExtend | undefined = undefined as ReturnType<typeof myFunc> | undefined;

Playground

As far as I know, if you need to enforce the test at type level only (without generating javascript objects), you need an external tool like dtslint .

Fuction return type definition can be a class type, or interface. If you are defining a function like this:

function myFunc(): MustExtend {}

where MustExtend is an interface, it will return object, that complies with interface MustExtend . But returned object will still have its type, which can be checked with instanceof . Actually, in typescript you cannot check if returned object complies with the interface you want - instanceof does not work on interfaces. So you have to check the actual type of the object. With this definition you can return any object inside, that complies with MustExtend (terrible name, name interfaces on nouns :) and later you can check (if you want) what exactly type it was. Hope that helps.

interface MustExtend {
  a: string;
}

const c = {
  a: 'hello',
  b: 2,
}
// Ensure `c` conforms to MustExtend. (`c` will continue to have its original type)
c as MustExtend // Works!

const d = {
  x: 1,
  y: 2,
}
// Ensure `d` conforms to MustExtend
d as MustExtend // Error! Property 'a' is missing

playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM