Created
February 17, 2024 22:19
-
-
Save zallesov/2876917d24bbf201ca30cabbee4b3588 to your computer and use it in GitHub Desktop.
Code snippet for medium article
This file contains 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
import { DateTime } from 'luxon'; | |
import { z } from 'zod'; | |
/** | |
* Replace type V1 with type V2. | |
*/ | |
type Replace<T, V1, V2> = T extends V1 ? V2 : never; | |
/** | |
* Replace all fields of type V1 with fields of type V2. | |
*/ | |
export type DeepReplace<T, V1, V2> = { | |
[P in keyof T]: T[P] extends V1 | |
? Replace<T[P], V1, V2> | |
: T[P] extends object | |
? DeepReplace<T[P], V1, V2> | |
: T[P]; | |
}; | |
export function deepReplace<T, V1, V2>( | |
obj: T, | |
isFunc: (value: any) => value is V1, | |
convert: (value: V1) => V2, | |
): DeepReplace<T, V1, V2> { | |
const isObject = (value: any): value is Object => | |
value !== null && typeof value === 'object' && !Array.isArray(value); | |
const replace = (current: any): any => { | |
if (Array.isArray(current)) { | |
// If current value is an array, recursively replace each element | |
return current.map((element: any) => replace(element)); | |
} else if (isFunc(current)) { | |
// Replace Date with its string representation | |
return convert(current); | |
} else if (isObject(current)) { | |
// Recursively replace Date with string for all object properties | |
const result: any = {}; | |
for (const key of Object.keys(current)) { | |
result[key] = replace(current[key]); | |
} | |
return result; | |
} else { | |
// Return the value unchanged if it's neither Date nor object nor array | |
return current; | |
} | |
}; | |
return replace(obj); | |
} | |
export const toDTO = <T>(obj: T) => | |
deepReplace<T, DateTime, string>( | |
obj, | |
(value: any): value is DateTime => value instanceof DateTime, | |
(value: DateTime) => value.toISO() || "", | |
); | |
export const toDAO = <T>(obj: T) => | |
deepReplace<T, DateTime, Date>( | |
obj, | |
(value: any): value is DateTime => value instanceof DateTime, | |
(value: DateTime) => value.toJSDate(), | |
); | |
const DateTimeType = z.union([ | |
z.string(), | |
z.custom<DateTime>(arg => arg instanceof DateTime), | |
z.date(), | |
z.number() | |
]).transform((value) => { | |
if (typeof value === 'string') { | |
return DateTime.fromISO(value); | |
} if (typeof value === 'number') { | |
return DateTime.fromMillis(value); | |
}else if (value instanceof Date) { | |
return DateTime.fromJSDate(value); | |
} else { | |
return value; | |
} | |
}); | |
// ZOD defintions | |
const Currency = z.enum(['$', '€', '£']); | |
const Amount = z.object({ | |
value: z.number(), | |
currency: Currency, | |
}); | |
const Payment = z.object({ | |
createdAt: DateTimeType, | |
amount: Amount, | |
}); | |
// Derived model types | |
type Currency = z.infer<typeof Currency>; | |
type Amount = z.infer<typeof Amount>; | |
type Payment = z.infer<typeof Payment>; | |
// DTO and DAO types derived from Payment | |
type DTO<T> = DeepReplace<T, DateTime, string>; | |
type DAO<T> = DeepReplace<T, DateTime, Date>; | |
type PaymentDTO = DTO<Payment>; | |
// type PaymentDAO = { | |
// createdAt: string | |
// amount: { | |
// value: number; | |
// currency: Currency; | |
// }; | |
// }; | |
type PaymentDAO = DAO<Payment>; | |
// type PaymentDAO = { | |
// createdAt: Date | |
// amount: { | |
// value: number; | |
// currency: Currency; | |
// }; | |
// }; | |
const payment: Payment = { | |
createdAt: DateTime.local(), | |
amount: { | |
value: 100, | |
currency: '$', | |
}, | |
}; | |
// Example usage | |
// PaymentDTO | |
const paymentDto = { | |
createdAt: "2022-01-01T00:00:00.000Z", | |
amount: { | |
value: 100, | |
currency: "$", | |
}, | |
}; | |
// PaymentDAO | |
const paymentDao = { | |
createdAt: new Date(), | |
amount: { | |
value: 100, | |
currency: "$", | |
}, | |
}; | |
const paymentFromDto: Payment = Payment.parse(paymentDto); | |
const paymentFromDao: Payment = Payment.parse(paymentDao); | |
console.log("from DTO", paymentFromDto); | |
console.log("from DAO", paymentFromDao); | |
const dtoFromPayment: PaymentDTO = toDTO(payment); | |
const daoFromPayment: PaymentDAO = toDAO(payment); | |
console.log("DTO", dtoFromPayment); | |
console.log("DAO", daoFromPayment); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment