I have been trying to create a generic field copy function in TypeScript, but I am struggling to properly define the typing. Refer to method 4 in the code below. My main question is, how can I write a function that ensures TypeScript typing works correctly and verifies the destination type and source type?
Background: I am currently working on a project that involves retrieving data from a NoSQL database and sending it to clients through an API. For security reasons, it is recommended to perform a projection (explicit field copy) to prevent any potential addition of fields being sent to clients in the future. I am looking for a simple solution to achieve this.
// ----------
// TypeScript type definition
// ----------
interface Student {
name: string
studentId?: string
}
interface StudentDbRecord extends Student {
createTimestamp: number
}
interface StudentApiResult extends Student {
// ID is not part of the record due to using NoSQL
id: string
}
// ----------
// Database data
// ----------
const docId: string = '6542fdba-fcae-4b15-a1c8-72a2a57f51c7'
const dbRecord: StudentDbRecord = {
name: 'Chirs',
createTimestamp: Date.now()
}
// ----------
// Implementation
// ----------
// Method 1: Adding an extra `createTimestamp` field in `apiResult1` and returning it to the API caller
const apiResult1: StudentApiResult = {
...dbRecord,
id: docId
}
const apiResult2: StudentApiResult = {
id: docId,
name: dbRecord.name,
// Method 2: This results in a field with a `undefined` value, leading to other issues
studentId: dbRecord.studentId
}
// Method 3: It works, but it is prone to errors as `studentId` is specified 3 times
const apiResult3: StudentApiResult = {
id: docId,
name: dbRecord.name
}
if (dbRecord.studentId !== null) { apiResult3.studentId = dbRecord.studentId }
// Method 4, this should be the best approach but I am unable to make it work in TypeScript
function copyField<D, S, K extends (keyof D & keyof S)>(dest: D, src: S, key: K): void {
if (src[key] !== undefined) {
// Error ts(2322): 'D' could be instantiated with an arbitrary type which could be unrelated to 'S'
dest[key] = src[key]
}
}
const apiResult4: StudentApiResult = {
id: docId,
name: dbRecord.name
}
copyField(apiResult4, dbRecord, 'studentId')