Search code examples
typescripttypescript-typingstypescript-conditional-types

Specify return type when value of input interface is specific value


I have a function that takes an object as an argument. When a property of that input is a given value, I want to specify the return type as being one or the other. I assume this requires conditional types, but I'm struggling with the syntax to access the property of a given interface.

What I currently have is below:

enum FileAcl {
    private = 'private',
    public = 'public',
}

interface UploadOpts {
    someval: string;
    acl: FileAcl;
}

interface PrivateAclResponse {
    acl: FileAcl.private;
    key: string;
}

interface PublicAclResponse {
    acl: FileAcl.public;
    file: string;
}

const uploadFile = async (opts: UploadOpts):
    Promise<PublicAclResponse | PrivateAclResponse> => {
    const { acl } = opts;

    if (opts.acl === FileAcl.public) return { file: 'file', acl }

    return { key: 'key', acl }

}

I did try the following, but I'm clearly wrong so any guidance would be much appreciated.

enum FileAcl {
    private = 'private',
    public = 'public',
}

interface UploadOpts {
    someval: string;
    acl: FileAcl;
}

interface PrivateAclResponse {
    acl: FileAcl.private;
    key: string;
}

interface PublicAclResponse {
    acl: FileAcl.public;
    file: string;
}

// assuming here that ts can match the common properties
type PublicOrPrivate<T extends UploadOpts> = T extends PublicAclResponse
    ? PublicAclResponse
    : PrivateAclResponse;

const uploadFile = async (
    opts: UploadOpts
): Promise<PublicOrPrivate<UploadOpts>> => {
    const { acl } = opts;

    if (opts.acl === FileAcl.public) return { file: 'file', acl }

    return { key: 'key', acl }

}

TIA


Solution

  • You can indeed use a Conditional Type. But additionally a generic parameter is needed for the condition. Unfortunately, TypeScript doesn't seem to be able to narrow opts to either PublicAclResponse or PrivateAclResponse which means we need to assert the return type.

    const uploadFile = async <
      T extends UploadOpts,
      R = T["acl"] extends FileAcl.public
        ? PublicAclResponse
        : PrivateAclResponse
    >(
      opts: T
    ): Promise<R> => {
      const {acl} = opts;
    
      if (opts["acl"] === FileAcl.public) return {file: "", acl} as R;
    
      return {key: "key", acl} as R;
    };
    
    const publicResponse = uploadFile({ // Promise<PublicAclResponse>
      acl: FileAcl.public,
      someval: ""
    });
    
    const privateResponse = uploadFile({ // Promise<PrivateAclResponse>
      acl: FileAcl.private,
      someval: ""
    });
    

    TypeScript Playground