Приведение объекта к интерфейсу в TypeScript

94

Я пытаюсь выполнить приведение своего кода из тела запроса в экспресс (с использованием промежуточного программного обеспечения парсера тела) в интерфейс, но это не обеспечивает безопасность типов.

Это мой интерфейс:

export interface IToDoDto {
  description: string;
  status: boolean;
};

Это код, в котором я пытаюсь выполнить приведение:

@Post()
addToDo(@Response() res, @Request() req) {
  const toDo: IToDoDto = <IToDoDto> req.body; // <<< cast here
  this.toDoService.addToDo(toDo);
  return res.status(HttpStatus.CREATED).end();
}

И, наконец, вызываемый метод сервиса:

public addToDo(toDo: IToDoDto): void {
  toDo.id = this.idCounter;
  this.todos.push(toDo);
  this.idCounter++;
}

Я могу передавать любые аргументы, даже те, которые не подходят для соответствия определению интерфейса , и этот код будет работать нормально. Я ожидал, что если приведение тела ответа к интерфейсу невозможно, то во время выполнения, например Java или C #, будет выброшено исключение.

Я читал, что в TypeScript не существует приведения типов, только Type Assertion, поэтому он будет сообщать компилятору только о том, что объект имеет тип x, так что ... Я ошибаюсь? Как правильно применять и обеспечивать безопасность типов?

Элиас Гарсия
источник
1
Пожалуйста, определите «это не работает». Будьте точны. Есть ошибка? Который из? Во время компиляции? Во время выполнения? Что происходит?
JB Nizet
1
Во время выполнения код выполняется нормально, с любым переданным мной объектом.
Элиас Гарсия
Непонятно, о чем вы спрашиваете
Ницан Томер
Мой вопрос в том, как преобразовать входящий объект в типизированный объект. Если приведение невозможно, вызовите исключение во время выполнения, например, Java, C # ...
Элиас Гарсия
Отвечает ли это на ваш вопрос? Приведение типов в TypeScript или JavaScript
Майкл Фрейджейм,

Ответы:

133

В javascript нет приведения, поэтому вы не можете выбросить, если «приведение не удалось».
TypeScript поддерживает приведение типов, но это только на время компиляции, и вы можете сделать это следующим образом:

const toDo = <IToDoDto> req.body;
// or
const toDo = req.body as IToDoDto;

Вы можете проверить во время выполнения, является ли значение допустимым, а если нет, вывести ошибку, то есть:

function isToDoDto(obj: any): obj is IToDoDto {
    return typeof obj.description === "string" && typeof obj.status === "boolean";
}

@Post()
addToDo(@Response() res, @Request() req) {
    if (!isToDoDto(req.body)) {
        throw new Error("invalid request");
    }

    const toDo = req.body as IToDoDto;
    this.toDoService.addToDo(toDo);
    return res.status(HttpStatus.CREATED).end();
}

редактировать

Как отметил @huyz, утверждение типа не нужно, потому что isToDoDtoэто защита типа, поэтому этого должно быть достаточно:

if (!isToDoDto(req.body)) {
    throw new Error("invalid request");
}

this.toDoService.addToDo(req.body);
Ницан Томер
источник
Я не думаю, что вам нужно приведение, const toDo = req.body as IToDoDto;так как компилятор TS знает, что это IToDoDtoна данный момент
huyz
9
для всех, кто ищет утверждение типа в целом, не используйте <>. это устарело. useas
Abhishek Deb
« В javascript нет приведения типов, поэтому вы не можете отбрасывать их, если« приведение не выполняется ». « Я думаю, что более конкретно, интерфейсы в TypeScript не являются действенными; на самом деле, это 100% синтетический сахар . Они упрощают поддержку структур концептуально , но не имеют фактического влияния на транслируемый код, что, по-моему, безумно сбивает с толку / анти-паттерн, как свидетельствует вопрос OP. Нет никаких причин, по которым вещи, которые не соответствуют интерфейсам, не могли бы добавить транспилированный JavaScript; это сознательный (и плохой, я думаю) выбор TypeScript.
Руффин
Интерфейсы @ruffin не являются синтаксическим сахаром, но они сделали сознательный выбор, чтобы оставить его только во время выполнения. Я думаю, что это отличный выбор, так как нет потери производительности во время выполнения.
Ницан Томер
Томайто томахто ? Безопасность типов из интерфейсов в TypeScript не распространяется на ваш транслируемый код, и даже перед запуском безопасность типов сильно ограничена - как мы видим в проблеме OP, где безопасность типов отсутствует вообще . TS мог сказать: «Эй, подожди, anyэто еще не гарантировано IToDoDto!», Но TS предпочла этого не делать. Если компилятор улавливает только некоторые конфликты типов и их нет в транслируемом коде (и вы правы; я должен был быть более ясным @, чем в оригинале), интерфейсы, к сожалению, имо, [в основном?] Сахар.
ерш
7

Вот еще один способ принудительного преобразования типов даже между несовместимыми типами и интерфейсами, на которые компилятор TS обычно жалуется:

export function forceCast<T>(input: any): T {

  // ... do runtime checks here

  // @ts-ignore <-- forces TS compiler to compile this as-is
  return input;
}

Затем вы можете использовать его для принудительного приведения объектов к определенному типу:

import { forceCast } from './forceCast';

const randomObject: any = {};
const typedObject = forceCast<IToDoDto>(randomObject);

Обратите внимание, что я пропустил ту часть, которую вы должны выполнять перед кастингом, чтобы упростить ее. В своем проекте я компилирую все .d.tsфайлы интерфейса в схемы JSON и использую ajvдля проверки во время выполнения.

Sepehr
источник
1

Если это кому-то поможет, то у меня возникла проблема, когда я хотел рассматривать объект как другой тип с аналогичным интерфейсом. Я попытался сделать следующее:

Не прошел линтинг

const x = new Obj(a as b);

Линтер жаловался на aотсутствие свойств, которые существовали b. Другими словами, aобладает некоторыми свойствами и методами b, но не всеми. Чтобы обойти это, я последовал предложению VS Code:

Прошла линтинг и тестирование

const x = new Obj(a as unknown as b);

Обратите внимание, что если ваш код пытается вызвать одно из свойств, существующих в типе b, которое не реализовано в типе a, вы должны реализовать ошибку времени выполнения.

Джейсон
источник
1
Я рад, что нашел этот ответ, но обратите внимание, что если вы отправляете 'x' по сети или в другое приложение, вы можете потерять личную информацию (например, если 'a' является пользователем), потому что 'x' все еще имеет все свойства 'a', они просто недоступны для машинописного текста.
Золтан Маток,
@ ZoltánMatók хорошее замечание. Кроме того, что касается отправки сериализованного объекта по сети, существует аргумент для методов получения и установки в стиле Java через JavaScript getи setметоды.
Джейсон