У меня есть некоторые данные в моем коде, как показано ниже:
interface Product {
id: number
name: string;
}
enum EnumValue {
'VALUE1' = 'VALUE1',
'VALUE2' = 'VALUE2',
'VALUE3' = 'VALUE3',
}
const data = {
'VALUE1': {
num1: {id: 1, name: '2'},
num2: {id: 2, name: '2'},
},
'VALUE2': {
num1: {id: 1, name: '2'},
},
'VALUE3': {
num1: {id: 1, name: '2'},
},
} as const satisfies { readonly [key in EnumValue]: { [key: string]: Product} };
Мне нужно определить валидатор для моего типа данных, чтобы для каждого свойства EnumValue использовались только уникальные идентификаторы. Я имею в виду
data = {
'VALUE1': {
num1: {id: 1, name: '2'},
num2: {id: 1, name: '2'},
},
'VALUE2': {
num1: {id: 1, name: '2'},
},
'VALUE3': {
num1: {id: 1, name: '2'},
},
}
ts должен выдать ошибку, поскольку VALUE1 имеет 2 объекта с идентификатором = 1, но
data = {
'VALUE1': {
num1: {id: 1, name: '2'},
num2: {id: 2, name: '2'},
},
'VALUE2': {
num1: {id: 1, name: '2'},
},
'VALUE3': {
num1: {id: 1, name: '2'},
},
является допустимым значением.
мне нужна часть as const satisfies, чтобы использовать тип модели данных в моем коде.
Итак, можете ли вы помочь мне определить валидатор для исправления моего типа данных?
Существует некоторый код для проверки уникальных идентификаторов массива объектов, который может помочь, но проблема в том, что я не знаю, как получить доступ к значениям объекта для повторения при проверке типа. ссылка на этот вопрос
interface IProduct<Id extends number> {
id: Id
name: string;
}
type Validation<
Products extends IProduct<number>[],
Accumulator extends IProduct<number>[] = []>
=
(Products extends []
// #1 Last call
? Accumulator
// #2 All calls but last
: (Products extends [infer Head, ...infer Tail]
? (Head extends IProduct<number>
// #3 Check whether [id] property already exists in our accumulator
? (Head['id'] extends Accumulator[number]['id']
? (Tail extends IProduct<number>[]
// #4 [id] property is a duplicate, hence we need to replace it with [never] in order to trigger the error
? Validation<Tail, [...Accumulator, { id: never, name: Head['name'] }]>
: 1)
// #5 [id] is not a duplicate, hence we can add to our accumulator whole product
: (Tail extends IProduct<number>[]
? Validation<Tail, [...Accumulator, Head]>
: 2)
)
: 3)
: Products)
)
@jcalz Да, это решило мою проблему. Спасибо за ваше решение. Да, было бы неплохо, если бы вы тоже объяснили подход.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


В TypeScript нет конкретного типа ValidData, соответствующего вашему требованию, чтобы каждое свойство имело уникальные id подсвойства, поэтому вы не можете писать const data = {⋯} as const satisfies ValidData. Вместо этого вы можете создать универсальный ValidData<T> тип , который проверяет тип входных данных T и проверяет его вместе со вспомогательной validData() функцией. Итак, вы пишете const data = validData({⋯});, и в зависимости от введенных данных это либо получится, либо потерпит неудачу.
Сначала давайте напишем тип BasicData<K>, представляющий супертип всех типов ValidData<T>, где нас не волнует уникальность id; все, что нас волнует, это то, что у него есть ключи K и значения, все свойства которых являются Product:
type BasicData<K extends PropertyKey = EnumValue> =
Record<K, { [k: string]: Product }>;
Обратите внимание, что я использовал аргумент общего типа по умолчанию, так что BasicData сам по себе соответствует BasicData<EnumValue>, что по сути то же самое, что вы использовали после satisfies.
Тогда ValidData<T> будет выглядеть так
type ValidData<T extends BasicData<keyof T>> =
{ [K in keyof T]: UniqueId<T[K]> }
где UniqueId<T> — универсальный валидатор, который гарантирует, что T обладает уникальными id свойствами. Вот один из способов написать это:
type UniqueId<T extends Record<keyof T, Product>> =
{ [K in keyof T]: {
id: Exclude<T[K]["id"], T[Exclude<keyof T, K>]["id"]>,
name: string
} }
Это сопоставленный тип над T, где каждое свойство является Product, свойство id которого явно исключает свойства id из всех остальных ключей. T[Exclude<keyof T, K>] — это объединение всех свойств T, кроме свойства с ключом K. И поэтому свойство id выглядит так: «Возьмите свойство id этого свойства и свойства Exclude всех остальных свойств».
Если свойства id уникальны, это ничего не будет исключать. Если это не так, то свойство id для дубликатов в конечном итоге будет типа «никогда». Итак, если id расширяет UniqueId<T>, то T допустимо. В противном случае T недействителен именно в тех свойствах, где есть повторяющиеся T.
Итак, теперь мы можем написать id вот так:
const validData =
<const T extends BasicData & BasicData<keyof T> & ValidData<T>>(
d: T) => d;
Здесь используется параметр типа const , поэтому вызывающим абонентам не нужно помнить об использовании утверждения const . Ограничение — это пересечение всех отдельных ограничений, которые нас интересуют. Первый — validData(), что означает, что он должен иметь все ключи BasicData. Второй — EnumValue, что означает, что для каждого свойства, которое оно имеет, все свойства должны быть набором BasicData<keyof T>. И последний — Product, что означает, что его свойства должны иметь уникальные ValidData<T>.
Хорошо, давайте проверим:
const data = validData({
'VALUE1': {
num1: { id: 1, name: '2' },
num2: { id: 2, name: '2' },
},
'VALUE2': {
num1: { id: 1, name: '2' },
},
'VALUE3': {
num1: { id: 1, name: '2' },
},
}); // okay
const data2 = validData({
'VALUE1': {
num1: { id: 1, name: '2' }, // error
num2: { id: 1, name: '2' }, // error
},
'VALUE2': {
num1: { id: 1, name: '2' },
},
'VALUE3': {
num1: { id: 1, name: '2' },
},
});
Выглядит неплохо. Ибо id всё проходит. Для data1 свойство data2 не работает, а свойства 'VALUE1' как для id, так и для num1 содержат ошибки, говорящие о том, что они не могут быть назначены num2. Это не самое красивое сообщение об ошибке, но оно работает.
Вы можете сделать что-нибудь сложное, чтобы сделать сообщение об ошибке более понятным:
type Repl<T, U> = [T] extends [never] ? U : T;
// https://github.com/microsoft/TypeScript/issues/23689 workaround
interface ErrMsg<T> { nope: never, msg: T }
type UniqueId<T extends Record<keyof T, Product>> =
{ [K in keyof T]: {
id: Repl<Exclude<T[K]["id"], T[Exclude<keyof T, K>]["id"]>,
ErrMsg<`conflicts with ${string &
{ [P in keyof T]:
T[K]["id"] & T[P]["id"] extends never ? never : Exclude<P, K>
}[keyof T]}`>>,
name: string
} }
const data2 = validData({
'VALUE1': {
num1: { id: 1, name: '2' }, // ErrMsg<"conflicts with num2">
num2: { id: 1, name: '2' }, // ErrMsg<"conflicts with num1">
},
'VALUE2': {
num1: { id: 1, name: '2' },
},
'VALUE3': {
num1: { id: 1, name: '2' },
},
});
Но я считаю, что это выходит за рамки данного вопроса, и не буду отвлекаться дальше, объясняя это здесь.
Соответствует ли такой подход вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?