2
export interface IGUser {
  biography: string;
  id: string;
  ig_id: string;
  followers_count: number;
  follows_count: number;
  media_count: number;
  name: string;
  profile_picture_url: string;
  shopping_product_tag_eligibility: boolean;
  username: string;
  website: string;
}

const values: keyof IGUser = ['biography', ''];

enter image description here

How can I implement that I can only allow one instance of biography in the array?

4
  • 2
    I don't think that's possible Commented Oct 13, 2023 at 7:45
  • 1
    This is not effectively expressible as a type. The first thing you should try is replacing it with an object literal, which is the right data structure for a set of unique keys. If you still want a tuple your options are: 1) A union of all possible tuple combinations (not efficient) 2) A constrained identity function or similar tool for enforcing type constraints, possibly paired with branded types to disallow bypassing validation (efficient but probably overcomplicated). Commented Oct 13, 2023 at 9:28
  • i was thinking about option 1 but yeah, it's very not efficient. and option 2 is, i don't have enough knowledge of typescript to do it. thank you! Commented Oct 13, 2023 at 15:41
  • @sailybra i think i will use object literals, i have no other option. thanks man! :D Commented Oct 13, 2023 at 15:57

3 Answers 3

2

You can't do that with typing only, but you could run it through a function that strips out duplicates: Delete duplicate elements from an array

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

1 Comment

i already have this implemented, i was trying to achieve it with types only. but it's very hard or not possible? :( thanks man
1

Well, it is possible but only to a certain amount of keys. At some point the calculation of that unique array literal type will become to expensice because it is dependent on the length of your Union Type (keyof IGUser). You'll get an "Type instantiation is excessively deep and possibly infinite" error.

Anyway, inspired by this answer by Mu Tsun Tsai on the question Unordered tuple type I came up with the following. Using a Mapped Type and Exclude you can ensure that all literals in your array are unique . You essentially compute all valid combination based on your initial object keys union. Note that this approach disallows any duplicate literals, not just "biography"

interface IGUser {
  biography: string;
  name: string;
  username: string;
//   id: string;
//   ig_id: string;
//   followers_count: number;
//   follows_count: number;
//   media_count: number;
//   profile_picture_url: string;
//   shopping_product_tag_eligibility: boolean;
//   website: string;
}

type SingleBiographyKeyArray<T extends string, R extends any[] = []> = R | {
    [K in T]: SingleBiographyKeyArray<Exclude<T, K>, [K, ...R]>
}[T];

let values: SingleBiographyKeyArray<keyof IGUser> = []; // valid
values = ["biography"] // valid
values = ["biography", "name", "username"] // valid

values = ["biography", "biography", "name"];
// ~~~ invalid
values = ["biography", "username", "name", "name"];
// ~~~ invalid

TypeScript Playground

1 Comment

yeah you're right :( thanks man! appreciate it
1

Given you have a specific set of allowed values, you may do it like https://tsplay.dev/NlXgrN

type ValidateUniqueArray<T, A extends T[]> =
  | A extends [infer F, ...infer L]
  ? (L extends Exclude<T, F>[]
    ? [F, ...ValidateUniqueArray<Exclude<T, F>, L>]
    : [F, Exclude<T, F>?]
  ) : [T?, ...unknown[]]
  ;

type ValidateCompleteUniqueArray<T, A extends any[]> =
  | [T] extends [never]
  ? []
  : A extends [infer F, ...infer L]
  ? (F extends T
    ? [F, ...ValidateCompleteUniqueArray<Exclude<T, F>, L>]
    : [T, ...ValidateCompleteUniqueArray<T, L>]
  ) : [T]
  ;

type conforms<T, V> = T extends V ? T : V;

function uniqueArray<T>() {
  return {
    partial<A extends T[]>(a: conforms<A, ValidateUniqueArray<T, A>>): A {
      return a as A;
    },
    complete<A extends T[]>(a: conforms<A, ValidateCompleteUniqueArray<T, A>>): A {
      return a as A;
    },
  }
}

let a1 = uniqueArray<1 | 2 | 3 | 4>().partial([
  1, 2, 3, 4 // ok
])
let a2 = uniqueArray<1 | 2 | 3 | 4>().partial([
  1, 1 // Type '1' is not assignable to type '2 | 3 | 4'.(2322)
])
let a3 = uniqueArray<1 | 2 | 3 | 4>().partial([
  1, 'foo' // Type 'string' is not assignable to type '2 | 1 | 3 | 4'.(2322)
])

let b1 = uniqueArray<1 | 2 | 3 | 4>().complete([
  1, 2, 3, 4 // ok
])
let b2 = uniqueArray<1 | 2 | 3 | 4>().complete([
  1, 2, 3, 1 // Type '1' is not assignable to type '4'.(2322)
])
let b3 = uniqueArray<1 | 2 | 3 | 4>().complete([
  1, 2, 3, 'foo' // error
])
let b4 = uniqueArray<1 | 2 | 3 | 4>().complete([
  1, 2, 3 // Argument of type '[1, 2, 3]' is not assignable to parameter of type '[1, 2, 3, 4]'.
])

1 Comment

thanks man, this works! but not what i wanted but it works. thanks a lot :D

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.