Как типизировать объект, у которого 1 ключ вариативен?
с бэка приходит массив объектов с такой структурой
export interface IUserNotifySubscribesResponse {
id: number
mId: number
uId: number
notifyType: UserNotifyType
notifyRulesJson: string
}
export enum UserNotifyType {
UNOTIFY_BY_MODEL,
UNOTIFY_BY_WEBCASE,
UNOTIFY_BY_PLATFORM,
}
для работе на фронте , создается новый тип в котором notifyRulesJson парсится
export interface IUserNotifySubscribes extends IUserNotifySubscribesResponse {
notifyRulesObj: RulesObj
}
RulesObj в свою очередь вариативен
export type RulesObj = SubscriptionSettings | CasesSettings | PlatformSettings
export interface SubscriptionSettings {
modelNotify: boolean
defect: boolean
}
export interface PlatformSettings {
platform: boolean
sms: boolean
email: boolean
}
export interface CasesSettings {
cases: boolean
}
как сделать, чтобы TypeScript понимал, что именно приходит в RulesObj
Полагаю тут нужны дженерики,
export interface IUserNotifySubscribes<T extends RulesObj> extends IUserNotifySubscribesResponse {
notifyRulesObj: T
}
но в таком случае не совсем понимаю как нужно типизировать массив, ведь в нем объекты всех 3х вариаций
Ответы (3 шт):
В вашем случае не нужны дженерики, воспользуйтесь защитой типа (Type Guards)
Создайте интерфейс с перечислением всех реализаций:
export interface SubscriptionSettings {
modelNotify: boolean
defect: boolean
}
export interface PlatformSettings {
platform: boolean
sms: boolean
email: boolean
}
export interface CasesSettings {
cases: boolean
}
export type RulesObj = SubscriptionSettings | CasesSettings | PlatformSettings
export interface UserNotifySubscribes extends UserNotifySubscribesResponse {
notifyRulesObj: RulesObj
}
Затем создайте функции которые будут проверять ключи или любые другие данные с указанным типом.
export function isUserNotifySubscriptionSettings(value: RulesObj): value is SubscriptionSettings {
return 'modelNotify' in value;
}
export function isUserNotifyPlatformSettings(value: RulesObj): value is PlatformSettings {
return 'platform' in value;
}
export function isUserNotifyCasesSettings(value: RulesObj): value is SubscriptionSettings {
return 'cases' in value;
}
главная функция, которая обрабатывает логику, должна быть фабрикой и проверять необходимые данные для корректного определения типа:
export async function main() {
const result: UserNotifySubscribes = await getUserNotifySubscribes();
if (isUserNotifySubscriptionSettings(result.notifyRulesObj)) {
console.log(result.notifyRulesObj.modelNotify);
} else if (isUserNotifyPlatformSettings(result.notifyRulesObj)) {
console.log(result.notifyRulesObj.platform);
} else if (isUserNotifyCasesSettings(result.notifyRulesObj)) {
console.log(result.notifyRulesObj.cases);
}
// Another logic
}
Не могу подсказать более элегантного и type safety
решения. Уверен есть и другие решения которые не нарушают какие-то принципы.
Весь код тут:
export interface UserNotifySubscribesResponse {
id: number
mId: number
uId: number
notifyType: NotifyType
notifyRulesJson: string
}
export enum NotifyType {
UNOTIFY_BY_MODEL,
UNOTIFY_BY_WEBCASE,
UNOTIFY_BY_PLATFORM,
}
export interface SubscriptionSettings {
modelNotify: boolean
defect: boolean
}
export interface PlatformSettings {
platform: boolean
sms: boolean
email: boolean
}
export interface CasesSettings {
cases: boolean
}
export type RulesObj = SubscriptionSettings | CasesSettings | PlatformSettings
export interface UserNotifySubscribes extends UserNotifySubscribesResponse {
notifyRulesObj: RulesObj
}
export async function getUserNotifySubscribes(): Promise < UserNotifySubscribes > {
const response: UserNotifySubscribesResponse = {
id: 0,
mId: 0,
uId: 0,
notifyType: NotifyType.UNOTIFY_BY_WEBCASE,
notifyRulesJson: '',
}
const result = {}
as UserNotifySubscribes;
Object.assign(result, response, {
notifyRulesObj: {
cases: false,
}
});
return result;
}
export function isUserNotifySubscriptionSettings(value: RulesObj): value is SubscriptionSettings {
return 'modelNotify' in value;
}
export function isUserNotifyPlatformSettings(value: RulesObj): value is PlatformSettings {
return 'platform' in value;
}
export function isUserNotifyCasesSettings(value: RulesObj): value is SubscriptionSettings {
return 'cases' in value;
}
export async function main() {
const result: UserNotifySubscribes = await getUserNotifySubscribes();
if (isUserNotifySubscriptionSettings(result.notifyRulesObj)) {
console.log(result.notifyRulesObj.modelNotify);
} else if (isUserNotifyPlatformSettings(result.notifyRulesObj)) {
console.log(result.notifyRulesObj.platform);
} else if (isUserNotifyCasesSettings(result.notifyRulesObj)) {
console.log(result.notifyRulesObj.cases);
}
// Another logic
}
type IUserNotifySubscribes = {
[key in UserNotifyType]: IUserNotifySubscribesResponse & {
notifyType: key,
notifyRulesObj: {
[UserNotifyType.UNOTIFY_BY_MODEL]: SubscriptionSettings,
[UserNotifyType.UNOTIFY_BY_WEBCASE]: CasesSettings,
[UserNotifyType.UNOTIFY_BY_PLATFORM]: PlatformSettings,
}[key]
}
}[UserNotifyType]
Код полностью: playground
export interface IUserNotifySubscribesResponse {
id: number
mId: number
uId: number
notifyType: UserNotifyType
notifyRulesJson: string
}
export enum UserNotifyType {
UNOTIFY_BY_MODEL,
UNOTIFY_BY_WEBCASE,
UNOTIFY_BY_PLATFORM,
}
export type RulesObj = SubscriptionSettings | CasesSettings | PlatformSettings
export interface SubscriptionSettings {
modelNotify: boolean
defect: boolean
}
export interface PlatformSettings {
platform: boolean
sms: boolean
email: boolean
}
export interface CasesSettings {
cases: boolean
}
type IUserNotifySubscribes = {
[key in UserNotifyType]: IUserNotifySubscribesResponse & {
notifyType: key,
notifyRulesObj: {
[UserNotifyType.UNOTIFY_BY_MODEL]: SubscriptionSettings,
[UserNotifyType.UNOTIFY_BY_WEBCASE]: CasesSettings,
[UserNotifyType.UNOTIFY_BY_PLATFORM]: PlatformSettings,
}[key]
}
}[UserNotifyType]
declare const data: IUserNotifySubscribes[]
for (const x of data) {
switch (x.notifyType) {
case UserNotifyType.UNOTIFY_BY_PLATFORM:
console.log(x.notifyRulesObj.platform)
break
case UserNotifyType.UNOTIFY_BY_MODEL:
console.log(x.notifyRulesObj.defect)
break
case UserNotifyType.UNOTIFY_BY_WEBCASE:
console.log(x.notifyRulesObj.cases)
break
}
}
Можно также использовать Union Types
type IUserNotifySubscribes = IUserNotifySubscribesResponse & ({
notifyType: UserNotifyType.UNOTIFY_BY_MODEL;
notifyRulesObj: SubscriptionSettings;
} | {
notifyType: UserNotifyType.UNOTIFY_BY_WEBCASE;
notifyRulesObj: CasesSettings;
} | {
notifyType: UserNotifyType.UNOTIFY_BY_PLATFORM;
notifyRulesObj: PlatformSettings;
});
Полное решение:
export interface IUserNotifySubscribesResponse {
id: number
mId: number
uId: number
notifyType: UserNotifyType
notifyRulesJson: string
}
export enum UserNotifyType {
UNOTIFY_BY_MODEL,
UNOTIFY_BY_WEBCASE,
UNOTIFY_BY_PLATFORM,
}
export interface SubscriptionSettings {
modelNotify: boolean
defect: boolean
}
export interface PlatformSettings {
platform: boolean
sms: boolean
email: boolean
}
export interface CasesSettings {
cases: boolean
}
type IUserNotifySubscribes = IUserNotifySubscribesResponse & ({
notifyType: UserNotifyType.UNOTIFY_BY_MODEL;
notifyRulesObj: SubscriptionSettings;
} | {
notifyType: UserNotifyType.UNOTIFY_BY_WEBCASE;
notifyRulesObj: CasesSettings;
} | {
notifyType: UserNotifyType.UNOTIFY_BY_PLATFORM;
notifyRulesObj: PlatformSettings;
});
const userNotifySubscribes: IUserNotifySubscribes[] = []; // Получение данных с сервера
// Можно сужать тип через switch case
userNotifySubscribes.forEach((item) => {
switch (item.notifyType) {
case UserNotifyType.UNOTIFY_BY_MODEL:
item.notifyRulesObj; // SubscriptionSettings
break;
case UserNotifyType.UNOTIFY_BY_WEBCASE:
item.notifyRulesObj; // CasesSettings
break;
case UserNotifyType.UNOTIFY_BY_PLATFORM:
item.notifyRulesObj; // PlatformSettings
break;
}
});
// Можно сужать тип через if
userNotifySubscribes.forEach((item) => {
if (item.notifyType === UserNotifyType.UNOTIFY_BY_MODEL) {
item.notifyRulesObj; // SubscriptionSettings
} else if (item.notifyType === UserNotifyType.UNOTIFY_BY_WEBCASE) {
item.notifyRulesObj; // CasesSettings
} else if (item.notifyType === UserNotifyType.UNOTIFY_BY_PLATFORM) {
item.notifyRulesObj; // PlatformSettings
}
});