[Medium] Object Path Parsing
1. Question Description
問題描述
實作物件路徑解析函式,能夠根據路徑字串獲取和設置巢狀物件的值。
需求
get函式:根據路徑獲取物件值
const obj = { a: { b: { c: 1 } } };
get(obj, 'a.b.c'); // 1
get(obj, 'a.b.d', 'default'); // 'default'
set函式:根據路徑設置物件值
const obj = {};
set(obj, 'a.b.c', 1);
// obj = { a: { b: { c: 1 } } }
2. Implementation: get Function
實作 get 函式
方法 1:使用 split 和 reduce
思路:將路徑字串分割成陣列,然後使用 reduce 逐層訪問物件。
function get(obj, path, defaultValue) {
// 處理邊界情況
if (!obj || typeof path !== 'string') {
return defaultValue;
}
// 將路徑字串分割成陣列
const keys = path.split('.');
// 使用 reduce 逐層訪問
const result = keys.reduce((current, key) => {
// 如果當前值為 null 或 undefined,返回 undefined
if (current == null) {
return undefined;
}
return current[key];
}, obj);
// 如果結果為 undefined,返回預設值
return result !== undefined ? result : defaultValue;
}
// 測試
const obj = {
a: {
b: {
c: 1,
d: [2, 3, { e: 4 }],
},
},
x: null,
};
console.log(get(obj, 'a.b.c')); // 1
console.log(get(obj, 'a.b.d[2].e')); // undefined(需要處理陣列索引)
console.log(get(obj, 'a.b.f', 'default')); // 'default'
console.log(get(obj, 'x.y', 'default')); // 'default'
方法 2:支援陣列索引
思路:處理路徑中的陣列索引,如 'a.b[0].c'。
function get(obj, path, defaultValue) {
if (!obj || typeof path !== 'string') {
return defaultValue;
}
// 正則表達式匹配:屬性名或陣列索引
// 匹配 'a', 'b', '[0]', 'c' 等
const keys = path.match(/[^.[\]]+|\[(\d+)\]/g) || [];
const result = keys.reduce((current, key) => {
if (current == null) {
return undefined;
}
// 處理陣列索引 [0] -> 0
if (key.startsWith('[') && key.endsWith(']')) {
const index = parseInt(key.slice(1, -1), 10);
return current[index];
}
return current[key];
}, obj);
return result !== undefined ? result : defaultValue;
}
// 測試
const obj = {
a: {
b: {
c: 1,
d: [2, 3, { e: 4 }],
},
},
};
console.log(get(obj, 'a.b.d[2].e')); // 4
console.log(get(obj, 'a.b.d[0]')); // 2
console.log(get(obj, 'a.b.d[5]', 'not found')); // 'not found'
方法 3:完整實作(處理邊界情況)
function get(obj, path, defaultValue) {
// 處理邊界情況
if (obj == null) {
return defaultValue;
}
if (typeof path !== 'string' || path === '') {
return obj;
}
// 解析路徑:支援 'a.b.c' 和 'a.b[0].c' 格式
const keys = path.match(/[^.[\]]+|\[(\d+)\]/g) || [];
let result = obj;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// 如果當前值為 null 或 undefined,返回預設值
if (result == null) {
return defaultValue;
}
// 處理陣列索引
if (key.startsWith('[') && key.endsWith(']')) {
const index = parseInt(key.slice(1, -1), 10);
result = result[index];
} else {
result = result[key];
}
}
return result !== undefined ? result : defaultValue;
}
// 測試
const obj = {
a: {
b: {
c: 1,
d: [2, 3, { e: 4 }],
},
},
x: null,
y: undefined,
};
console.log(get(obj, 'a.b.c')); // 1
console.log(get(obj, 'a.b.d[2].e')); // 4
console.log(get(obj, 'a.b.f', 'default')); // 'default'
console.log(get(obj, 'x.y', 'default')); // 'default'
console.log(get(obj, 'y.z', 'default')); // 'default'
console.log(get(null, 'a.b', 'default')); // 'default'
console.log(get(obj, '', obj)); // obj(空路徑返回原物件)
3. Implementation: set Function
實作 set 函式
方法 1:基本實作
思路:根據路徑創建巢狀物件結構,然後設置值。
function set(obj, path, value) {
if (!obj || typeof path !== 'string' || path === '') {
return obj;
}
// 解析路徑
const keys = path.match(/[^.[\]]+|\[(\d+)\]/g) || [];
// 創建巢狀結構
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
// 處理陣列索引
if (key.startsWith('[') && key.endsWith(']')) {
const index = parseInt(key.slice(1, -1), 10);
if (!Array.isArray(current[index])) {
current[index] = {};
}
current = current[index];
} else {
// 如果鍵不存在或不是物件,創建新物件
if (!current[key] || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
}
// 設置最後一個鍵的值
const lastKey = keys[keys.length - 1];
if (lastKey.startsWith('[') && lastKey.endsWith(']')) {
const index = parseInt(lastKey.slice(1, -1), 10);
if (!Array.isArray(current)) {
// 如果當前不是陣列,需要轉換
const temp = { ...current };
current = [];
Object.keys(temp).forEach((k) => {
current[k] = temp[k];
});
}
current[index] = value;
} else {
current[lastKey] = value;
}
return obj;
}
// 測試
const obj = {};
set(obj, 'a.b.c', 1);
console.log(obj); // { a: { b: { c: 1 } } }
set(obj, 'a.b.d[0]', 2);
console.log(obj); // { a: { b: { c: 1, d: [2] } } }