4 ms·
> And I don't know how to express the correct solution (i.e. where we actually assert that A and B are object types). You can do this: function merge< A
by KrishnaShripad 4y ago
> And I don't know how to express the correct solution (i.e. where we actually assert that A and B are object types).
You can do this:
function merge<
A extends Record<string, unknown>,
B extends Record<string, unknown>
>(a: A, b: B): A & B {
return { ...a, ...b }
}
const result = merge({ a: 1 }, { b: 2 })
- ttymck 4y agoWhy Record and not object?
- lediur 4y agoLinters for TypeScript recommend using `Record<string, any>` instead of `object`, since using the `object` type is misleading and can make it harder to use as intended. See: - https://typescript-eslint.io/rules/ban-types/ https://typescript-eslint.io/rules/ban-types/ - https://github.com/typescript-eslint/typescript-eslint/issues/2063 https://github.com/typescript-eslint/typescript-eslint/issue... - https://github.com/microsoft/TypeScript/issues/21732 https://github.com/microsoft/TypeScript/issues/21732 - https://github.com/microsoft/TypeScript/pull/50666 https://github.com/microsoft/TypeScript/pull/50666
- davidatbu 4y agoAvoid the Object and {} types, as they mean 'any non-nullish value'. This is a point of confusion for many developers, who think it means 'any object type'. https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/ban-types.md#options https://github.com/typescript-eslint/typescript-eslint/blob/...
- KrishnaShripad 4y agoBecause you only want to merge two objects that have keys with string type. "object" is represented as Record<any, any>. That would mean, you can use any type as key. Here is an example: function merge< A extends object, B extends object >(a: A, b: B): A & B { return { ...a, ...b } } const result = merge(() => {}, () => {}) // should fail! const anotherResult = merge([1, 2], [3, 4]) // should fail! Which is obviously not what you want. This table here gives you a good overview of differences between object and Record<string, unknown>: https://www.reddit.com/r/typescript/comments/tq3m4f/the_difference_between_object_and_recordstring/ https://www.reddit.com/r/typescript/comments/tq3m4f/the_diff...
- Benjamin_Dobell 4y agoThis fails on: const result = merge({ a: 1 }, { a: "fdsfsd" }) The correct type is quite complex and depends on whether or not `exactOptionalPropertyTypes` is enabled. EDIT: I think this is correct for when `exactOptionalPropertyTypes` is off. type OptionalKeys<T extends { [key in symbol | string | number]?: unknown }> = { [K in keyof T]: {} extends Pick<T, K> ? K : never }[keyof T] function merge< A extends { [K in symbol | string | number]?: unknown }, B extends { [K in symbol | string | number]?: unknown }, >(a: A, b: B): { [K in Exclude<keyof B, keyof A>]: B[K] } & { [K in Exclude<keyof A, keyof B>]: A[K] } & { [K in keyof A & keyof B]: K extends OptionalKeys<B> ? A[K] | Exclude<B[K], undefined> : B[K]; } That's for when `exactOptionPropertyTypes` is enabled. With it disabled, then you'd replace `Exclude<B[K], undefined>` with `B[K]`. As to whether this is a good idea. Ah... it's not :P
- KrishnaShripad 4y agoWow yeah it gets way too complex if you want to track the types of properties within the objects too! If that is the case, then I would just prefer to do this instead as it is much simpler: type Value = { a: string } const result = merge<Value, Value>({ a: 1 }, { a: "fdsfsd" })