Подтвердить что ты не робот

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 перечисления.:)

4b9b3361

Ответ 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;
}