Skip to content

Instantly share code, notes, and snippets.

@tsimbalar
Last active June 16, 2019 11:05
Show Gist options
  • Save tsimbalar/f5731e17f2a5134fb6cb0114e7f259dd to your computer and use it in GitHub Desktop.
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
// 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