Reputation: 7405
I'm creating a TypeScript definition file for a 3rd party js library. One of the methods allows for an options object, and one of the properties of the options object accepts a string from the list: "collapse"
, "expand"
, "end-expand"
, and "none"
.
I have an interface for the options object:
interface IOptions {
indent_size?: number;
indent_char?: string;
brace_style?: // "collapse" | "expand" | "end-expand" | "none"
}
Can the interface enforce this, so if you include an IOptions
object with the brace_style
property, it will only allow a string that is in the acceptable list?
Upvotes: 335
Views: 292368
Reputation: 10673
This was released in version 1.8 as "string literal types"
What's New in Typescript - String Literal Types
Example from the page:
interface AnimationOptions {
deltaX: number;
deltaY: number;
easing: "ease-in" | "ease-out" | "ease-in-out";
}
Upvotes: 472
Reputation: 69
This answer is a bit broad but can be extended to many use cases.
You would make use of MappedTypes
.
Here's an use case:
// Let's say that you have different string literals defining a 'type' property
// i.e: 'text' | 'video' | 'image' […]
// You would like each 'type' to 'enable' or 'disable' different
// properties based on the `type` property:
const fooObj: MyObject = {
type: "foo",
value: {
sharedProp: "shared",
fooProperty1: 123,
fooProperty2: "hello"
}
};
const barObj: MyObject = {
type: "bar",
value: {
sharedProp: "shared",
barProperty1: true,
barProperty2: new Date()
}
};
type CommonProps = {
sharedProp: "shared";
};
type FooProperties = {
fooProperty1: number;
fooProperty2: string;
// ... other foo properties
};
type BarProperties = {
barProperty1: boolean;
barProperty2: Date;
// ... other bar properties
};
type AllProperties = {
foo: FooProperties,
bar: BarProperties,
// ... other discriminators and their properties
};
type MyObject = {
[K in keyof AllProperties]: {
type: K;
value: AllProperties[K] & CommonProps;
};
}[keyof AllProperties];
Upvotes: 0
Reputation: 34315
Most likely you want the enum
options. But if you do not, and you really need your const
to exist, the keyOf
that some answers post here works, with a slight modification:
export const BRACE_STYLES = {
collapse: 'collapse',
'end-expand': 'end-expand',
expand: 'expand',
none: 'none'
}
export type BraceStyle = keyof typeof BRACE_STYLES
export interface IOptions {
indent_size?: number
indent_char?: string
brace_style?: BraceStyle
}
This will actually get you the "collapse" | "expand" | "end-expand" | "none"
effect you want, still allow the const
to exists, without the need of hardcoding the type as well.
Upvotes: 1
Reputation: 460
You can create a custom type as stated by others.
I would add to that, that you can also infer that created type from an object const:
export const braceStyles = {
collapse: "collapse",
expand: "expand",
end-expand: "end-expand",
none: "none"
}
export type braceStyle = typeof braceStyles[keyof typeof braceStyles]
export interface IOptions {
indent_size?: number;
indent_char?: string;
brace_style?: bracestyle;
}
That way you don't have to use enum and you can also use the object properties everywhere you need them, where they will be of type string, not type enum.member
Upvotes: 0
Reputation: 86076
enum
is probably the best solution, but if you have your values as keys of a const object and you can't change that, the syntax would be
brace_style?: typeof BRACE_STYLES[keyof typeof BRACE_STYLES];
where BRACE_STYLES
is the name of the const object
Upvotes: 1
Reputation: 3900
Try this
export type ReadingTypes = 'some'|'variants'|'of'|'strings';
export interface IReadings {
param:ReadingTypes
}
Edit: Many thanks for upvotes, but, as time passed and me evolved as a developer :), now in most of the cases I wouldn't recommend this approach anymore. Yes it still valid but the point is the construction above is very similar to enum structure so why not use enum instead (advantages below):
export enum ReadingTypes {
Some = 'some',
Variants = 'variants',
Of = 'of',
Strings = 'strings',
}
export interface IReadings {
param: ReadingTypes
}
Advantages: (Yes, might be it is more like IMHO, I understand, but, nonetheless)
if(item.reading === 'some') {
...
}
// vs
if(item.reading === ReadingTypes.Some) {
...
}
In first case when you read the code you could not catch, from the first glance, that .reading field can only contain few certain params, and not like, any string value.
Upvotes: 370
Reputation: 4849
I favour this approach because it avoids the need to have the same hard coded string in more than one place.
Its possible to make an enum where the values are strings
export enum VISIBILITY {
PUBLISH = "publish",
DRAFT = "draft"
}
This enum can then be used as a type on an interface or class
export interface UserOptions {
visibility: VISIBILITY
}
Upvotes: 16
Reputation: 36630
TS offers a typing to specific string values, which are called String literal types.
Here is an example of how to use them:
type style = "collapse" | "expand" | "end-expand" | "none";
interface IOptions {
indent_size?: number;
indent_char?: string;
brace_style1?: "collapse" | "expand" | "end-expand" | "none";
brace_style2?: style;
}
// Ok
let obj1: IOptions = {brace_style1: 'collapse'};
// Compile time error:
// Type '"collapsessss"' is not assignable to type '"collapse" | "expand" | "end-expand" | "none" | undefined'.
let obj2: IOptions = {brace_style1: 'collapsessss'};
Upvotes: 27
Reputation: 1418
function keysOf<T>(obj: T, key: keyof T) { return obj[key]; }
interface SomeInterface {
a: string;
}
const instance: SomeInterface = { a: 'some value'};
let value = keysOf<SomeInterface>(instance, 'b'); // invalid
value = keysOf<SomeInterface>(instance, 'a'); // valid
Upvotes: 2
Reputation: 5547
Maybe not exactly what you wanted, but Enum
s seem like a perfect solution for you.
enum BraceStyle {Collapse, Expand, EndExpand, None}
interface IOptions {
indent_size?: number;
indent_char?: string;
brace_style?: BraceStyle
}
Enums are, however, number-based. It means that during runtime a real value for e.g. BraceStyle.Collapse
will be 0 in this case. But you can use them with other, even non-typescript scripts, since they compile to objects. This is how BraceStyle
will look after compile&run:
{
0: "Collapse",
1: "Expand",
2: "EndExpand",
3: "None",
Collapse: 0,
Expand: 1,
EndExpand: 2,
None: 3
}
If you want strings instead, you can use a class with static members, as described here
Upvotes: 10