0

By "Or split type" I mean string | number | boolean. If you have a better name, I'll update the question.

I'm working on an interface which contains a list of tags, similar to this:

type Tags = "big" | "small" | "sharp" | "dull";
interface Shape {
  name: string;
  tags: Tags[];
}

Later in the code, I'd like to list out all the possible tags. In my actual code, the amount of tags is far greater than four, so I want to use type assertion to make sure I list them all. How can I do this?

Two ways I can imagine are:

  1. Creating an array from an "or split type," which is impossible for obvious reasons (nested conditions).
  2. Asserting a type based off an array of strings. This one seems fairly possible, I know that document.createElement("a" | "div" | "span"...) does something magical with this, though I can't find the right keywords to figure out what it's called.

My ideal would be something along these lines:

// Not real code
const Tags = ["big", "small", "sharp", "dull"];
interface Shape {
  name: string;
  tags: Array<item in Tags>;
}

So, is there a way to make an array of strings act like an or split type?

Seph Reed
  • 8,797
  • 11
  • 60
  • 125
  • Why not use an enum? – Erik Philips Oct 01 '18 at 17:04
  • @SephReed They're called [union types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types). – Patrick Roberts Oct 01 '18 at 17:14
  • Possible duplicate of [TypeScript String Union to String Array](https://stackoverflow.com/questions/44480644/typescript-string-union-to-string-array/45486495#45486495) (answer is about turning an array to a union, which seems to be your question) – jcalz Oct 01 '18 at 17:48

2 Answers2

1

In my actual code, the amount of tags is far greater than four, so I want to use type assertion to make sure I list them all. How can I do this?

I had to do this recently and we had two choices.

  1. Object with the attributes as properties of the object
  2. An array and an Interface, so any time you add a new item to your tags you add to both the array and the object

It seems like it's a matter of personal preference

I would use an object Tags to enforce type safety as you want

class Tags {
    public one: string;
    public two: string;
    public three: string;
    constructor({ one, two, three }: Tags) {
        this.one = one;
        this.two = two;
        this.three = three;
    }
}

interface Shape {
    name: string;
    tags: Tags;
}

let a = new Tags({}); // <-- This will fail because is not type Tag
a = new Tags({ one: '1' }); // <-- This will fail because it hasn't type two or type three
a = new Tags({ one: '1', two: '2', three: '3' }); // <-- This will pass

The second option we did is as follows:

const TagsArray = ["big", "small", "sharp", "dull"]
interface Tags {
  big: string;
  small: string;
  sharp: string;
  dull: string;
}

And everywhere we use them or pass around a Tag we pass it as an Object:

interface Shape {
  name: string;
  tags: Tags;
}

Usage as with everything is depends on your application, for us we needed to pass an array to some services, and type checkings on objects with the array values as properties, so we went with 2 because this prevents converting the Array into Keys via Object.keys(Tags) and whatever the service returns is type Checked using Shape

Claudiordgz
  • 3,023
  • 1
  • 21
  • 48
0

The solution I ended up using was to create a file for housing all the tags containing two exports:

  1. the Tags type which is a union type of each string that a tag can be
  2. the tagList array, a listing of properties from an object of type {[key in Tags]: null}. This object type requires every tag to be represented as a prop (and no more).

export type Tags = "featured" | "design" | "fabrication" | "development" | "audio" 
| "imagery" | "social" | "leadership" | "writing" | "3d" | "interactive" | "work";

// the following typedef will assert that every tag is added as a prop
const tagsObject: {[key in Tags]: null} = {
    featured: null,
    design: null,
    fabrication: null,
    development: null,
    audio: null,
    imagery: null,
    social: null,
    leadership: null,
    writing: null,
    "3d": null,
    interactive: null,
    work: null,
}
// because the tagsObject must have all tags as props to pass type assertion
// tagList will always contain every tag
const tagList: Tags[] = Array.from(Object.keys(tagsObject)) as any;
export { tagList };
Seph Reed
  • 8,797
  • 11
  • 60
  • 125