Внутри моего ответа API данных я пытаюсь перебрать массив объектов в items. Мне нужно отформатировать каждое значение внутри объекта. Каждый ключ в списке объектов items соответствует типу формата в моем ключе header. У меня также есть функция getFormat для форматирования значений

У меня вопрос: как бы я это сделал, учитывая мою структуру?

// Data
const data = {
  header: {
    date: {
      feature: "Date",
      format: "date",
    },
    description: {
      feature: "Description",
      format: "text",
    },
    salary: {
      feature: "Salary",
      format: "currency",
    },
    score: {
      feature: "Score",
      format: "float",
    },
    useage: {
      feature: "Useage",
      format: "percentage",
    },
  },
  items: [
    {
      date: "2021-02-08",
      description: "Light",
      salary: "50000",
      score: 20,
      useage: 20,
    },
    {
      date: "2021-02-08",
      description: "Heavy",
      salary: "60000",
      score: 50.235,
      useage: 30,
    }
  ],
};

// format helper
export const getFormat = (value: any, format: FormatProp) => {
  let options: Intl.NumberFormatOptions;

  switch (format) {
      case "currency":
          options = { currency: "USD", style: "currency" };
          break;
      case "percentage":
          options = { style: "percent", maximumFractionDigits: 2 };
          break;
      case "float":
          options = { maximumFractionDigits: 2, minimumFractionDigits: 2 };
          break;
      case "text":
          return value;
      case "date":
          return value;
      default:
          throw Error("Wrong Format!");
  }
  return Intl.NumberFormat("en-US", options).format(value);
};

export type FormatProp =
  | "currency"
  | "percentage"
  | "float"
  | "text"
  | "date";

Пожеланная выходная мощность

[{
  date: "2021-02-08",
  description: "Light",
  salary: "$50,000.00",
  score: "20.00",
  useage: "20%",
},
{
  date: "2021-02-08",
  description: "Heavy",
  salary: "$60,000",
  score: "50.24",
  useage: "30%",
}]
2
dko512 10 Фев 2021 в 06:18

2 ответа

Лучший ответ

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

Таким образом, мы будем map над элементами, использовать Object.entries, чтобы получить массив из key и связанных value, а затем использовать reduce для создания ключа объекта элемента по ключу.

// Data
const data = {
  header: {
    date: {
      feature: 'Date',
      format: 'date',
    },
    description: {
      feature: 'Description',
      format: 'text',
    },
    salary: {
      feature: 'Salary',
      format: 'currency',
    },
    score: {
      feature: 'Score',
      format: 'float',
    },
    useage: {
      feature: 'Useage',
      format: 'percentage',
    },
  },
  items: [{
      date: '2021-02-08',
      description: 'Light',
      salary: '50000',
      score: 20,
      useage: 20,
    },
    {
      date: '2021-02-08',
      description: 'Heavy',
      salary: '60000',
      score: 50.235,
      useage: 30,
    },
  ],
};

// format helper
const getFormat = (value, format) => {
  let options;

  switch (format) {
    case 'currency':
      options = {
        currency: 'USD',
        style: 'currency'
      };
      break;
    case 'percentage':
      options = {
        style: 'percent',
        maximumFractionDigits: 2
      };
      break;
    case 'float':
      options = {
        maximumFractionDigits: 2,
        minimumFractionDigits: 2
      };
      break;
    case 'text':
      return value;
    case 'date':
      return value;
    default:
      throw Error('Wrong Format!');
  }
  return Intl.NumberFormat('en-US', options).format(value);
};

const output = data.items.map(item => // iterate over items
  Object.entries(item).reduce((a, [key, value]) => { // for each item get the key/value pairs
    const header = data.header[key]; // find the associated header
    return {
      ...a,
      [key]: header ? getFormat(value, header.format) : value, // if the header exists lets get the formatted value back, else return the previous value
    };
  }, {}),
);

console.log(output);
2
Mateusz Siniarski 10 Фев 2021 в 03:55

Ввод ответа

Мы знаем, что наша неформатированная версия содержит смесь значений string и number. Мы также знаем, что когда мы закончим форматирование, все значения будут преобразованы в string.

Мы знаем, что data.header содержит те же ключи, что и элементы data.items, и что значения в data.header содержат свойство format. Значение format равно FormatProp или string, в зависимости от того, насколько строгими вы хотите быть. Это проще, если вы используете string, поскольку машинописный текст будет интерпретировать строковые значения в вашем объекте data как string, а не как их буквальные значения (если не используется as const).

Собирая все вместе, ответ API можно ввести как:

type Data<Keys extends string> = {
  header: Record<Keys, {
      feature: string;
      format: string;
    }>,
  items: Array<Record<Keys, number | string>>
}

Ввод функций карты

Мы знаем, что функция .format() объекта Intl.NumberFormat принимает только number, поэтому передача ей значения с типом any - не лучший вариант. Мы можем лучше. Давайте передавать ему только number значения. Допустим, что случаи, связанные с полями string, будут вызывать ошибку.

export const getFormat = (value: number, format: FormatProp): string => {
  let options: Intl.NumberFormatOptions;

  switch (format) {
      case "currency":
          options = { currency: "USD", style: "currency" };
          break;
      case "percentage":
          options = { style: "percent", maximumFractionDigits: 2 };
          break;
      case "float":
          options = { maximumFractionDigits: 2, minimumFractionDigits: 2 };
          break;
      default:
          throw Error("Wrong Format!");
  }
  return Intl.NumberFormat("en-US", options).format(value);
};

На самом деле лучше, если мы разрешим любое string как format, если это то, что вы делали в Data. Но я сохраняю FormatProp в союзе с string ради автозаполнения.

export const getFormat = (value: number, format: FormatProp | string): string => {

При отображении это в основном то же самое, что делает @Mateusz, за исключением того, что нам нужны дополнительные аннотации. В частности, мы должны ввести пустой объект {}, который мы reduce в качестве типа для полного объекта Record<Keys, string>, и мы должны ввести массив из Object.entries как [Keys, string | number][], чтобы получить ключи как их буквальные значения, а не только string.

const formatItems = <Keys extends string>(data: Data<Keys>): Array<Record<Keys, string>> => {
  return data.items.map( item => {
    // we have to assert the correct type here with `as` or else all keys are `string`
    return (Object.entries(item) as [Keys, string | number][]).reduce( 
      (formatted, [key, value]) => {
        formatted[key] = typeof value === "number" ? getFormat(value, data.header[key].format) : value;
        return formatted;
    }, {} as Record<Keys, string>); // assert the type for the incomplete object
  })
}

Вызов formatItems(data) с этим конкретным объектом data теперь возвращает следующий тип:

Record<"date" | "description" | "salary" | "score" | "useage", string>[]

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

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

2
Linda Paiste 10 Фев 2021 в 04:41
66130440