Я борюсь с вопросом здесь. Я не нашел, можно ли это сделать в Typescript...
Цель состоит в том, чтобы получить все ключи объекта и выполнить некоторые преобразования, не теряя при этом функции безопасности типов. Если у меня есть объект с ключами в тире, я бы хотел, например, преобразовать все ключи в змеиный регистр. Вот как близко я мог подобраться:
// 5.0.2 TypeScript Version
type InputType = {
'first-name': string;
};
function transform<T>(input: any) {
const result = {};
for (const key of input) {
const parsedKey = parseKey(key);
result[parsedKey] = input[key];
}
return result as Required<T>;
}
function parseKey(input: string) {
return input.replace(/(\-)/g, '_');
}
const before = { 'first-name': 'Fernando' };
const after = transform<InputType>(before);
// INFO: This works but it isn't what I want...
after['first-name'];
// INFO: This is what I want, but it isn't work...
after['first_name'];
// INFO: The "output type" should be something like this:
//
// type OutputType = {
// 'first_name': string;
// };
заранее спасибо
Вы же этого не сделали... Сейчас я попробую, а потом вернусь сюда, чтобы рассказать вам! Спасибо брат!
@jcalz, это почти то, что я хочу в конце концов ... Я внес некоторые [изменения] () в ваш код, чтобы достичь своей цели. Я не знаю, сможете ли вы сделать эти изменения лучше, чем я... Если да, это будет потрясающе! Еще раз спасибо за вашу помощь и, пожалуйста, напишите ответ, потому что я могу проверить, как написал.
Вы ничего не связали, но я обеспокоен тем, что объем расширяется; Я рад написать ответ на заданный вопрос, но последующие комментарии не очень хороши, и их следует переместить в новый вопрос, если это важно.
Мой плохой хахахаха! Это изменения
Я мог бы сделать это по-другому, но если это работает для вас, то все в порядке. Как я уже говорил, это не совсем подходящий форум для дополнительных вопросов.
Вы можете написать KebabToSnake<T>
служебный тип для преобразования строковых литеральных типов из случая кебаба в случая змеи, заменив тире символами подчеркивания:
type KebabToSnake<T extends string, A extends string = ""> =
T extends `${infer L}-${infer R}` ?
KebabToSnake<R, `${A}${L}_`> : `${A}${T}`
Это рекурсивный условный тип , который использует шаблонные литералы для разбора и объединения строковых литералов. Это хвостовой рекурсивный условный тип, поэтому он должен вести себя хорошо даже для очень длинных строк.
Как это работает: он принимает тип входного строкового литерала T
и создает или накапливает тип выходного строкового литерала A
. Если T
содержит тире, строка в первом тире разбивается на левую L
и правую R
стороны. Затем мы рекурсивно используем правую часть R
в качестве новой строки для синтаксического анализа и добавляем L
и подчеркивание к A
в качестве нового накопленного вывода. Если T
не содержит тире, то мы просто добавляем T
к накопленному выводу и возвращаемся.
Давайте проверим это:
type Test1 =
KebabToSnake<"a-bc-def-ghij-klm-no-p-q-r-s-t--u---v----x-----y--------z">;
// type Test1 = "a_bc_def_ghij_klm_no_p_q_r_s_t__u___v____x_____y________z";
Итак, это выглядит хорошо.
Затем мы можем дать transform()
общую сигнатуру вызова, которая принимает input
объект типа T
и возвращает сопоставленный тип , где каждый string
-совместимый ключ свойства K
из T
переназначается с KebabToSnake<K>
, и каждое свойство значение оставлено в покое:
declare function transform<T extends object>(
input: T
): { [K in Extract<keyof T, string> as KebabToSnake<K>]: T[K] }
Давайте удостоверимся, что это работает для вызывающих абонентов:
const before = {
'first-name': 'Fernando',
'number-of-fingers-and-toes': 20,
'is-a-human-being-or-other-mammal': true
};
const after = transform(before);
/* const after: {
first_name: string;
number_of_fingers_and_toes: number;
is_a_human_being_or_other_mammal: boolean;
} */
Выглядит хорошо, тип вывода такой же, как и тип ввода, за исключением того, что ключи были преобразованы из корпуса кебаба в корпус змеи.
Что касается реализации, она в основном такая же (за исключением того, что я использую in
вместо of
при повторении ключей; у вас опечатка?). По сути, компилятор не может проверить, можно ли присвоить какое-либо конкретное значение сложному универсальному условному типу, такому как KebabToSnake<T>
, или его переназначенной версии. Вместо того, чтобы пытаться заставить компилятор делать то, что он не может, мы просто скажем компилятору не беспокоиться о типах, используя преднамеренно небезопасный тип any:
function transform<T extends object>(
input: T
): { [K in Extract<keyof T, string> as KebabToSnake<K>]: T[K] } {
const result: any = {};
for (const key in input) {
const parsedKey = parseKey(key);
result[parsedKey] = input[key];
}
return result;
}
Это компилируется без ошибок. И, наконец, давайте просто убедимся, что это действительно работает так, как рекламируется:
console.info(after.first_name.toUpperCase()); // "FERNANDO"
console.info(after.number_of_fingers_and_toes.toFixed(1)) // "20.0"
Выглядит неплохо. Компилятор знает тип after
, и значение времени выполнения согласуется с ним.
этот подход то, что вы ищете? Если да, то напишу поясняющий ответ (ну, завтра... мне пора спать 😴); если нет, то что мне не хватает?