Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep Omit with typescript

Tags:

typescript

Is it possible to maintain type coverage on a function that deeply removes all instances of a key in an object?

My function looks like this.

function omitDeep<T extends object>(obj: T, key: string): TWithoutProvidedKey {
  return JSON.parse(
    JSON.stringify(obj),
    (key: string, value: any) => key === "__typename" ? undefined : value
  );
}

Is there any way to make TWithoutProvidedKey a reality?

like image 252
TLadd Avatar asked Apr 05 '19 16:04

TLadd


People also ask

How does omit work in TypeScript?

The TypeScript Omit utility type Like the Pick type, the Omit can be used to modify an existing interface or type. However, this one works the other way around. It will remove the fields you defined. We want to remove the id field from our user object when we want to create a user.

How do you omit multiple types in TypeScript?

To omit multiple keys from an object, pass a union of string literals in K .

What is record <> in TypeScript?

TypeScript Records are a great way to ensure consistency when trying to implement more complex types of data. They enforce key values, and allow you to create custom interfaces for the values. The TypeScript Record type was implemented in TypeScript 2.1, and takes the form Record<K, T> .

What is ReturnType TypeScript?

The ReturnType in TypeScript is a utility type which is quite similar to the Parameters Type. It let's you take the return output of a function, and construct a type based off it.


2 Answers

The answers here were inspiring. I had some small issues with TypeScript 4.0 that I was able to work out. I'm maintaining it as a gist: https://gist.github.com/ahuggins-nhs/826906a58e4c1e59306bc0792e7826d1. Hope this helps some people, especially those wanting to deal with Partial utility in a deep omit.

/** Union of primitives to skip with deep omit utilities. */
type Primitive = string | Function | number | boolean | Symbol | undefined | null

/** Deeply omit members of an array of interface or array of type. */
export type DeepOmitArray<T extends any[], K> = {
    [P in keyof T]: DeepOmit<T[P], K>
}

/** Deeply omit members of an interface or type. */
export type DeepOmit<T, K> = T extends Primitive ? T : {
    [P in Exclude<keyof T, K>]: //extra level of indirection needed to trigger homomorhic behavior
        T[P] extends infer TP ? // distribute over unions
        TP extends Primitive ? TP : // leave primitives and functions alone
        TP extends any[] ? DeepOmitArray<TP, K> : // Array special handling
        DeepOmit<TP, K>
        : never
}

/** Deeply omit members of an array of interface or array of type, making all members optional. */
export type PartialDeepOmitArray<T extends any[], K> = Partial<{
    [P in Partial<keyof T>]: Partial<PartialDeepOmit<T[P], K>>
}>

/** Deeply omit members of an interface or type, making all members optional. */
export type PartialDeepOmit<T, K> = T extends Primitive ? T : Partial<{
    [P in Exclude<keyof T, K>]: //extra level of indirection needed to trigger homomorhic behavior
        T[P] extends infer TP ? // distribute over unions
        TP extends Primitive ? TP : // leave primitives and functions alone
        TP extends any[] ? PartialDeepOmitArray<TP, K> : // Array special handling
        Partial<PartialDeepOmit<TP, K>>
        : never
}>
like image 41
aaron.huggins Avatar answered Sep 17 '22 15:09

aaron.huggins


This can easily be done, you just need to use mapped types to recurse down the properties:

type Primitive = string | Function | number | boolean | Symbol | undefined | null 
type DeepOmitHelper<T, K extends keyof T> = {
    [P in K]: //extra level of indirection needed to trigger homomorhic behavior 
        T[P] extends infer TP ? // distribute over unions
        TP extends Primitive ? TP : // leave primitives and functions alone
        TP extends any[] ? DeepOmitArray<TP, K> : // Array special handling
        DeepOmit<TP, K> 
        : never
}
type DeepOmit<T, K> = T extends Primitive ? T : DeepOmitHelper<T,Exclude<keyof T, K>> 

type DeepOmitArray<T extends any[], K> = {
    [P in keyof T]: DeepOmit<T[P], K>
}
type Input =  {
    __typename: string,
    a: string,
    nested: {
        __typename: string,
        b: string
    }
    nestedArray: Array<{
        __typename: string,
        b: string
    }>
    nestedTuple: [{
        __typename: string,
        b: string
    }]
}

type InputWithoutKey = DeepOmit<Input, '__typename'>

let s: InputWithoutKey = {
    a: "",
    nested: {
        b:""
    },
    nestedArray: [
        {b: ""}
    ],
    nestedTuple: [
        { b: ""},
    ]
}

Just a caveat, this works on 3.4, the handling of mapped types on arrays and tuples has changed recently, so depending on version you might need to handle arrays as a special case.

like image 110
Titian Cernicova-Dragomir Avatar answered Sep 19 '22 15:09

Titian Cernicova-Dragomir