Ввод универсальной функции, которая извлекает данные из объектов и (глубоко) вложенных объектов в Typescript

#typescript

#typescript

Вопрос:

В прошлом я создал универсальную функцию с использованием ES6 и хочу повторно использовать ее в новом проекте React Typescript.

Функция называется getNestedValuesFromObject и извлекает данные из объектов и (глубоко) вложенных объектов.

 const getValueFromNestedObject = (objectPath, baseObject) => {
    if (typeof objectPath !== 'string') return undefined;
    return objectPath
        .split('.')
        .reduce((object, key) => (object amp;amp; object[key] !== 'undefined' ? object[key] : undefined),
            baseObject);
};

export default getValueFromNestedObject;
 

И может использоваться следующим образом:

 const testObject = {
  exampleString: 'string',
  nestedObject: {
   exampleNumber: 1,
   nestedNestedObject: {
     exampleArray: [1, 2, 3],
   }
 }
}

const arrayFromNestedNestedObject = getValueFromNestedObject('nestedObject.nestedNestedObject', testObject);
 

Можете ли вы помочь ввести эту функцию?
Очевидно, что ObjectPath — это строка, а BaseObject может быть универсальным и возвращаемым типом.

Комментарии:

1. Просто FWIW, функция, которую вы показали, есть getValueFromNestedObject , но в вашем примере используется getNestedValuesFromObject .

Ответ №1:

Проблема с такого рода функциями заключается в том, что они по своей сути не типобезопасны. Вы предоставляете путь к объекту в виде строки, предполагая, что эта строка действительна для данного объекта (и нет никакого способа избежать этого предположения), и возвращаете любое значение, которое вы в конечном итоге получаете. Черт возьми, я бы посоветовал не использовать ее. Вместо этого используйте необязательную цепочку (и, при необходимости, нулевое объединение):

 const arrayFromNestedNestedObject = testObject.nestedObject?.nestedNestedObject;
// or
const arrayFromNestedNestedObject = testObject.nestedObject?.nestedNestedObject ?? defaultValue;
 

Таким образом, вы получаете проверку типа.

Но если вы хотите использовать функцию:

  • Как вы говорите, path будет string .
  • baseObject вероятно, она должна быть действительно широко открыта, например {[key: string]: any} .
  • Возвращаемое значение проблематично. Это может быть что угодно.

Для возвращаемого значения у вас в основном есть два варианта:

  1. Возвращаемое значение функции может быть ужасным any .
  2. Функция может принимать требуемый параметр типа, указывающий, какой конечный тип вы ожидаете.

У них обоих есть плюсы и минусы. Плюсом any является то, что вы можете назначить его чему угодно. Итак, если у вас есть:

 let num: number = getValueFromNestedObject("the.number", obj);
 

это сработает. Недостатком является то, что она будет работать, даже если значение, которое находит функция, не является числом, поэтому вы не получите ошибку компиляции, но поведение во время выполнения будет (вероятно) неправильным.

Требуемый общий параметр имеет почти ту же проблему: если вы думаете, что результат будет number и будет:

 let num = getValueFromNestedObject<number>("the.number", obj);
 

… и это не так, снова вы не получаете ошибки компиляции, но поведение во время выполнения не очень хорошее.

Просто для полноты, вот функция с any возвращаемым типом:

 const getValueFromNestedObject = (objectPath: string, baseObject: {[key: string]: any}): any => {
    if (typeof objectPath !== 'string') return undefined;
    return objectPath
        .split('.')
        .reduce((object, key) => (object amp;amp; object[key] !== 'undefined' ? object[key] : undefined),
            baseObject);
};
 

Ссылка на игровую площадку

Чтобы устранить проблему с возвращаемым типом, вы могли бы рассмотреть возможность использования нескольких функций:

  • getStringFromNestedObject
  • getNumberFromNestedObject
  • getBooleanFromNestedObject
  • getObjectFromNestedObject

… где все они являются оболочками для вышеуказанного, но с определенными типами возвращаемых данных:

 const getStringFromNestedObject = (objectPath: string, baseObject: {[key: string]: any}): string => {
    const result = getValueFromNestedObject(objectPath, baseObject);
    if (typeof result !== "string") {
        throw new Error(`Invalid assertion, ${objectPath} didn't result in a string`);
    }
    return resu<
};
 

Он может быть перегружен, чтобы разрешить undefined :

 function getStringFromNestedObject(objectPath: string, baseObject: {[key: string]: any}, allowUndefined: true): string | undefined;
function getStringFromNestedObject(objectPath: string, baseObject: {[key: string]: any}, allowUndefined: false): string;
function getStringFromNestedObject(objectPath: string, baseObject: {[key: string]: any}, allowUndefined = false): string | undefined {
    const result = getValueFromNestedObject(objectPath, baseObject);
    if (typeof result === "undefined" amp;amp; allowUndefined) {
            return resu<
    }
    if (typeof result !== "string") {
        throw new Error(`Invalid assertion, ${objectPath} didn't result in a string`);
    }
    return resu<
}
 

И т.д.

Но опять же, я предлагаю использовать необязательное объединение в цепочки и / или нулевое объединение, чтобы вы получали ошибки во время компиляции из-за несоответствий типов.

Комментарии:

1. Спасибо вам за ваше быстрое и подробное объяснение! Эта функция была создана, когда мы все еще использовали ES5. Затем преобразовал его в ES6, не зная, что возможна дополнительная цепочка. Работая с React в сочетании с typescript, я использовал необязательную цепочку и буду использовать ее для замены этой ненужной функции.

2. @drw026 — Да, такого рода функции иногда были действительно полезны несколько лет назад, но ситуация изменилась. 🙂