After coming across this specific query on SO, I wanted to delve into creating an Exact type.
My attempt at implementing something akin to a DeepExact
seems to be close:
// Desired type that should only accept Exact versions of
type Opts = {
firstName?: string;
parent?: string;
children?: { firstName?: string }[];
};
// Incoming type with unknown fields that should trigger rejections
type Args = {
firstName?: string;
parentId?: string;
children?: { firstName?: string; unknownField?: string }[];
};
const a: Args = null!;
// Compiles as intended, but aiming for parentId and children.unknownField to cause type failures
const o: Opts = a;
// Enforcing only the exact Opts type through this function
function onlyOpts<O>(o: Exact<Opts, O>): void {}
// Examples distinguishing known from unknown fields
const g1 = { firstName: "f" };
onlyOpts(g1);
const g2 = { parent: "p", children: [{ firstName: "c1" }] };
onlyOpts(g2);
// Failing examples involving unknown fields
const b1 = { firstName: "f", parentId: undefined };
onlyOpts(b1); // Correctly fails due to parentId being `never`
const b2 = { firstName: "f", children: [{ firstName: "c1", unknownField: "c1" }] };
onlyOpts(b2); // Should fail but does not, where unknownField still remains `string`
// Demonstrating that B2["children"][]["unknownField"] is still a string
type B2 = Exact<Opts, typeof b2>;
// Prototype of Exact/DeepExact
type Exact<T, U> = T &
{
[K in keyof U]: K extends keyof T
? T[K] extends Array<infer TU>
? U[K] extends Array<infer UU>
? Array<Exact<TU, UU>>
: never
: U[K]
: never;
};
Despite my efforts, the example involving b2
is not failing in the deep/recursive aspect. The type B2
retains children
with unknownField: string
instead of unknownField: never
.
I had expected isolating TU
and UU
for separate inference, followed by recursion within Exact
would resolve the issue, but it hasn't.
Any insights or suggestions?