TypeScript: как создать тип или интерфейс, представляющий собой объект с одним из n ключей или строку?

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

В основном у меня есть функция, которая в зависимости от ошибки что-то возвращает:

export const determineError = (error: ServerAlerts): AlertError => {
  if (typeof error !== "string") {
    if (error.hasOwnProperty("non_field_errors")) {
      return error.non_field_errors[0];
    } else if (error.hasOwnProperty("detail")) {
      return error.detail;
    } else if (error.hasOwnProperty("email")) {
      return error.email[0];
    } else {
      return UNKNOWN_ERROR;
    }
  } else {
    return error;
  }
};

Вот типы:

export type AlertError =
  | "Unable to log in with provided credentials."
  | "E-mail is not verified."
  | "Password reset e-mail has been sent."
  | "Verification e-mail sent."
  | "A user is already registered with this e-mail address."
  | "Facebook Log In is cancelled."
  | string;

export interface ServerAlerts {
  non_field_errors: [string];
  detail: string;
  email: [string];
}

Но способ, которым я разработал ServerAlerts здесь, не работает для меня, поскольку ServerAlerts также может быть string, и если у него есть один из его ключей, он имеет только один.

Как бы вы спроектировали такой тип или интерфейс?

РЕДАКТИРОВАТЬ: Я попытался сделать ключи необязательными, поставив им вопросительный знак, но затем мой линтер жалуется на сообщение об ошибке соответствующего ключа в determineError.


person J. Hesters    schedule 06.10.2018    source источник
comment
Все они необязательны. Их может быть один, два или все трое одновременно. Если его нет, это просто струна, которая проходит.   -  person T.J. Crowder    schedule 06.10.2018
comment
Помимо союза _1_, предложенного ниже, это дубликат Как создать частичное свойство, для которого требуется установить одно свойство.   -  person J. Hesters    schedule 06.10.2018
comment
Ваш линтер жалуется, что ... это свойство может быть | string, я полагаю? Да, вопросительный знак позволяет (в режиме _2_) передать _3_ для свойства, поэтому вам также необходимо проверить это (или использовать утверждение).   -  person jcalz    schedule 07.10.2018
comment
Вы действительно хотите, чтобы undefined и --strictNullChecks были одноэлементными массивами (также известными как кортеж из одного элемента или одноэлемент)? Вот что означает undefined: одна строка в массиве. Если массивы могут иметь более одного элемента, вместо этого следует использовать синтаксис _4_ или _5_.   -  person jcalz    schedule 07.10.2018
comment
Спасибо! Это решает половину проблемы. Он рассматривает возможность строки. Но чего не хватает, так это проверки ключей интерфейса (non_field_errors, detail, email).   -  person jcalz    schedule 07.10.2018


Ответы (1)


Если я правильно вас понял, просто объявите параметр как ServerAlerts или string:

export const determineError = (error: ServerAlerts|string): AlertError => {
// -----------------------------------------------^^^^^^^

В комментарии вы сказали, что все три свойства ServerAlerts необязательны, поэтому вам нужно пометить их как таковые с помощью ?:

interface ServerAlerts {
  non_field_errors?: [string];
  detail?: string;
  email?: [string];
}

Однако это означает, что все, что набрано object, также будет работать, потому что все поля являются необязательными. Итак, если вы сделаете обе эти вещи, вы получите:

determineError("foo");                       // Works
determineError({ non_field_errors: ["x"] }); // Works
determineError({ detail: "x" });             // Works
determineError({ email: ["x"] });            // Works
determineError({});                          // Works (because all fields are optional)
let nonLiteralServerAlerts: object;
nonLiteralServerAlerts = { foo: ["x"] };
determineError(nonLiteralServerAlerts);      // Works (because all fields are optional)
determineError({ foo: ["x"] });              // Fails (correctly)

Пример игровой площадки

Это предполагает, что вы можете просто использовать object в сигнатуре параметра. Если вы хотите потребовать одно из трех полей (что, как я полагаю, устранит эту UNKNOWN_ERROR ветку), вы должны определить три интерфейса и сделать ServerAlerts их объединение:

interface ServerAlertsNonFieldErrors {
  non_field_errors: [string];
}

interface ServerAlertsDetail {
  detail: string;
}

interface ServerAlertsEmail {
  email: [string];
}

type ServerAlerts = ServerAlertsNonFieldErrors | ServerAlertsDetail | ServerAlertsEmail;

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

if (error.hasOwnProperty("non_field_errors")) {
  return (error as ServerAlertsNonFieldErrors).non_field_errors[0];
// ------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Если вы это сделаете, то получите:

determineError("foo");                       // Works
determineError({ non_field_errors: ["x"] }); // Works
determineError({ detail: "x" });             // Works
determineError({ email: ["x"] });            // Works
determineError({});                          // Fails (correctly)
let nonLiteralServerAlerts: object;
nonLiteralServerAlerts = { foo: ["x"] };
determineError(nonLiteralServerAlerts);      // Fails (correctly)
determineError({ foo: ["x"] });              // Fails (correctly)

Re ServerAlerts, string и

export const determineError = (error: ServerAlerts|string): AlertError => {
// -----------------------------------------------^^^^^^^
- являются ли они взаимоисключающими? Какие-либо (или все) необязательны?

person T.J. Crowder    schedule 06.10.2018
comment
@ J.Hesters - Как вы сказали в своем вопросе, если они необязательны (и вы уже подтвердили, что они являются обязательными), используйте _1_, чтобы отметить их так. Я обновил ответ. - person J. Hesters; 06.10.2018
comment
Теперь любой объект соответствует ?, поскольку все поля являются необязательными. - person T.J. Crowder; 07.10.2018
comment
Это потому, что вы используете буквальный объект, при использовании буквальных объектов машинописный текст жалуется на дополнительные поля. Если вы используете нелитеральный объект, он не потерпит неудачу (ссылка на игровую площадку слишком длинная для комментариев, поэтому просто добавьте эти три строки в свой пример) ServerAlerts - person Motti; 07.10.2018
comment
@Motti - Спасибо! Я сложил это в ответ. - person Motti; 07.10.2018
comment
Пример игровой площадки - person T.J. Crowder; 07.10.2018