(TypeScript 5.0.2) Можно ли вывести безопасный тип для объекта json после преобразования его ключей?

Я борюсь с вопросом здесь. Я не нашел, можно ли это сделать в 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 06.04.2023 05:54

Вы же этого не сделали... Сейчас я попробую, а потом вернусь сюда, чтобы рассказать вам! Спасибо брат!

NandoMB 06.04.2023 14:53

@jcalz, это почти то, что я хочу в конце концов ... Я внес некоторые [изменения] () в ваш код, чтобы достичь своей цели. Я не знаю, сможете ли вы сделать эти изменения лучше, чем я... Если да, это будет потрясающе! Еще раз спасибо за вашу помощь и, пожалуйста, напишите ответ, потому что я могу проверить, как написал.

NandoMB 06.04.2023 16:31

Вы ничего не связали, но я обеспокоен тем, что объем расширяется; Я рад написать ответ на заданный вопрос, но последующие комментарии не очень хороши, и их следует переместить в новый вопрос, если это важно.

jcalz 06.04.2023 16:35

Мой плохой хахахаха! Это изменения

NandoMB 06.04.2023 16:52

Я мог бы сделать это по-другому, но если это работает для вас, то все в порядке. Как я уже говорил, это не совсем подходящий форум для дополнительных вопросов.

jcalz 06.04.2023 16:59
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
6
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете написать 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, и значение времени выполнения согласуется с ним.

Площадка ссылка на код

Другие вопросы по теме