[Medium] Object Path Parsing
1. Question Description
Descripción del problema
Implementar funciones de análisis de rutas de objetos que puedan obtener y establecer valores de objetos anidados basándose en cadenas de ruta.
Requisitos
- Función
get: Obtener el valor de un objeto según la ruta
const obj = { a: { b: { c: 1 } } };
get(obj, 'a.b.c'); // 1
get(obj, 'a.b.d', 'default'); // 'default'
- Función
set: Establecer el valor de un objeto según la ruta
const obj = {};
set(obj, 'a.b.c', 1);
// obj = { a: { b: { c: 1 } } }
2. Implementation: get Function
Implementación de la función get
Método 1: Usando split y reduce
Enfoque: Dividir la cadena de ruta en un array y luego usar reduce para acceder al objeto nivel por nivel.
function get(obj, path, defaultValue) {
if (!obj || typeof path !== 'string') return defaultValue;
const keys = path.split('.');
const result = keys.reduce((current, key) => {
if (current == null) return undefined;
return current[key];
}, obj);
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 (necesita manejar índices de array)
console.log(get(obj, 'a.b.f', 'default')); // 'default'
console.log(get(obj, 'x.y', 'default')); // 'default'
Método 2: Soporte para índices de array
Enfoque: Manejar índices de array en la ruta, como 'a.b[0].c'.
function get(obj, path, defaultValue) {
if (!obj || typeof path !== 'string') return defaultValue;
const keys = path.match(/[^.[\]]+|\[(\d+)\]/g) || [];
const result = keys.reduce((current, key) => {
if (current == null) return undefined;
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'
Método 3: Implementación completa (manejo de casos límite)
function get(obj, path, defaultValue) {
if (obj == null) return defaultValue;
if (typeof path !== 'string' || path === '') return obj;
const keys = path.match(/[^.[\]]+|\[(\d+)\]/g) || [];
let result = obj;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (result == null) return defaultValue;
if (key.startsWith('[') && key.endsWith(']')) {
result = result[parseInt(key.slice(1, -1), 10)];
} 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(null, 'a.b', 'default')); // 'default'
console.log(get(obj, '', obj)); // obj (ruta vacía devuelve el objeto original)
3. Implementation: set Function
Implementación de la función set
Método 1: Implementación básica
Enfoque: Crear la estructura de objetos anidados según la ruta y luego establecer el valor.
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] } } }
Método 2: Implementación completa (manejo de arrays y objetos)
function set(obj, path, value) {
if (!obj || typeof path !== 'string' || path === '') return obj;
const keys = path.match(/[^.[\]]+|\[(\d+)\]/g) || [];
if (keys.length === 0) return obj;
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)) {
const temp = current; current = [];
Object.keys(temp).forEach((k) => { current[k] = temp[k]; });
}
if (current[index] == null) {
const nextKey = keys[i + 1];
current[index] = nextKey.startsWith('[') ? [] : {};
}
current = current[index];
} else {
if (current[key] == null) {
const nextKey = keys[i + 1];
current[key] = nextKey.startsWith('[') ? [] : {};
} else if (typeof current[key] !== 'object') {
const nextKey = keys[i + 1];
current[key] = nextKey.startsWith('[') ? [] : {};
}
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);
set(obj, 'a.b.d[0]', 2);
set(obj, 'x[0].y', 3);
console.log(obj); // { a: { b: { c: 1, d: [2] } }, x: [{ y: 3 }] }