Prześlij obiekt do interfejsu w TypeScript

102

Próbuję wykonać rzutowanie w moim kodzie z treści żądania w trybie ekspresowym (przy użyciu oprogramowania pośredniczącego z parserem treści) do interfejsu, ale nie wymusza to bezpieczeństwa typów.

To jest mój interfejs:

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

Oto kod, w którym próbuję wykonać rzut:

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

I wreszcie wywoływana metoda usługi:

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

Mogę przekazać dowolne argumenty, nawet takie, które nie są bliskie dopasowania definicji interfejsu , a ten kod będzie działał dobrze. Spodziewałbym się, że jeśli rzutowanie z treści odpowiedzi na interfejs nie jest możliwe, wyjątek zostanie wyrzucony w czasie wykonywania, jak Java lub C #.

Wyczytałem, że w rzutowaniu TypeScript nie istnieje, tylko Asercja typu, więc tylko powie kompilatorowi, że obiekt jest typu x, więc ... Czy się mylę? Jaki jest właściwy sposób egzekwowania i zapewnienia bezpieczeństwa typu?

Elias Garcia
źródło
1
Określ „to nie działa”. Bądź precyzyjny. Czy jest jakiś błąd? Który? W czasie kompilacji? W czasie wykonywania? Co się dzieje?
JB Nizet
1
W czasie wykonywania kod jest wykonywany normalnie, niezależnie od przekazanego obiektu.
Elias Garcia
Nie jest jasne, o co pytasz
Nitzan Tomer
Moje pytanie brzmi: jak rzutować przychodzący obiekt na obiekt wpisany na maszynie. Jeśli obsada nie jest możliwa, wyrzuć wyjątek w czasie wykonywania, na przykład Java, C # ...
Elias Garcia
Czy to odpowiada na twoje pytanie? Przesyłanie typu TypeScript lub JavaScript
Michael Freidgeim

Odpowiedzi:

144

W javascript nie ma przesyłania, więc nie możesz go przesłać, jeśli „rzutowanie się nie powiedzie”.
Typescript obsługuje rzutowanie, ale to tylko na czas kompilacji i możesz to zrobić w następujący sposób:

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

Możesz sprawdzić w czasie wykonywania, czy wartość jest prawidłowa, a jeśli nie, zgłoś błąd, np .:

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();
}

Edytować

Jak zauważył @huyz, asercja typu nie jest potrzebna, ponieważ isToDoDtojest to strażnik typu, więc to powinno wystarczyć:

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

this.toDoService.addToDo(req.body);
Nitzan Tomer
źródło
Nie sądzę, że potrzebujesz obsady, const toDo = req.body as IToDoDto;ponieważ kompilator TS wie, że jest IToDoDtow tym momencie
huyz
10
dla każdego, kto szuka ogólnie asercji typu, nie używaj <>. to jest przestarzałe. użycieas
Abhishek Deb
W javascript nie ma rzutowania, więc nie można go wyrzucić, jeśli„ rzutowanie się nie powiedzie ”. „ Mówiąc dokładniej, interfejsy w TypeScript nie są obsługiwane; w rzeczywistości są one w 100% syntetycznym cukrem . Ułatwiają one koncepcyjne utrzymanie struktur , ale nie mają rzeczywistego wpływu na transpiled kod - co jest, imo, szalenie zagmatwane / anty-wzorzec, jak dowodzi pytanie OP. Nie ma powodu, dla którego rzeczy, które nie pasują do interfejsów, nie mogły wrzucić transpiled JavaScript; to świadomy (i kiepski, imo) wybór przez TypeScript.
ruffin
Interfejsy @ruffin nie są cukrem syntaktycznym, ale dokonali świadomego wyboru, aby zachować go tylko w czasie wykonywania. Myślę, że to świetny wybór, dzięki czemu nie ma spadku wydajności w czasie wykonywania.
Nitzan Tomer
Tomayto tomahto ? Bezpieczeństwo typów z interfejsów w TypeScript nie rozciąga się na transpiled kod, a nawet przed uruchomieniem bezpieczeństwo typów jest poważnie ograniczone - jak widzimy w problemie OP, w którym w ogóle nie ma bezpieczeństwa typów . TS mógłby powiedzieć: „Hej, czekaj, jeszcze nie masz anygwarancji IToDoDto!”, Ale TS zdecydował się tego nie robić . Jeśli kompilator wychwytuje tylko niektóre konflikty typów, a żadnego w transpiled kodu (i masz rację; powinienem był być bardziej jasny @ niż w oryginale), interfejsy są niestety, imo, [głównie?] Sugar.
ruffin
7

Oto inny sposób wymuszenia rzutowania typu nawet między niekompatybilnymi typami i interfejsami, w przypadku których kompilator TS normalnie narzeka:

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

  // ... do runtime checks here

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

Następnie możesz go użyć do wymuszenia rzucania obiektów do określonego typu:

import { forceCast } from './forceCast';

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

Zauważ, że pominąłem część, którą powinieneś sprawdzić w czasie wykonywania przed rzutowaniem, aby zmniejszyć złożoność. To, co robię w moim projekcie, to kompilowanie wszystkich .d.tsplików interfejsu do schematów JSON i używanie ich ajvdo sprawdzania poprawności w czasie wykonywania.

Sepehr
źródło
2

Jeśli komuś to pomoże, miałem problem polegający na tym, że chciałem traktować obiekt jako inny typ z podobnym interfejsem. Próbowałem wykonać następujące czynności:

Nie przeszedł lintingu

const x = new Obj(a as b);

Linter narzekał, że abrakuje mu właściwości, które istniały b. Innymi słowy,a miał pewne właściwości i metody b, ale nie wszystkie. Aby obejść ten problem, postąpiłem zgodnie z sugestią VS Code:

Przeszedł linting i testy

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

Należy zauważyć, że jeśli kod próbuje wywołać jedną z właściwości, które istnieją w typie, bktóry nie jest zaimplementowany w typie a, należy zrealizować błąd czasu wykonywania.

Jason
źródło
1
Cieszę się, że znalazłem tę odpowiedź, ale zwróć uwagę, że jeśli wysyłasz „x” przez sieć lub do innej aplikacji, możesz wyciekać dane osobowe (jeśli na przykład „a” jest użytkownikiem), ponieważ „x” nadal ma wszystkie właściwości „a”, są one po prostu niedostępne dla maszynopisu.
Zoltán Matók
@ ZoltánMatók dobra uwaga. Ponadto, jeśli chodzi o wysyłanie serializowanego obiektu przez sieć, istnieje argument przemawiający za pobieraniem i ustawianiem w stylu Java zamiast JavaScript geti setmetod.
Jason