0

I have the following array:

  const notifications : {
    title: string;
    type: 'slack' | 'email';
  }[] = [
    { title: 'Slack', type: 'slack' },
    { title: 'Email', type: 'email' },
  ];

And the corresponding model object:

const model = {
    slack: {
      payload: {
        a: ''
      }
    },
    email: {
      payload: {
        b: ''
      }
    }
  }

I want to create a new array that merges both objects:

const result = notifications.map((n) => {
    return {
      ...n,
      ...model[n.type]
    }
})

But how can I force the type to be based on the model type:

 result: [
   { type: 'slack', payload: { a: string } },
   { type: 'email', payload: { b: string } },
 ]
undefined
  • 6,366
  • 12
  • 46
  • 90

1 Answers1

2

Here you have:


const notifications = [
  { title: 'Slack', type: 'slack' },
  { title: 'Email', type: 'email' },
] as const;

const model = {
  slack: {
    payload: {
      a: ''
    }
  },
  email: {
    payload: {
      b: ''
    }
  }
} as const


type Model = typeof model;

type notifications = typeof notifications;

type MapPredicate<T> = T extends notifications[number] ? T['type'] extends keyof Model ? { readonly type: T['type'] } & Model[T['type']] : 1 : 2

// http://catchts.com/tuples#map
type Mapped<
  Arr extends Array<unknown>,
  Result extends Array<unknown> = []
  > = Arr extends []
  ? []
  : Arr extends [infer H]
  ? [...Result, MapPredicate<H>]
  : Arr extends [infer Head, ...infer Tail]
  ? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
  : Readonly<Result>;

// https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type/50375286#50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

// https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type UnionToOvlds<U> = UnionToIntersection<
  U extends any ? (f: U) => void : never
>;

// https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;

// https://stackoverflow.com/questions/53953814/typescript-check-if-a-type-is-a-union#comment-94748994
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

// http://catchts.com/union-array
type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
  ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
  : [T, ...A];

type Arr = notifications[number]

const mapping = <T extends typeof notifications>(data: T) => {
  const result = data.map((n) => {
    const { title, ...rest } = n;
    return {
      ...rest,
      ...model[n.type]
    }
  })

/**
 * AFAIK, it is impossible to omit this type casting
 */
  return result as Mapped<UnionToArray<Arr>>
}

const result = mapping(notifications)

type Return = [
  { type: 'slack', payload: { a: string } },
  { type: 'email', payload: { b: string } },
]

Because, JS arrays are mutable, I'm not sure it is possible to type result as an array with exactly two elements.

  • @undefined here https://stackoverflow.com/questions/64112702/possible-to-use-array-prototype-map-on-tuple-in-typescript-while-preserving-tu/64117673#64117673 you can find related question – captain-yossarian from Ukraine Jan 17 '21 at 23:07