Skip to content

Commit

Permalink
Add SharedUnionFields type (#994)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emiyaaaaa authored Dec 2, 2024
1 parent cbcf97e commit a716c29
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 8 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export type {ArraySplice} from './source/array-splice';
export type {ArrayTail} from './source/array-tail';
export type {SetFieldType} from './source/set-field-type';
export type {Paths} from './source/paths';
export type {SharedUnionFields} from './source/shared-union-fields';
export type {SharedUnionFieldsDeep} from './source/shared-union-fields-deep';
export type {IsNull} from './source/is-null';
export type {IfNull} from './source/if-null';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ Click the type names for complete docs.
- [`ArrayTail`](source/array-tail.d.ts) - Extracts the type of an array or tuple minus the first element.
- [`SetFieldType`](source/set-field-type.d.ts) - Create a type that changes the type of the given keys.
- [`Paths`](source/paths.d.ts) - Generate a union of all possible paths to properties in the given object.
- [`SharedUnionFields`](source/shared-union-fields.d.ts) - Create a type with shared fields from a union of object types.
- [`SharedUnionFieldsDeep`](source/shared-union-fields-deep.d.ts) - Create a type with shared fields from a union of object types, deeply traversing nested structures.
- [`DistributedOmit`](source/distributed-omit.d.ts) - Omits keys from a type, distributing the operation over a union.
- [`DistributedPick`](source/distributed-pick.d.ts) - Picks keys from a type, distributing the operation over a union.
Expand Down
18 changes: 10 additions & 8 deletions source/shared-union-fields-deep.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ type Dog = {
function displayPetInfo(petInfo: (Cat | Dog)['info']) {
// typeof petInfo =>
// {
// name: string;
// type: 'cat';
// catType: string; // Needn't care about this field, because it's not a common pet info field.
// name: string;
// type: 'cat';
// catType: string; // Needn't care about this field, because it's not a common pet info field.
// } | {
// name: string;
// type: 'dog';
// dogType: string; // Needn't care about this field, because it's not a common pet info field.
// name: string;
// type: 'dog';
// dogType: string; // Needn't care about this field, because it's not a common pet info field.
// }
// petInfo type is complex and have some needless fields
Expand All @@ -66,8 +66,8 @@ function displayPetInfo(petInfo: (Cat | Dog)['info']) {
function displayPetInfo(petInfo: SharedUnionFieldsDeep<Cat | Dog>['info']) {
// typeof petInfo =>
// {
// name: string;
// type: 'cat' | 'dog';
// name: string;
// type: 'cat' | 'dog';
// }
// petInfo type is simple and clear
Expand All @@ -77,6 +77,8 @@ function displayPetInfo(petInfo: SharedUnionFieldsDeep<Cat | Dog>['info']) {
}
```
@see SharedUnionFields
@category Object
@category Union
*/
Expand Down
83 changes: 83 additions & 0 deletions source/shared-union-fields.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type {NonRecursiveType, IsUnion} from './internal';
import type {IsNever} from './is-never';
import type {UnknownArray} from './unknown-array';

/**
Create a type with shared fields from a union of object types.
Use-cases:
- You want a safe object type where each key exists in the union object.
- You want to focus on the common fields of the union type and don't want to have to care about the other fields.
@example
```
import type {SharedUnionFields} from 'type-fest';
type Cat = {
name: string;
type: 'cat';
catType: string;
};
type Dog = {
name: string;
type: 'dog';
dogType: string;
};
function displayPetInfo(petInfo: Cat | Dog) {
// typeof petInfo =>
// {
// name: string;
// type: 'cat';
// catType: string; // Needn't care about this field, because it's not a common pet info field.
// } | {
// name: string;
// type: 'dog';
// dogType: string; // Needn't care about this field, because it's not a common pet info field.
// }
// petInfo type is complex and have some needless fields
console.log('name: ', petInfo.name);
console.log('type: ', petInfo.type);
}
function displayPetInfo(petInfo: SharedUnionFields<Cat | Dog>) {
// typeof petInfo =>
// {
// name: string;
// type: 'cat' | 'dog';
// }
// petInfo type is simple and clear
console.log('name: ', petInfo.name);
console.log('type: ', petInfo.type);
}
```
@see SharedUnionFieldsDeep
@category Object
@category Union
*/
export type SharedUnionFields<Union> =
// If `Union` is not a union type, return `Union` directly.
IsUnion<Union> extends false
? Union
// `Union extends` will convert `Union`
// to a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
// But this is not what we want, so we need to wrap `Union` with `[]` to prevent it.
: [Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | UnknownArray]
? Union
: [Union] extends [object]
// `keyof Union` can extract the same key in union type, if there is no same key, return never.
? keyof Union extends infer Keys
? IsNever<Keys> extends false
? {
[Key in keyof Union]: Union[Key]
}
: {}
: Union
: Union;
84 changes: 84 additions & 0 deletions test-d/shared-union-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {expectType} from 'tsd';
import type {SharedUnionFields} from '../index';

type TestingType = {
function: (() => void);
record: Record<string, {
propertyA: string;
}>;
object: {
subObject: {
subSubObject: {
propertyA: string;
};
};
};
string: string;
union: 'test1' | 'test2';
number: number;
boolean: boolean;
date: Date;
regexp: RegExp;
symbol: symbol;
null: null;
undefined: undefined;
optional?: boolean | undefined;
readonly propertyWithKeyword: boolean;
map: Map<string, {propertyA: string; propertyB: string}>;
set: Set<string> ;
objectSet: Set<{propertyA: string; propertyB: string}>;
};

declare const normal: SharedUnionFields<TestingType | {string: string; number: number; foo: any}>;
expectType<{string: string; number: number}>(normal);

declare const normal2: SharedUnionFields<TestingType | {string: string; foo: any}>;
expectType<{string: string}>(normal2);

declare const unMatched: SharedUnionFields<TestingType | {foo: any}>;
expectType<{}>(unMatched);

declare const number: SharedUnionFields<TestingType | {number: number; foo: any}>;
expectType<{number: number}>(number);

declare const string: SharedUnionFields<TestingType | {string: string; foo: any}>;
expectType<{string: string}>(string);

declare const boolean: SharedUnionFields<TestingType | {boolean: boolean; foo: any}>;
expectType<{boolean: boolean}>(boolean);

declare const date: SharedUnionFields<TestingType | {date: Date; foo: any}>;
expectType<{date: Date}>(date);

declare const regexp: SharedUnionFields<TestingType | {regexp: RegExp; foo: any}>;
expectType<{regexp: RegExp}>(regexp);

declare const symbol: SharedUnionFields<TestingType | {symbol: symbol; foo: any}>;
expectType<{symbol: symbol}>(symbol);

declare const null_: SharedUnionFields<TestingType | {null: null; foo: any}>;
expectType<{null: null}>(null_);

declare const undefined_: SharedUnionFields<TestingType | {undefined: undefined; foo: any}>;
expectType<{undefined: undefined}>(undefined_);

declare const optional: SharedUnionFields<TestingType | {optional: string; foo: any}>;
expectType<{optional?: boolean | string | undefined}>(optional);

declare const propertyWithKeyword: SharedUnionFields<TestingType | {readonly propertyWithKeyword: string; foo: any}>;
expectType<{readonly propertyWithKeyword: boolean | string}>(propertyWithKeyword);

declare const map: SharedUnionFields<TestingType | {map: Map<string, {propertyA: string}>; foo: any}>;
expectType<{map: TestingType['map'] | Map<string, {propertyA: string}>}>(map);

declare const set: SharedUnionFields<TestingType | {set: Set<number>; foo: any}>;
expectType<{set: TestingType['set'] | Set<number>}>(set);

declare const moreUnion: SharedUnionFields<TestingType | {string: string; number: number; foo: any} | {string: string; bar: any}>;
expectType<{string: string}>(moreUnion);

declare const union: SharedUnionFields<TestingType | {union: {a: number}}>;
expectType<{union: 'test1' | 'test2' | {a: number}}>(union);

declare const unionWithOptional: SharedUnionFields<{a?: string; foo: number} | {a: string; bar: string}>;
expectType<{a?: string}>(unionWithOptional);

0 comments on commit a716c29

Please sign in to comment.