I have an interface like this:
export interface Campaign {
id: string
orders?: number
avgOrderValue?: number
optionalAttributes: string[]
attributeValues: {
optionalAttributes: CampaignAttribute[]
mandatoryAttributes: CampaignAttribute[]
values?: { [key: string]: unknown }
}
created: number
lastUpdated: number
}
And I want to create a type out of this for my form that needs to omit the attributeValues.optionalAttributes and attributeValues.mandatoryAttributes from the interface.
I was thinking that maybe Typescript can do something like this:
export type CampaignFormValues = Omit<Campaign, 'attributeValues.mandatoryAttributes'>
But this doesn't work.
I used the answer from this question: Deep Omit with typescript But this answer just deep omits every matched key, so using it like this:
export type CampaignFormValues = Omit<Campaign, 'optionalAttributes'>
Would also remove the root level optionalAttributes which I want to keep.
Is there any way to do a nested omit with Typescript?
type A = {
a: {
b: string
c: string
}
x: {
y: number
z: number,
w: {
u: number
}
}
}
type Primitives = string | number | boolean | symbol
/**
* Get all valid nested pathes of object
*/
type AllProps<Obj, Cache extends Array<Primitives> = []> =
Obj extends Primitives ? Cache : {
[Prop in keyof Obj]:
| [...Cache, Prop] // <------ it should be unionized with recursion call
| AllProps<Obj[Prop], [...Cache, Prop]>
}[keyof Obj]
type Head<T extends ReadonlyArray<any>> =
T extends []
? never
: T extends [infer Head]
? Head
: T extends [infer Head, ...infer _]
? Head
: never
type Tail<T extends ReadonlyArray<any>> =
T extends []
? []
: T extends [infer _]
? []
: T extends [infer _, ...infer Rest]
? Rest
: never
type Last<T extends ReadonlyArray<any>> = T['length'] extends 1 ? true : false
type OmitBase<Obj, Path extends ReadonlyArray<any>> =
Last<Path> extends true
? {
[Prop in Exclude<keyof Obj, Head<Path>>]: Obj[Prop]
} : {
[Prop in keyof Obj]: OmitBase<Obj[Prop], Tail<Path>>
}
// we should allow only existing properties in right order
type OmitBy<Obj, Keys extends AllProps<Obj>> = OmitBase<A, Keys>
type Result = OmitBy<A,['a', 'b']> // ok
type Result2 = OmitBy<A,['b']> // expected error. order should be preserved
Playground
More explanation you can find in my blog
Above solution works with deep nested types
If you want to use dot syntax prop1.prop2, consider next type:
type Split<Str, Cache extends string[] = []> =
Str extends `${infer Method}.${infer Rest}`
? Split<Rest, [...Cache, Method]>
: Str extends `${infer Last}`
? [...Cache, Last,]
: never
type WithDots = OmitBy<A, Split<'a.b'>> // ok
You need to create a new interface where attributeValues is overwritten:
export interface Campaign {
id: string
orders?: number
avgOrderValue?: number
optionalAttributes: string[]
attributeValues: {
optionalAttributes: CampaignAttribute[]
mandatoryAttributes: CampaignAttribute[]
values?: { [key: string]: unknown }
}
created: number
lastUpdated: number
}
interface MyOtherCampaign extends Omit<Campaign, 'attributeValues'> {
attributeValues: {
values?: { [key: string]: unknown }
}
}
let x:MyOtherCampaign;

Playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With