This issue has two facets... the perspective of the caller in method()
, and how method()
is implemented. In TypeScript, it's usually more straightforward to ensure smooth behavior for callers compared to handling it within implementations.
On the caller's end, we can enforce that method
must be invoked with at least one argument containing a defined id
property. One approach is through using overloads. The example shown below demonstrates this concept:
type WithId = {
id: number;
};
function method(state: FactoryOptions & WithId, options: MethodOptions): void;
function method(state: FactoryOptions, options: MethodOptions & WithId): void;
function method(state: FactoryOptions, options: MethodOptions) {
// implementation
}
By utilizing overloads, callers can invoke either call signature while the actual implementation is abstracted from them:
method({}, {}) // results in an error
method({id: 1}, {}) // valid
method({}, {id: 1}) // valid
However, dealing with this scenario within the implementation context is more challenging. There isn't a simple way to convince the compiler that a value of type
[number, number | undefined] | [number | undefined, number]
has a defined value at either index. Unfortunately, treating such a union as a
discriminated union doesn't yield the desired result. While a custom
type guard could potentially solve this, it might be excessive.
Instead, employing a type assertion allows us to work around this limitation:
function method(state: FactoryOptions & WithId, options: MethodOptions): void;
function method(state: FactoryOptions, options: MethodOptions & WithId): void;
function method(state: FactoryOptions, options: MethodOptions) {
const id = (typeof state.id !== "undefined"
? state.id
: options.id) as number; // assert as number
console.log(id.toString()); // works correctly now
}
Furthermore, I opted to replace your check on state.id
with a ternary operation to handle cases where 0
may cause issues due to its falsy nature. This ensures that id
maintains its numeric nature as intended. Though this may not align completely with your expectations, it serves as the most viable solution given the circumstances. Best of luck!
Link to code