Typescript `enum` из строки JSON
Есть ли способ иметь переименование TypeScript, совместимое со строками из JSON?
Например:
enum Type { NEW, OLD }
interface Thing { type: Type }
let thing:Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW); // false
Я бы хотел, чтобы thing.type == Type.NEW
был правдой. Или, более конкретно, мне хотелось бы указать значения enum
, которые будут определены как строки, а не числа.
Мне известно, что я могу использовать thing.type.toString() == Type[Type.NEW]
, но это громоздко и, похоже, делает аннотацию типа enum запутанной и вводящей в заблуждение, что побеждает ее цель. JSON технически не снабжает действительным значением перечисления, поэтому я не должен вводить свойство в перечисление.
Итак, в настоящее время я использую строковый тип со статическими константами:
const Type = { NEW: "NEW", OLD: "OLD" }
interface Thing { type: string }
let thing:Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW); // true
Это дает мне использование, которое я хочу, но аннотация типа string
слишком широка и подвержена ошибкам.
Я немного удивлен тем, что надмножество JavaScript не содержит строковых переписей. Я что-то упускаю? Есть ли другой способ сделать это?
Обновить TS 1.8
Использование строковых литералов типов является еще одной альтернативой (спасибо @basaret), но для получения желаемого использования перечисления (выше) требуется определение ваши значения дважды: один раз в строковом литерале и один раз в качестве значения (константа или пространство имен):
type Type = "NEW" | "OLD";
const Type = {
NEW: "NEW" as Type,
OLD: "OLD" as Type
}
interface Thing { type: Type }
let thing:Thing = JSON.parse(`{"type": "NEW"}`);
alert(thing.type === Type.NEW); // true
Это работает, но требует много шаблонов, достаточно, чтобы я не использовал его большую часть времени. На данный момент я надеюсь, что предложение для string enums
в конечном итоге сделает дорожную карту.
Обновить TS 2.1
Новый поиск keyof
позволяет генерировать тип строкового литерала из ключей константы или пространства имен, что делает определение немного менее избыточное:
namespace Type {
export const OLD = "OLD";
export const NEW = "NEW";
}
type Type = keyof typeof Type;
interface Thing { type: Type }
const thing: Thing = JSON.parse('{"type": "NEW"}');
thing.type == Type.NEW // true
Обновить TS 2.4
TypeScript 2.4 добавлена поддержка перечислений строк! Вышеприведенный пример:
enum Type {
OLD = "OLD",
NEW = "NEW"
}
interface Thing { type: Type }
const thing: Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW) // true
Это выглядит почти идеально, но все еще есть страдание:
- Вам все равно придется записывать значение дважды, т.е.
OLD = "OLD"
, и нет подтверждения, что у вас нет опечатки, например NEW = "MEW"
... это уже укусило меня в реальном коде.
-
Есть некоторые странности (возможно, ошибки?) с тем, как проверяется тип перечисления, своя стенограмма строкового литерала, что было бы действительно правильным. Некоторые проблемы, с которыми я столкнулся:
enum Color { RED = "RED", BLUE = "BLUE", GREEN = "GREEN" }
type ColorMap = { [P in Color]: number; }
declare const color: Color;
declare const map: ColorMap;
map[color] // Error: Element implicitly has an 'any' type because type 'ColorMap' has no index signature.
const red: Color = "RED"; // Type '"RED"' is not assignable to type 'Color'.
const blue: Color = "BLUE" as "RED" | "BLUE" | "GREEN"; // Error: Type '"RED" | "BLUE" | "GREEN"' is not assignable to type 'Color'.
Эквивалентный код с enum Color
, замененный строковыми литералами, работает нормально...
Да, я думаю, что у меня есть OCD, я просто хочу, чтобы мои идеальные JS перечисления.:)
Ответы
Ответ 1
Если вы используете Typescript перед выпуском 2.4, есть способ добиться этого с помощью перечислений, переведя значения вашего перечисления в any
.
пример вашей первой реализации
enum Type {
NEW = <any>"NEW",
OLD = <any>"OLD",
}
interface Thing { type: Type }
let thing:Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW); // true
Typescript 2.4 уже встроен в поддержку перечислений строк, поэтому приведение к any
больше не понадобится, и вы можете достигните его без использования String Literal Union Type
, который подходит для проверки и автозаполнения, но не настолько хорош для удобочитаемости и рефакторинга, в зависимости от использования сценарий.
Ответ 2
но строка аннотации типа слишком широка и подвержена ошибкам.
Согласен. Одно быстрое решение (если у вас есть роскошь генерации кода, вы можете автоматизировать это):
interface Thing { type: "NEW" | "OLD" }
Они называются строковыми литералами в объединении. Подробнее: https://basarat.gitbooks.io/typescript/content/docs/tips/stringEnums.html
Ответ 3
Я использую функции конвертера в качестве остановки. Надеюсь, эта тема приходит к разрешению: https://github.com/Microsoft/TypeScript/issues/1206
enum ErrorCode {
Foo,
Bar
}
interface Error {
code: ErrorCode;
message?: string;
}
function convertToError(obj: any): Error {
let typed: Error = obj as Error;
// Fix any enums
typed.code = ErrorCode[typed.code.toString()];
return typed;
}