Skip to content

Instantly share code, notes, and snippets.

@dmorosinotto
Last active November 14, 2022 14:26
Show Gist options
  • Save dmorosinotto/d70f0e547010c4706cc6f02eb07c6a46 to your computer and use it in GitHub Desktop.
Save dmorosinotto/d70f0e547010c4706cc6f02eb07c6a46 to your computer and use it in GitHub Desktop.
TS Helpers: Mapped to trasform a Type copying only same props and forcing requied/optionals + Keep & Extract from Object only props of a specific Type
//EXPERIMENTS WITH TYPESCRIPT UTILITY TYPES: https://www.typescriptlang.org/docs/handbook/utility-types.html
//AND MAPPED TYPES: https://mariusschulz.com/blog/mapped-types-in-typescript
//READ MORE HERE: https://blog.logrocket.com/mastering-mapped-types-typescript/
type KeepOnlyPropOfT<O, T> = {
[K in keyof O]: O[K] extends T ? K : never
}[keyof O]
type ExtractOnlyT<O, T> = {
[P in KeepOnlyPropOfT<O,T>]: O[P]
}
//INSPIRED BY https://stackoverflow.com/questions/53899692/typescript-how-to-extract-only-the-optional-keys-from-a-type
type OptionalPropertyOf<T extends object> = Exclude<{
[K in keyof T]: T extends Record<K, T[K]>
? never
: K
}[keyof T], undefined>
//CONCEPTUALLY SIMILAR TO Omit<T,E> & Required<Pick<T,R>> & Partial<Pick<T,O>> BUT WITHOUT CONFLICTS REDEFINING PROPS
type Mapped<T extends object, Required extends keyof T, Optionals extends keyof T, Excluded extends keyof T>
= { [P in Required | Exclude<keyof T, Excluded | OptionalPropertyOf<T> | Optionals>]-?: Exclude<T[P],undefined> } //COPYING ORIGINAL PROPS OMIT Excluded + FORCE -? FOR Required!
& { [P in Optionals | Exclude<OptionalPropertyOf<T>, Excluded | Required>]?: T[P] } //FORCE +? FOR Optionals + TRICK TO KEEP ORIGINAL OPTIONALS PROPS? NOT (Excluded | Required)!
//SAMPLE USE CASE
type Party = {
name: string,
partecipants?: number,
location?: {
address: string,
city: string
},
ended: boolean,
selected: boolean,
}
type boolProps = KeepOnlyPropOfT<Party, boolean> // "ended" | "selected"
type stringProps = KeepOnlyPropOfT<Party, string> // "name"
type flags = ExtractOnlyT<Party, boolean> // { ended: boolean, selected: boolean }
type strings = ExtractOnlyT<Party, string> // { name: string }
type soloBoolProps<T> = KeepOnlyPropOfT<T, boolean>
type xx = soloBoolProps<Party> // "ended" | "selected"
function getBoolProp<T>(obj: T, prop: soloBoolProps<T>){
return obj[prop]
}
const party: Party = { name: "TS ROCKS!!!", ended: false, selected: true }
let e = getBoolProp(party,"ended") //boolean OK -> false
let s = getBoolProp(party,"selected") //boolean OK -> true
let ERROR = getBoolProp(party,"name") //TS ERROR NOT VALID PROPS!!!
// ^^^^^ Argument of type "name" is not assignable to soloBoolProps<Party>
type Opts = OptionalPropertyOf<Party>; // "partecipants" | "location"
type Copy = Mapped<Party, never, never, never> //IS THE SAMPE AS Party with partition from ORIGINALS required and optionals
let c: Copy = party; //OK same type no problem
type maybeSelPartecipant = Mapped<Party, "partecipants", "selected", "name" | "location" | "ended">
let ok: maybeSelPartecipant = { partecipants: 42 }; ok.selected = true; //OK ONLY partecipants IS REQUIERED + selcted OPTIONAL
let ko: maybeSelPartecipant = party; //ERROR SHAPE DON'T MACTCH partecipants IN Party CAN BE undefined BUT HERE IS REQUIRED!!!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment