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 } },
 ]
4

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

@undefined here stackoverflow.com/questions/64112702/… you can find related question

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.