Как проверить "путь" к элементу json на корректность?
Есть большая и запутанная json-схема. Нужно вытащить из неё несколько значений.
var result = res.query.pages[45454].revisions[0].slots.main["*"];
Можно ли как-то быстро проверить, существует ли элемент по такому пути и правилен ли сам путь (все ли элементы в наличии)? Банально, не выдаёт ли "путь" ошибок null или undefined. Чтобы не выстраивать вот такие лесенки в ад:
if (res.hasOwnProperty("query"))
if (res.query.hasOwnProperty("pages"))
if (res.query.pages.hasOwnProperty(45454))
if (res.query.pages[45454].hasOwnProperty("revisions"))
if (res.query.pages[45454].revisions.hasOwnProperty(0))
if (res.query.pages[45454].revisions[0].hasOwnProperty("slots"))
if (res.query.pages[45454].revisions[0].slots.hasOwnProperty("main"))
if (res.query.pages[45454].revisions[0].slots.main.hasOwnProperty("*"))
var result = res.query.pages[45454].revisions[0].slots.main["*"];
Ответы (2 шт):
Проверить наличие объектного свойства можно таким образом (используя то что объект в JS всегда является truthy значением):
const isPropExists = Boolean(res?.query?.pages?.[45454]?.revisions?.[0]?.slots?.main);
Получение значения глубоко вложенного свойства main, соотв. может выглядеть как:
// конечно же, тут результатом может оказаться и null, и undefined, но ошибок брошено не будет
let main = res?.query?.pages?.[45454]?.revisions?.[0]?.slots?.main;
// а тут мы используем fallback-значение (новый пустой объект)
main = res?.query?.pages?.[45454]?.revisions?.[0]?.slots?.main ?? {};
Наличие свойств примитивных типов, можно проверять множеством способов. Самое очевидное - именно тип значения и проверять. Для облегчения восприятия кода при таких проверках, можно использовать простые функции-хелперы:
// где-то в src/utils/some-helpful-stuff.js объявим (с последующим экспортом)...
const checkType = (type, val) => typeof val === type,
isBool = checkType.bind(this, 'boolean'),
isString = checkType.bind(this, 'string');
// ...и в основном коде (с импортами) становится более-менее понятно происходящее
const doWeHaveBoolX = isBool(res?.query?.pages?.[45454]?.revisions?.[0]?.slots?.main?.x),
resHasStringY = isString(res?.query?.pages?.[45454]?.revisions?.[0]?.slots?.main?.y);
MDN:
Оператор опциональной последовательности ?. проверяет наличие как собственных свойств, так и прототипных. Если же нужно проверить наличие свойства по цепочке именно собственных свойств вложенных объектов, то можно использовать подобную функцию с рекурсивным вызовом:
function getNestedProp(obj, path, type) {
if (!Array.isArray(path)) {
path = path
.replace(/([^\\])(\[['"`]?|\.|['"`]?\]\.)/g, '$1\0')
.replace(/\\\./g, '.')
.split('\0');
}
var len = path.length,
prop = len && path.shift();
if (!prop) {
var isFail = len || ((typeof type === 'string') && (typeof obj !== type));
return isFail ? void(0) : obj;
}
if (obj.hasOwnProperty(prop)) return getNestedProp(obj[prop], path, type);
}
const res = { a: { b: { c: [{ 'd.e': { f: { g: 'win' }}}]}}};
const getFromRes = getNestedProp.bind(this, res);
console.log(getFromRes('a["b"].c[0].d\\.e.f.g')); // 'win' (значение последнего свойства в цепочке)
console.log(getFromRes('a.b.c[0].d\\.e.ZZZ.g')); // undefined (OK: нет свойства `ZZZ`)
console.log(getFromRes('a.b.c[99].d\\.e.f.g')); // undefined (OK: нет эл-та массива с индексом `99`)
console.log(getFromRes('a.b.c.pop')); // undefined (OK: `pop` это прототипное св-во массива)
const but = { own: { valueOf: 'seems legit' }};
console.log(getNestedProp(but, 'own.valueOf')); // 'seems legit' (OK: тут свойство `valueOf` перекрывает прототипное)
// проверка типа встроена
console.log(getNestedProp(but, 'own.valueOf', 'number')); // undefined (OK: свойство есть, но тип значения не тот)
console.log(getNestedProp(but, 'own.valueOf', 'string')); // 'seems legit' (и свойство есть, и тип его значения соответствует)
Как минимум одним недостатком данной реализации является необходимость экранировать точки в именах свойств (см. в примере d.e: экранированно будет d\.e, но слэш в js-строке сам по себе требует экранирования, и получаем вид 'd\\.e').
// В функции намеренно использованы только возможности ES5.