Skip to content

Instantly share code, notes, and snippets.

@watert
Created September 30, 2019 07:36
Show Gist options
  • Save watert/4167c712e34c2f8f9fde43e311be21c6 to your computer and use it in GitHub Desktop.
Save watert/4167c712e34c2f8f9fde43e311be21c6 to your computer and use it in GitHub Desktop.
nested object set and get // extract from lodash
/*
path access: object util get/set with path access
get(obj, path) => value of obj at path:
expect(get({ a: 0 }, 'b[1].c')).toBe(null);
expect(get({ b: [0, { c: 'CCC' }] }, 'b[1].c')).toBe('CCC');
set(obj, path, value) => new object updates object value at path
expect(set({a: 0}, 'b.c', 1)).toMatchObject({ a:0, b: {c: 1} });
expect(set({a: 0}, 'b.c', 2, (v) => v * 10).b.c).toBe(20);
expect(set({ a: 0 }, 'b[1]', 1)).toMatchObject({ a:0, b: [undefined, 1] });
expect(set({ a: [0] }, 'a[2]', 'VALUE')).toMatchObject({ a:[0, undefined, 'VALUE'] });
expect(set({ a: [0] }, 'a[2].c', 'VALUE')).toMatchObject({ a:[0, undefined, {c: 'VALUE'}] });
set(obj, path, updater) => new object updates object with updater at path
*/
const reLeadingDot = /^\./;
const rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
const reEscapeChar = /\\(\\)?/g;
const isNaN = (i) => i !== i
const isArr = (a) => a instanceof Array
const isArrIndex = (key) => !isNaN(parseInt(key, 10));
export function stringToPath(string) {
const result = [];
if (reLeadingDot.test(string)) {
result.push('');
}
string.replace(rePropName, function(match, number, quote, string) {
result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
});
return result;
};
export function toPath(path) {
return typeof path === 'string' ? stringToPath(path) : [].concat(path);
}
export function get(obj, path) {
return stringToPath(path).reduce((memo, key) => {
if (memo == null || !(key in memo)) return null;
return memo[key];
}, obj);
}
const isPositiveInteger = (n) => n >>> 0 === parseFloat(n); // 这个版本只支持最大到 4294967295( Math.pow(2,32) - 1) ) 的数值
// function isPositiveInteger(n) { // 这个版本能支持到 Number.MAX_VALUE
// return 0 === n % (!isNaN(parseFloat(n)) && 0 <= ~~n);
// }
// 详细文档见 SO https://stackoverflow.com/questions/10834796/validate-that-a-string-is-a-positive-integer
function _set(obj, pathArr, value, customizer = r => r) {
if (pathArr.length === 0) {
return typeof value === 'function' ? value(obj) : value;
}
const firstKey = pathArr[0];
if (typeof obj !== 'object') {
obj = isPositiveInteger(firstKey) ? [] : {};
}
const val = _set(obj[firstKey], pathArr.slice(1), value, customizer);
let next;
if (Array.isArray(obj)) {
next = [].concat(obj);
next[firstKey] = val;
} else {
next = { ...obj, [firstKey]: val };
}
if (pathArr.length === 1) {
return customizer(next, firstKey, obj);
}
return next;
}
export default function set(obj, path, value, customizer) {
return _set(obj, toPath(path), value, customizer);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment