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