Last active
June 16, 2019 11:05
-
-
Save tsimbalar/f5731e17f2a5134fb6cb0114e7f259dd to your computer and use it in GitHub Desktop.
An example of a helper function to map an object with ISO6801 date string properties to an object with proper Date properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// v Scroll down to see the example first v | |
// v--------------------------------------v | |
// a mapped type that is a union of the names of properties of T that are of type string | |
type OnlyStringProperties<T> = { [Key in keyof T]: T[Key] extends string ? Key : never }[keyof T]; | |
// From an original object of type T, return another object where the properties | |
// whose name is passed are parsed to Date | |
function convertStringPropertiesToDates<T, K extends OnlyStringProperties<T>>( | |
original: T, | |
...propNames: K[] | |
): T & Record<K, Date> { | |
const extractedDates: Partial<Record<K, Date>> = {}; | |
for (const propName of propNames) { | |
const dateString = (original[propName] as unknown) as string; | |
extractedDates[propName] = new Date(dateString); | |
} | |
const populatedDates = extractedDates as Record<K, Date>; | |
return { ...original, ...populatedDates }; | |
} | |
// A little example | |
// ================ | |
// say we have a type that represents some JSON paylod we are getting. | |
// JSON doesn't have dates, so all we get are ISO 6801 date strings ... | |
interface IncomingJsonType { | |
readonly counter: number; | |
readonly someDate1: string; | |
readonly someDate2: string; | |
readonly things: string[]; | |
} | |
const json: IncomingJsonType = { | |
counter: 4, | |
someDate1: '2019-06-14T20:32:01.071Z', | |
someDate2: '2019-05-14T20:32:01.071Z', | |
things: ['a', 'b'] | |
}; | |
// ... but in the code, we mostly want that, but with real dates : | |
interface BusinessObjectWithRealDates { | |
readonly counter: number; | |
readonly someDate1: Date; | |
readonly someDate2: Date; | |
readonly things: string[]; | |
} | |
// we could parse the date strings one by one (with `new Date(ISOString)`) .... | |
// but we'd have to do it every time this happens | |
// ... or we can use some TypeScript tricks such as `Mapped Types` | |
// here, TypeScript only allows us to pass as params the names of properties | |
// from our object that are `string`s | |
const result = convertStringPropertiesToDates(json, 'someDate1', 'someDate2'); | |
// Here typescript does not complain ! | |
// the result type is compatible with the interface we wanted | |
const businessObj: BusinessObjectWithRealDates = result; | |
// More ... | |
// ======== | |
// Try it in the TypeScript playground to see the inferred types and compilation errors from TypeScript ! https://www.typescriptlang.org/play/ | |
// Inspired by : | |
// - https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c | |
// - https://stackoverflow.com/questions/50900533/how-can-i-extract-the-names-of-all-fields-of-a-specific-type-from-an-interface-i/50900933 | |
// | |
// More info about advanced types in TypeScript documentation : | |
// - https://www.typescriptlang.org/docs/handbook/advanced-types.html | |
// - https://www.typescriptlang.org/docs/handbook/utility-types.html | |
// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment