0

How, if at all, can TypeScript define an array that constrains a specified object key to be distinct?

Here is a demo that indicates the requirements.

type Magician = {
  distinctKey: string;
  lastName: string;
};

// How, if at all, can we make line 19 be a compiler error?
type ObjectArrayWithDistinctKey<TItem, TKey> = TItem[]; 

const example: ObjectArrayWithDistinctKey<Magician, 'distinctKey'> = [
  {
    distinctKey: 'ammar1956',
    lastName: 'Ammar'
  },
  {
    distinctKey: 'vernon1894',
    lastName: 'Vernon'
  },
  {
    distinctKey: 'ammar1956', // error!
    lastName: 'Ammar'
  }
];

I appreciate that this is a job for something like a Map not an array.

1
  • My best solution to this runs afoul of a bug in TypeScript which will place an error on the first object in the array no matter where the error is really originating. Not sure it's worth it. 😔 Commented May 31, 2019 at 2:08

1 Answer 1

1

No this is a behavior that isn't possible in Arrays, the reason is that Arrays can only be of a single type "A", A's cannot compare themselves against other A's.

This is a behavior that can however be achieved with tuple, or variable length tuples.

EDIT: There are many ways to achieve this with tuples, i would say there is no best way it really depends on your the whole situation, that's why i didn't add a solution however this is atleast one way is to turn a Union MagicianA | MagicianB into permutations of tuples [MagicianA | MagicianB] | [MagicianB, MagicianA] this allows the ordering to not matter but to force distinct keys and length.

interface Magician<DistinctKey extends "ammar1956" | "vernon1894"> {
  distinctKey: DistinctKey;
  lastName: string;
};
type AmmarMagician = Magician<"ammar1956">;
type VernonMagician = Magician<"vernon1894">;

/* Easiest quality of life solution */
type Overwrite<T, S extends any> = { [P in keyof T]: S[P] };
type TupleUnshift<T extends any[], X> = T extends any ? ((x: X, ...t: T) => void) extends (...t: infer R) => void ? R : never : never;
type TuplePush<T extends any[], X> = T extends any ? Overwrite<TupleUnshift<T, any>, T & { [x: string]: X }> : never;

type UnionToTuple<U> = UnionToTupleRecursively<[], U>;

type UnionToTupleRecursively<T extends any[], U> = {
    1: T;
    0: UnionToTupleRecursively_<T, U, U>;
}[[U] extends [never] ? 1 : 0]

type UnionToTupleRecursively_<T extends any[], U, S> =
    S extends any ? UnionToTupleRecursively<TupleUnshift<T, S> | TuplePush<T, S>, Exclude<U, S>> : never;
/* End Easiest qulaity of life solution */


const example: UnionToTuple<AmmarMagician | VernonMagician> = [
  {
    distinctKey: 'ammar1956',
    lastName: 'Ammar'
  },
  {
    distinctKey: 'vernon1894',
    lastName: 'Vernon'
  },
]; // works


const example2: UnionToTuple<AmmarMagician | VernonMagician> = [
  {
    distinctKey: 'vernon1894',
    lastName: 'Vernon'
  },
    {
    distinctKey: 'ammar1956',
    lastName: 'Ammar'
  },
]; // works swapped



const example3: UnionToTuple<AmmarMagician | VernonMagician> = [
  {
    distinctKey: 'vernon1894',
    lastName: 'Vernon'
  },
    {
    distinctKey: 'ammar1956',
    lastName: 'Ammar'
  },
    {
    distinctKey: 'ammar1956',
    lastName: 'Ammar'
  },
]; // fails duplicates.

Or you can also do something like this for variable length....

type ValidKeys = "ammar1956" | "vernon1894" | "UnknownMagician"
interface Magician<DistinctKey extends ValidKeys> {
  distinctKey: DistinctKey;
  lastName: string;
};
type AmmarMagician = Magician<"ammar1956">;
type VernonMagician = Magician<"vernon1894">;
type UnknownMagician = Magician<"UnknownMagician">;

/* Easiest quality of life solution */
type MagiciansArray = [AmmarMagician, VernonMagician, ...UnknownMagician[]];
/* End Easiest qulaity of life solution */
const example: MagiciansArray = [
  {
    distinctKey: 'ammar1956',
    lastName: 'Ammar'
  },
  {
    distinctKey: 'vernon1894',
    lastName: 'Vernon'
  },
  {
      distinctKey: "UnknownMagician",
      lastName: "hello"
  }
]; // works
Sign up to request clarification or add additional context in comments.

4 Comments

If it isn't too much to ask, how would you define such a thing with a tuple? I appreciate that this is outside the scope of the initial question, and I might go ahead and ask a new question re: tuples instead.
What documentation, if any, can you provide to back up the answer?
Sure i'll post the tuple based solution. (in a second)
There is no such documentation that exists, however it is inheriently a type-based problem there is no type-level feature of arrays that allows the type-parameter "A" to compare itself against other "A"'s in the Array because there is only a single "A"

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.