edhiwo
edhiwo

Reputation: 235

TypeScript: Partially "Partial" type

Environment

TypeScript's version is 3.2.1 and "tsconfig.json" is like below.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true
  }
}

Question

I'm looking for Partially "Partial" type in TypeScript.

type Entity = {
  a: string,
  b: string,
  c?: string,
};

type Ham = MyType<Entity, 'b'>;
/**
 * expected to equal
 * {
 *   a: string,
 *   b?: string, // changed to be optional
 *   c?: string,
 * };
 */

P.S. Titian and t7yang

Thank you for your replies. I checked your types then both types pass compiler's check!

const abc = { a: 'a', b: 'b', c: 'c' };
const ab = { a: 'a', b: 'b' };
const ac = { a: 'a', c: 'c' };
const a = { a: 'a' };

// by t7yang
let test1Abc: OptionalKey<Entity, 'b'> = abc;
let test1Ab: OptionalKey<Entity, 'b'> = ab;
let test1Ac: OptionalKey<Entity, 'b'> = ac;
let test1A: OptionalKey<Entity, 'b'> = a;

// by Titian Cernicova-Dragomir    
let test2Abc: PickPartial<Entity, 'b'> = abc;
let test2Ab: PickPartial<Entity, 'b'> = ab;
let test2Ac: PickPartial<Entity, 'b'> = ac;
let test2A: PickPartial<Entity, 'b'> = a;

Upvotes: 19

Views: 7079

Answers (4)

spinkus
spinkus

Reputation: 8550

Simple version of accepted answer (use Intersection of a Partial and Pick) without any intermediary types to confuse things:

type Entity = {
  a: number,
  b: number,
  c?: number,
}

type Ham = Partial<Entity> & Pick<Entity, Exclude<keyof Entity, 'b'>>;

const b: Ham[] = [{ a: 1 }, { a: 1, b: 1 }, { a: 1, c: 1 }, { a: 1, b: 1, c: 1 }]; // OK.
const c: Ham = {}; // Bad.

Playground

Upvotes: 2

iFreilicht
iFreilicht

Reputation: 14474

Real simple solution:

type PickPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type PartialExcept<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;

Titian's solution was written before Typescript 3.5, which added the Omit helper.

Also remember that you can use string unions to pick multiple attributes to be made optional:

type Full = {
  a: string;
  b: string;
  c: string;
}

// These are equivalent
type ARequired = PickPartial<Full, 'b' | 'c'>;
type ARequired = PartialExcept<Full, 'a'>;

Upvotes: 4

t7yang
t7yang

Reputation: 754

type Entity = {
  a: string,
  b: string,
  c?: string,
};

type OptionalKey<T, O extends keyof T> = Pick<T, Exclude<keyof T, O>> & Partial<{ [P in O]: T[P] }>;

const a: OptionalKey<Entity, 'b'> = {
  a: 'a',
}

const ab: OptionalKey<Entity, 'b'> = {
  a: 'a',
  b: 'b'
}

const ac: OptionalKey<Entity, 'b'> = {
  a: 'a',
  c: 'c'
}

The idea is pick all the properties that want to make optional, then merge with the type than we want to make the property optional.

You can check this in typescript playground

Upvotes: 2

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

You can use Pick in conjunction with Partial to pick only the properties you want to make optional, while preserving the rest using Exclude to get the keys excluding the ones passed in to make optional :

type Entity = {
   a: string,
   b: string,
   c?: string,
};

type PickPartial<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> & Partial<Pick<T, K>> 
type Ham = PickPartial<Entity, 'b'>; // a, b? , c?

Upvotes: 15

Related Questions