Сужение функции возвращаемого типа с защитой типа - PullRequest
0 голосов
/ 26 сентября 2018

У меня есть функция TypeScript, которая анализирует некоторый JSON и запускает его через средство защиты типов, чтобы убедиться, что данные действительны, чтобы остальная часть кода времени компиляции знала, что имеет дело с объектом, который фактически соответствует ожидаемому интерфейсу..

Однако мне трудно заставить TypeScript принудительно запустить защиту типов.Очевидно, JSON.parse возвращает any, который можно назначить любому другому типу и, следовательно, проверяет, даже если я указываю тип, отличный от any.

const validPerson = `{"firstName": "John", "lastName": "Doe"}`;
const invalidPerson = `{"foo": 123}`;

interface Person {
    firstName: string;
    lastName: string;
}

interface PersonGetter {
    (json: string): Person | undefined;
}

function isPerson(o: any): o is Person {
    return typeof o.firstName === "string" && typeof o.lastName === "string";
}

// BAD: Type checks, but it's overly permissive. `JSON.parse` could return anything.
const getPerson1: PersonGetter = (json) => {
    const o = JSON.parse(json);
    return o;
}

// GOOD (kinda): Requires type guard to pass.
// `unknown` requires TS 3, which is fine in general, but bad for me.
// Also, I feel like having to remember to case the return from `JSON.parse` is a responsibility the programmer shouldn't bear.
const getPerson2: PersonGetter = (json) => {
    const o: unknown = JSON.parse(json);
    if (isPerson(o)) {
        return o;
    } else {
        return undefined;
    }
}

// GOOD (kinda): Requires type guard to pass. Works in TS 2.8.
// Still, not great that I have to cast the return value from `JSON.parse`, but I could probably work around that.
type JSONPrimitive = string | number | boolean | null;
type JSONValue = JSONPrimitive | JSONObject | JSONArray;
type JSONObject = { [member: string]: JSONValue };
interface JSONArray extends Array<JSONValue> {}

const getPerson3: PersonGetter = (json) => {
    const o: JSONValue = JSON.parse(json);
    if (isPerson(o)) {
        return o;
    } else {
        return undefined;
    }
}

TypeScript Playground link

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

Может показаться, что JSON.parse возвращение any является источником моей проблемы здесь.Я уже работаю в режиме strict, но может показаться, что он по-прежнему позволяет расшифровать что-то явно напечатанное как any до явно возвращаемого типа функции.

Есть ли другой способ сказать,TypeScript, что возвращаемое значение функции должно быть типом возврата, указанным в реализуемом интерфейсе, а не any?

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

JSON объявлен в lib.es5.d.ts.Создайте свой собственный файл определения типа в своем проекте и объявите новый глобальный экземпляр JSON с определением, которое возвращает фиктивный тип из parse() вместо any.

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

interface JSONStrict extends JSON {
    /**
      * Converts a JavaScript Object Notation (JSON) string into an object.
      * @param text A valid JSON string.
      * @param reviver A function that transforms the results. 
      * This function is called for each member of the object.
      * If a member contains nested objects, the nested objects are
      * transformed before the parent object is.
      */
    parse(text: string, reviver?: (key: any, value: any) => any): { _dummyProp?: void };
}
// overide lib.es5 declaration of JSON
declare const JSON: JSONStrict;

/* ... */
function parseAndThrowCompilationError(): Person {
    var result = JSON.parse('{ "x": 1}');
    return result; 
    // Type '{ _dummyProp?: void }' has no properties in common with type 'Person'
}

Я добавил _dummyProp к результату, поскольку простое использование объекта соответствовало бы интерфейсу только сНеобязательные свойства и не выдают ошибку.

... Честно говоря, это немного сложно, и мне интересно, стоит ли это того.

0 голосов
/ 26 сентября 2018
const validPerson = `{"firstName": "John", "lastName": "Doe"}`;
const invalidPerson = `{"foo": 123}`;

interface Person {
    firstName: string;
    lastName: string;
}

function isPerson(o: any): o is Person {
    return typeof o.firstName === "string" && typeof o.lastName === "string";
}

function getPerson(json: string) {
    const o = JSON.parse(json);

    if (isPerson(o)) {
        return o;
    } else {
        return undefined;
    }
}

Минимальная игровая площадка .Не забудьте проверить включите strictNullChecks

...