0

I use Graphql codegen to generate types from our schema. I need access to nested types and have commonly used the following method:

 type Iresponse = myType[level1][level2]

This works great when there are no Maybes, meaning a possibility of it being null. When types are defined as "maybe" I cannot use it and it it throws an error.

Does anyone have a practical solution to specify these nested types? I dont wanna wrap every step in a number of nonNullable generics...

Thank you in advance !

Michael
  • 947
  • 7
  • 17

3 Answers3

3

Arbitrary depth:

type Get<T, X> = X extends keyof T ? T[X] : never
type Path<T, P extends any[]> = P extends [infer X, ...infer Y] ? Path<Get<NonNullable<T>, X>, Y> : T

type Iresponse = Path<myType, ["level1", "level2", "response"]>

Playground link

Jean-Alphonse
  • 800
  • 4
  • 10
  • Pretty interesting example. I think you need to handle also this case:`type Resp3b = Path` when array may contain wrong props. You can also consider computing the permutation of all allowed props like `KeysUnion` https://stackoverflow.com/questions/69126879/typescript-deep-keyof-of-a-nested-object-with-related-type – captain-yossarian from Ukraine Sep 13 '21 at 15:19
  • Indeed, it does not check that the path is valid, nor does it provide autocompletion. – Jean-Alphonse Sep 13 '21 at 15:23
  • You can event try this https://stackoverflow.com/questions/69126879/typescript-deep-keyof-of-a-nested-object-with-related-type . See last example – captain-yossarian from Ukraine Sep 13 '21 at 15:25
2

You can use built in NonNullable util

type MyType = {
    foo: {
        bar: [1, 2, 4] | null
    } | null
}


type GetNullable<T, Prop extends keyof NonNullable<T>> = NonNullable<T>[Prop]

// [1, 2, 4] | null
type NestedData = GetNullable<MyType['foo'], 'bar'>


Playground

OR

type Truthy<T> = NonNullable<T>

type GetData = Truthy<Truthy<MyType['foo']>>['bar']

If you need more sophisticated util, please see this example last row and related answer

type Structure = {
    user: {
        tuple: [42],
        emptyTuple: [],
        array: { age: number }[]|null
    }
}

type Values<T> = T[keyof T]

type IsNever<T> = [T] extends [never] ? true : false;

type IsTuple<T> =
    (T extends Array<any> ?
        (T['length'] extends number
            ? (number extends T['length']
                ? false
                : true)
            : true)
        : false)

type IsEmptyTuple<T extends Array<any>> = T['length'] extends 0 ? true : false

type HandleDot<
    Cache extends string,
    Prop extends string | number
    > =
    Cache extends ''
    ? `${Prop}`
    : `${Cache}.${Prop}`

type HandleObject<Obj, Cache extends string> = {
    [Prop in keyof Obj]:
    | HandleDot<Cache, Prop & string>
    | Path<Obj[Prop], HandleDot<Cache, Prop & string>>
}[keyof Obj]

type Path<Obj, Cache extends string = ''> =
    (Obj extends PropertyKey
        ? Cache
        : (Obj extends Array<unknown>
            ? (IsTuple<Obj> extends true
                ? (IsEmptyTuple<Obj> extends true
                    ? Path<PropertyKey, HandleDot<Cache, -1>>
                    : HandleObject<Obj, Cache>)
                : Path<Obj[number], HandleDot<Cache, number>>)
            : HandleObject<Obj, Cache>)
    )

type WithDot<T extends string> = T extends `${string}.${string}` ? T : never


type Acc = Record<string, any>

type ReducerCallback<Accumulator extends Acc, El extends string> =
    El extends keyof Accumulator ? Accumulator[El] : El extends '-1' ? never : Accumulator

type Reducer<
    Keys extends string,
    Accumulator extends Acc = {}
    > =
    Keys extends `${infer Prop}.${infer Rest}`
    ? Reducer<Rest, ReducerCallback<Accumulator, Prop>>
    : Keys extends `${infer Last}`
    ? ReducerCallback<Accumulator, Last>
    : never


type BlackMagic<T> = T & {
    [Prop in WithDot<Extract<Path<Structure>, string>>]: Reducer<Prop, T>
}

type Result = Reducer<'user.array', Structure>

Playground

0

Thanks guys for your answers, they were really helpful!

What I ended up doing is using "typescript-compatibility" with the graphql generator. It adds types and subtypes that may be null too. Basically what it does is just wrap all the nested types in NonNullable types.

Michael
  • 947
  • 7
  • 17