get-wild
Version:
Pluck nested properties from an object with support for wildcards
150 lines (121 loc) • 3.98 kB
JavaScript
const TOKEN = /(?:(?:\[(?:(?:(["'])((?:\\.|(?:(?!\1).))*)\1)|([-+]?\d+))\])|(?:(?:^|(?:(?!^)\.))([^\s"'\`\[\].\\]+))|(.))/g;
const ESCAPED = /\\(.)/g;
const parser = path => {
const steps = [];
for (const {
index,
1: quote,
2: quoted,
3: integer,
4: name
} of path.matchAll(TOKEN)) {
let step;
if (name) {
step = name;
} else if (integer) {
step = +integer;
} else if (quote) {
step = quoted.replace(ESCAPED, (match, escaped) => escaped === quote ? quote : match);
} else {
throw new SyntaxError(`Invalid step @ ${index}: ${JSON.stringify(path)}`);
}
steps.push(step);
}
return steps;
};
const {
isArray: __isArray
} = Array;
const {
defineProperty: __defineProperty,
values: defaultCollect
} = Object;
const NO_MAP = Symbol();
const NO_FLAT_MAP = Symbol();
const getter$1 = (options = {}) => {
const {
collect = defaultCollect,
default: $$default,
flatMap: $flatMap = '*',
map: $map = '**',
parser: parser$1 = parser,
split = parser$1
} = options;
const flatMap = $flatMap === false ? NO_FLAT_MAP : $flatMap;
const map = $map === false ? NO_MAP : $map;
const parse = typeof split === 'string' ? path => path.split(split) : split;
const toArray = (obj, ...args) => {
// ignore this warning to avoid adding a redundant check to appease
// TypeScript for something which works in JavaScript
//
// > A spread argument must either have a tuple type or be passed to a
// > rest parameter.
//
// @ts-ignore
return __isArray(obj) ? obj : collect(obj, ...args);
}; // XXX the name is important; if omitted, the `get` referenced in the body
// of this function refers to the default `get` export defined at the bottom
// of the file rather than this `get`, which may have different options
function get(obj, path, ...rest) {
const $default = rest.length ? rest[0] : $$default;
const coalesce = it => it === undefined ? $default : it;
let props;
if (__isArray(path)) {
props = path;
} else {
const type = path === null ? 'null' : typeof path;
if (type === 'string') {
props = parse(path);
} else if (type === 'number' || type === 'symbol') {
props = [path];
} else {
throw new TypeError(`Invalid path: expected a string, array, number, or symbol, got: ${type}`);
}
}
const lastIndex = props.length - 1;
for (let i = 0; i <= lastIndex; ++i) {
if (!obj) {
return $default;
}
const prop = props[i];
const isFlatMap = prop === flatMap;
if (isFlatMap || prop === map) {
const values = toArray(obj);
let recurse;
if (i === lastIndex) {
recurse = coalesce; // base case
} else {
const tailProps = props.slice(i + 1);
recurse = value => get(value, tailProps, $default);
}
return isFlatMap ? values.flatMap(recurse) : values.map(recurse);
} else if (Number.isInteger(prop)) {
const values = toArray(obj, prop);
const index = prop < 0 ? values.length + prop : prop;
obj = values[index];
} else {
obj = obj[prop];
}
}
return coalesce(obj);
} // expose the supplied/generated parser as a (read-only) property on the
// function. this is for the currying wrappers and isn't exposed by the
// curried functions
return __defineProperty(get, 'parse', {
value: parse
});
};
const get$1 = getter$1();
const parse = (path, get) => {
return typeof path === 'string' ? get.parse(path) : path;
};
const _get = (get, $$default) => {
return (_path, ...rest) => {
const path = parse(_path, get);
const $default = rest.length ? rest[0] : $$default;
return value => get(value, path, $default);
};
};
const get = _get(get$1);
const getter = (options = {}) => _get(getter$1(options), options.default);
export { get, getter };