This is a follow up to this SO question to try and imitate an Exact type.
I think I have something close to DeepExact
working:
// This is the good type/that we want to only accept Exact versions of
type Opts = {
firstName?: string;
parent?: string;
children?: { firstName?: string }[];
};
// This is the incoming type that has unknown fields which should be rejected.
type Args = {
firstName?: string;
parentId?: string;
children?: { firstName?: string; unknownField?: string }[];
};
const a: Args = null!;
// This compiles as expected but I want parentId and children.unknownField to cause type failures
const o: Opts = a;
// function that enforces only the exact Opts type
function onlyOpts<O>(o: Exact<Opts, O>): void {}
// good examples, using known fields
const g1 = { firstName: "f" };
onlyOpts(g1);
const g2 = { parent: "p", children: [{ firstName: "c1" }] };
onlyOpts(g2);
// bad examples, using unknown fields
const b1 = { firstName: "f", parentId: undefined };
onlyOpts(b1); // correctly fails b/c parentId is `never`
const b2 = { firstName: "f", children: [{ firstName: "c1", unknownField: "c1" }] };
onlyOpts(b2); // does not fail but should, unknownField is still `string`
// type alias to show B2["children"][]["unknownField"] is still string
type B2 = Exact<Opts, typeof b2>;
// Prototype 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;
};
But my b2
example is not failing, i.e. the deep/recursive part. The B2
type has still children
with unknownField: string
instead of the unknownField: never
.
I thought that separately inferring about TU
and UU
, and then recursing them into Exact
would work, but for some reason it's not.
Any ideas?