Hone logo
Hone
Problems

Implement a Generic Split Type in TypeScript

In TypeScript, we often need to manipulate types to create more specific or generalized versions. This challenge focuses on creating a utility type that can "split" a given type into two parts based on a discriminant property. This is particularly useful for handling discriminated unions where you might want to isolate specific variants of a union.

Problem Description

Your task is to implement a generic TypeScript utility type called Split<T, K>. This type should take two generic arguments:

  1. T: The type from which to split. This is expected to be a union of objects.
  2. K: The key of the discriminant property. This key will be used to determine how to split the union.

The Split type should return a tuple where:

  • The first element of the tuple represents all members of the union T that do not have the specific value of the discriminant property K.
  • The second element of the tuple represents all members of the union T that do have the specific value of the discriminant property K.

Essentially, you are filtering the union based on the presence of a specific property value.

Key Requirements:

  • The Split type must be generic and accept T (a union of objects) and K (a string literal representing the discriminant key) as type parameters.
  • It should return a tuple [TypeWithoutK, TypeWithK].
  • TypeWithoutK should be a union of all members of T that do not possess the property K.
  • TypeWithK should be a union of all members of T that do possess the property K.

Expected Behavior:

When Split<T, K> is applied, it should analyze each member of the union T. If a member has the property K, it belongs to the second part of the tuple. If it does not have the property K, it belongs to the first part.

Edge Cases:

  • What happens if T is not a union of objects? (While not explicitly tested in examples, consider the behavior.)
  • What if K refers to a property that doesn't exist on any of the union members?
  • What if a union member has the property K, but its value is undefined? (For this challenge, consider undefined as a value, so if K is undefined, it should be included in TypeWithK).

Examples

Example 1:

type Shape =
  | { type: "circle"; radius: number }
  | { type: "square"; sideLength: number }
  | { type: "triangle"; base: number; height: number };

type NonCircles = Split<Shape, "type">[0];
// Expected: { type: "square"; sideLength: number } | { type: "triangle"; base: number; height: number }

type Circles = Split<Shape, "type">[1];
// Expected: { type: "circle"; radius: number }

Explanation: We are splitting the Shape union based on the type property. The Split<Shape, "type"> type should conceptually separate the shapes where type is not "circle" and those where type is "circle". The first element of the tuple contains non-circle shapes, and the second contains the circle shape.

Example 2:

type Event =
  | { name: "click"; x: number; y: number }
  | { name: "keydown"; key: string }
  | { name: "scroll"; delta: number };

type NonKeyEvents = Split<Event, "key">[0];
// Expected: { name: "click"; x: number; y: number } | { name: "scroll"; delta: number }

type KeyEvents = Split<Event, "key">[1];
// Expected: { name: "keydown"; key: string }

Explanation: Here, we split the Event union based on the presence of the key property. Events that don't have a key property go into the first part of the tuple, and the event that does have a key property goes into the second.

Example 3: (Edge Case - Discriminant property not present)

type DataPoint =
  | { id: 1; value: string }
  | { id: 2; timestamp: Date };

type WithoutTimestamp = Split<DataPoint, "timestamp">[0];
// Expected: { id: 1; value: string }

type WithTimestamp = Split<DataPoint, "timestamp">[1];
// Expected: { id: 2; timestamp: Date }

Explanation: This example demonstrates splitting based on a property that is not present on all members of the union. DataPoint members without the timestamp property are in the first part, and the member with it is in the second.

Constraints

  • The type T is assumed to be a union of object types.
  • The type K must be a string literal type representing the key to discriminate by.
  • Your solution should use only standard TypeScript features (no external libraries).
  • The solution should be efficient in terms of type computation.

Notes

Consider how you can use conditional types and mapped types to iterate over the union T and check for the presence of the property K. Think about how to construct the two resulting unions within a tuple. You might find the keyof operator and type inference useful. Remember that in TypeScript, keyof T returns a union of keys of an object T. You are checking for the presence of a specific key K on each member of the union T.

Loading editor...
typescript