Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type {SingleKeyObject} from './source/single-key-object.d.ts';
export type {OmitIndexSignature} from './source/omit-index-signature.d.ts';
export type {PickIndexSignature} from './source/pick-index-signature.d.ts';
export type {PartialDeep, PartialDeepOptions} from './source/partial-deep.d.ts';
export type {UnwrapPartial} from './source/unwrap-partial.d.ts';
export type {RequiredDeep} from './source/required-deep.d.ts';
export type {PickDeep} from './source/pick-deep.d.ts';
export type {OmitDeep} from './source/omit-deep.d.ts';
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Click the type names for complete docs.
- [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if you only need one level deep.
- [`PartialOnUndefinedDeep`](source/partial-on-undefined-deep.d.ts) - Create a deep version of another type where all keys accepting `undefined` type are set to optional.
- [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`.
- [`UnwrapPartial`](source/unwrap-partial.d.ts) - Revert the `Partial` modifier on an object type.
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep.
- [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
- [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.)
Expand Down Expand Up @@ -474,6 +475,8 @@ There are many advanced types most users don't know about.
// NodeConfig interface.
new NodeAppBuilder().config({appName: 'ToDoApp'});
```

`Partial<T>` can be reverted with [`UnwrapPartial`](source/unwrap-partial.d.ts).
</details>

- [`Required<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) - Make all properties in `T` required.
Expand Down
33 changes: 33 additions & 0 deletions source/unwrap-partial.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
Revert the `Partial` modifier on an object type.

Use case: Infer the underlying type `T` when only `Partial<T>` is available or the original type may not be directly accessible.

@example
```
import type {UnwrapPartial} from 'type-fest';

type Config = Partial<{
port: number;
host: string;
secure?: boolean;
}>;

type InitializedConfig = UnwrapPartial<Config>;
//=> {port: number; host: string; secure?: boolean}
```

Note: If the provided type isn’t of `Partial<T>`, `UnwrapPartial` has no effect on the original type.

@category Object
*/
export type UnwrapPartial<PartialObjectType> =
PartialObjectType extends Partial<infer ObjectType>
? (
Partial<ObjectType> extends PartialObjectType
? ObjectType
: PartialObjectType
)
: PartialObjectType;

export {};
81 changes: 81 additions & 0 deletions test-d/unwrap-partial.ts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the test cases could be simplified, there are so many test cases of the form UnwrapPartial<Partial<Something>>, with different values for Something. I think we don't need that many because the value of Something doesn't really matter, so we could just keep a few and remove the others.

Tests that don't have Partial are useful, like UnwrapPartial<{a?: string; b: number}>, UnwrapPartial<{a?: string; b?: number}>.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Som, thanks for the review.

I use UnwrapPartial<Partial<Something>> to ensure that the original Something type gets preserved. And there is a good number of types to consider.

Cases like UnwrapPartial<{a?: string; b: number}> and UnwrapPartial<{a?: string; b?: number}> are already covered (under `UnwrapPartial` preserves optional properties).

I’ll add any missing cases you pointed out, but feel free to prune things on your end as well.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cases like UnwrapPartial<{a?: string; b: number}> and UnwrapPartial<{a?: string; b?: number}> are already covered (under `UnwrapPartial` preserves optional properties).

Yeah, I meant all of those are useful, don't remove them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@som-sm — Unless you want to make any other specific changes, I think this PR is ready to merge.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {expectType} from 'tsd';
import type {EmptyObject, UnwrapPartial} from '../index.d.ts';

type TestType = {
a: string;
b: number;
};

expectType<TestType>({} as UnwrapPartial<Partial<TestType>>);
expectType<TestType>({} as UnwrapPartial<{a?: string; b?: number}>);
expectType<Partial<TestType>>({} as UnwrapPartial<Partial<Partial<TestType>>>);

// `UnwrapPartial` preserves optional properties
type TestTypeWithOptionalProp = TestType & {
c?: boolean;
};

type AnotherTestType = {
c: boolean;
d: 'literal';
};

type TestTypeWithOptionalProps = TestType & Partial<AnotherTestType>;

expectType<TestTypeWithOptionalProp>({} as UnwrapPartial<Partial<TestTypeWithOptionalProp>>);
expectType<TestTypeWithOptionalProps>({} as UnwrapPartial<Partial<TestTypeWithOptionalProps>>);

// `UnwrapPartial` preserves nested `Partial` properties
type TestTypeWithPartialProp = TestType & {
c: Partial<TestType>;
};

expectType<TestTypeWithPartialProp>({} as UnwrapPartial<Partial<TestTypeWithPartialProp>>);

// `UnwrapPartial` preserves readonly properties
type TestTypeWithReadonlyProps = Readonly<TestType> & {
readonly c: boolean;
};

expectType<TestTypeWithReadonlyProps>({} as UnwrapPartial<Partial<TestTypeWithReadonlyProps>>);

// `UnwrapPartial` works with methods
type TestTypeWithMethod = {
c(): void;
};

expectType<TestTypeWithMethod>({} as UnwrapPartial<Partial<TestTypeWithMethod>>);

// `UnwrapPartial` works with union types
type PartialUnionType = Partial<TestType> | Partial<AnotherTestType>;

expectType<TestType | AnotherTestType>({} as UnwrapPartial<PartialUnionType>);
expectType<TestType | AnotherTestType>({} as UnwrapPartial<Partial<TestType> | AnotherTestType>);

// `UnwrapPartial` works with index signatures
type ArrayLikeTestType = {
[index: number]: string;
};

type PlainObjectTestType = {
readonly [key: string]: number | undefined;
};

expectType<ArrayLikeTestType>({} as UnwrapPartial<Partial<ArrayLikeTestType>>);
expectType<PlainObjectTestType>({} as UnwrapPartial<PlainObjectTestType>);
expectType<Record<number, string>>({} as UnwrapPartial<Partial<Record<number, string>>>);
expectType<string[]>({} as UnwrapPartial<Partial<string[]>>);
expectType<Array<string | undefined>>({} as UnwrapPartial<Array<string | undefined>>);

// `UnwrapPartial` works with tuples
type TestTuple = [string, number, boolean];

expectType<TestTuple>({} as UnwrapPartial<Partial<TestTuple>>);
expectType<TestTuple>({} as UnwrapPartial<[string?, number?, boolean?]>);

// `UnwrapPartial` works with empty objects and unknown types
expectType<EmptyObject>({} as UnwrapPartial<Partial<EmptyObject>>);
expectType<unknown>({} as UnwrapPartial<Partial<unknown>>);
expectType<any>({} as UnwrapPartial<Partial<any>>);
expectType<Record<string, unknown>>({} as UnwrapPartial<Partial<Record<string, unknown>>>);
expectType<Record<string, any>>({} as UnwrapPartial<Partial<Record<string, any>>>);