Ts-toolbelt: Recursively add property to nested objects of type

Created on 23 Oct 2019  路  6Comments  路  Source: millsp/ts-toolbelt

馃 Question

Describe your question

Is there a way with ts-toolbelt to recursively add a specific property to all nested objects of a given type?

Example of what I am trying to do:

interface Address {
  city: string
  country: string
}
interface User {
  id: string
  name: string
  address: Address
}
interface Session {
  id: string
  user: User
}

Convert this to:

interface SessionTypenames {
  session: {
   __typename: string // new property
   id: string

   user: {
      __typename: string // new property
      id: string
      name: string

      address: {
         __typename: string // new property
         city: string
         country: string
      }
  }
}

This would be very helpful in defining mocked data from server when using Apollo's MockedProvider which requires adding this data to the mocked data, but not defined by the original graphql schema types.

Search tags, topics

typescript #graphql #apollo-client

question

Most helpful comment

@pirix-gh Manual nesting (NestedSession) doesn't seem necessary - see this code snippet:

import {A} from 'ts-toolbelt'

interface Address {
  city: string
  country: string
}
interface User {
  id: string
  name: string
  address: Address
}
interface Session {
  id: string
  user: User
}

type AddTypeNames<O> =
  O extends object
    // A.Compute here isn't necessary, but produces nicer readable types
    ? A.Compute<{__typename: string} & {[K in keyof O]: AddTypeNames<O[K]>}>
    : O;

type SessionTypenames = AddTypeNames<Session>;

Feel free to use this in any project - the above code snippet is free software released into the public domain.

All 6 comments

There is no magic solution. If you'd like to do this generically, you'd have to write a custom type.

import {O} from "ts-toolbelt";

interface Address {
    city: string
    country: string
  }
interface User {
    id: string
    name: string
    address: Address
}
interface Session {
    id: string
    user: User
}

type NestedSession = { // we nest like suggested
    session: O.MergeUp<Session, {
        user: O.MergeUp<User, {
            address: Address
        }, 'deep'>
    }, 'deep'>
}

type AddTypeNames<O extends object> = {
    [K in keyof O]: O[K] extends object
                    // if it is an object then recursive call to add field
                    ? AddTypeNames<O.MergeUp<O[K], {__typename: string}>>
                    : O[K] // if not, then we'll leave the field untouched
} & {} // <- force to display neatly (try to remove this to see)

type SessionTypenames = AddTypeNames<NestedSession>

@JeremyJonas do you need a generic type to do this for you?

@pirix-gh Manual nesting (NestedSession) doesn't seem necessary - see this code snippet:

import {A} from 'ts-toolbelt'

interface Address {
  city: string
  country: string
}
interface User {
  id: string
  name: string
  address: Address
}
interface Session {
  id: string
  user: User
}

type AddTypeNames<O> =
  O extends object
    // A.Compute here isn't necessary, but produces nicer readable types
    ? A.Compute<{__typename: string} & {[K in keyof O]: AddTypeNames<O[K]>}>
    : O;

type SessionTypenames = AddTypeNames<Session>;

Feel free to use this in any project - the above code snippet is free software released into the public domain.

Yes you're right, I somehow missed the types were already nested :man_facepalming:

type AddTypeNames<O> =
  O extends object
    // A.Compute here isn't necessary, but produces nicer readable types
    ? A.Compute<{__typename: string} & {[K in keyof O]: AddTypeNames<O[K]>}>
    : O;

This works perfectly. Exactly what I was looking for!

Thank you!

type AddTypeNames<O> =
  O extends object
    // A.Compute here isn't necessary, but produces nicer readable types
    ? A.Compute<{__typename: string} & {[K in keyof O]: AddTypeNames<O[K]>}>
    : O;

This works perfectly. I had to actually remove the A.Compute because that was causing the outcome to be any, while removing it output correctly nested types as expected.

Thank you!

@JeremyJonas do you need a generic type to do this for you?

This works great, thanks @pirix-gh! Exactly what I was looking for (minus the additional nested type as mentioned ;)

Was this page helpful?
0 / 5 - 0 ratings