microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.78k stars 12.46k forks source link

Partial<T> make only some properties optional #25760

Closed ghost closed 6 years ago

ghost commented 6 years ago

Search Terms

Suggestion

Use Cases

current Partial<T> will make all properties of T optional I want to specify only some properties of T optional

Examples

interface Message {
    mid: number;
    content: string;
}

interface MessageOptional {
   mid?: number;
   content: string;
}

const message: Message = assembleMessage({ content: 'hello' })

function assembleMessage(message: MessageOptional) : Message{
    const mid =  ... // generate mid somehow
    message.mid = mid
    return message
} 

it's very often sometimes we want to omit some properties of an object and later supplement it. currently typescript has concepts like IntersectionUnion, how about Complement?

Checklist

My suggestion meets these guidelines:

0x414c commented 6 years ago

You can use existing Partial and Exclude to implement an approach like this:

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface Foo {
    foo: string;
    bar: number;
    baz: boolean;
}
// { foo?: string; bar: number; baz?: boolean; }
type OptionalFoo = WithOptional<Foo, 'foo' | 'baz'>;

const createFoo = (base: OptionalFoo): Foo => {
  return { foo: 'foo', baz: true, ...base };
};
const optionalFoo: OptionalFoo = { foo: '???', bar: 2 };
const fullFoo = createFoo(optionalFoo); // { foo: '???', bar: 2, baz: true }
ghost commented 6 years ago

@0x414c thanks anyway, I hope Omit and WithOptional can be added to lib.d.ts in the future cause it's really a very common need. yet there's another problem with keyof a Union type

interface MessageA {
   mid: number;
   type: number;
   text: string;
}

interface MessageB {
   mid: number;
   type: number;
   url: string;
}
type Message = MessageA | MessageB

type Keys = keyof Message    //    'mid' | 'type'

/**
*  {
*       mid?: number;
*       type: number; 
*  }
*/
type OptionalMessage = WithOptional<Message, 'mid'> 

// what I really what :
type OptionalMessageNeed = WithOptional<MessageA, 'mid'> | WithOptional<MessageB, 'mid'> 
ghost commented 6 years ago

I just got a solution.

type AllKeyOf<T> = T extends never ? never : keyof T

type Omit<T, K> = { [P in Exclude<keyof T, K>]: T[P] }

type Optional<T, K> = { [P in Extract<keyof T, K>]?: T[P] }

type WithOptional<T, K extends AllKeyOf<T>> = T extends never ? never : Omit<T, K> & Optional<T, K>

/**
  {
       mid?: number;
       type: number;
       text?: string; 
  }
| {
       mid?: number;
       type: number;
       url: string;
  }    
*/
type a = WithOptional<Message, 'mid' | 'text'>
typescript-bot commented 6 years ago

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

kacluk123 commented 4 years ago

Guys those solutions are unredable. I think it's better to create separate interface.

gr2m commented 4 years ago

This works for me

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;

Playground link

SrBrahma commented 4 years ago

This works for me

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;

Playground link

This is really good. However, is there a way to keep the keys ordered as the original type?

rattrayalex commented 3 years ago

type-fest has SetOptional, for those who would rather npm install --dev utilities like this.

b-steel commented 3 years ago

@rattrayalex Thank you! This has all that I could need and more

mfuatnuroglu commented 3 years ago

type onlyOneRequired = Partial<Tag> & {id: string} In the above type all properties of Tag are optional instead id which is required.

tresorama commented 2 years ago

Edited on 2023-09-06: The PartialWithRequired present here will not convert property declared as optional (like daz?: string) as required. In case is what you need use the great code of @techieshark of this comment


In case you need to ensurethat All prop are optional but some of those are required this worked for me (Thanks to @gr2m for the starting point.)

// All props are required but some are optional
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;

type Test = {
  foo: string;
  bar: string;
  baz: string;
  daz?: string;
};
type Test1 = Optional<Test, "foo">;
const test1: Test1 = {
  bar: '',
  baz: ''
}
type Test2 = Optional<Test, "bar" | "baz">;
const test2: Test2 = {
  foo: ''
}

// All prop are optional but some of those are required
type PartialWithRequired<T, K extends keyof T> = Pick<T, K> & Partial<T>;

type Test3 = PartialWithRequired<Test, 'bar' | 'foo'>;
const test3:Test3 = {
  bar: '',
  foo: ''
}

Playground

techieshark commented 1 year ago

@tresorama thanks for that starting point. It seems that since type Test already requires all properties but one (daz?), the code provided works.

But if starting out with optional properties and we want to actually make them required, we can also use the Required<> utility. Here's this:


type Test = {
  foo?: string;
  bar?: string;
  baz: string;
};

// All prop are optional but some of those are required -- NOTE! doesn't work, see Test1.
type PartialWithRequired<T, K extends keyof T> = Pick<T, K> & Partial<T>;

type Test1 = PartialWithRequired<Test, 'foo'|'bar'>;
// @ts-expect-error since `Pick` from `PartialWithRequired` does NOT mark an optional parameter like 'foo' or 'bar' as required
const test1:Test1 = {}

// Actually works:
type PartialExceptTheseRequired<T, K extends keyof T> = Pick<Required<T>, K> & Partial<T>

type Test2 = PartialExceptTheseRequired<Test, 'foo'|'bar'>
const test2:Test2 = {}
// ^ it errors, which we want, since we are now requiring foo and bar but haven't set them.

Playground

tresorama commented 1 year ago

@techieshark Fantastic! I didn't know Required