基于指定字段路径将不等长嵌套对象转换为对象数组
问题描述
给定嵌套结构的response对象:
const response = { "device": { "name": "Foo", "type": "Bar", "telemetry": [ { "timeStamp": "2022-06-01T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-02T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-03T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-04T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-05T00:00:00.000Z", "temperature": 100, "pressure": 50 } ] } };
以及指定字段路径的数组:
const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature']
需要将其转换为如下格式的对象数组:
[ {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-01T00:00:00.000Z", "device/telemetry/temperature": 100}, {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-02T00:00:00.000Z", "device/telemetry/temperature": 100}, {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-03T00:00:00.000Z", "device/telemetry/temperature": 100}, {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-04T00:00:00.000Z", "device/telemetry/temperature": 100}, {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-05T00:00:00.000Z", "device/telemetry/temperature": 100} ]
补充说明:response的结构可能完全不同,层级也可能更深,但可以保证选择的字段只会来自一个数组,不会同时选取多个数组的字段。
自己实现的粗糙方案:
function createRecord(key: string, value: any){ return new Map<string, any>([[key, value]]) } function getNestedData (data: any, fieldPath: string, records: Map<string, any[]>=new Map<string, any[]>()) { let dataPoints: any = []; const paths = fieldPath.split('/') paths.forEach((key, idx, arr) => { if(Array.isArray(data)){ data.forEach( (row: any) => { dataPoints.push(row[key]) } ) } else { data = data[key] if(idx + 1== paths.length){ dataPoints.push(data); } } }) records.set(fieldPath, dataPoints) return records } function getNestedFields(data: any, fieldPaths: string[]){ let records: Map<string, any>[] = [] let dataset: Map<string, any[]> = new Map<string, any[]>() let maxLength = 0; // Fetch all the fields fieldPaths.forEach((fieldPath) => { dataset = getNestedData(data, fieldPath, dataset) const thisLength = dataset.get(fieldPath)!.length; maxLength = thisLength > maxLength ? thisLength : maxLength; }) for(let i=0; i<maxLength; i++){ let record: Map<string, any> = new Map<string, any>() for(let [key, value] of dataset){ const maxIdx = value.length - 1; record.set(key, value[i > maxIdx ? maxIdx : i]) } records.push(record) } // Normalize into records return records }
现需更优雅的实现思路和代码。
优化实现方案
核心思路
- 遍历字段路径,区分包含数组节点和不包含数组节点的路径:
- 含数组节点的路径:遍历数组每个元素,提取对应层级的字段值
- 不含数组节点的路径:提取单一值后,复制为与目标数组长度一致的数组
- 以数组节点的长度为基准,将各字段的数组按索引对齐,组合成目标对象数组
TypeScript 实现代码
type FieldValue = any; type FieldValuesMap = Record<string, FieldValue[]>; // 根据路径提取对应的值数组,自动处理嵌套数组 function getFieldValues(data: any, path: string): FieldValue[] { const segments = path.split('/'); let current = data; let arrayIndex = -1; // 先找到路径中的数组节点位置 for (let i = 0; i < segments.length; i++) { if (Array.isArray(current)) { arrayIndex = i; break; } current = current[segments[i]]; // 路径遍历完未找到数组,返回单元素数组 if (i === segments.length - 1) { return [current]; } } // 处理包含数组的路径,遍历数组提取后续字段 if (arrayIndex !== -1) { return current.map((item: any) => { let subCurrent = item; for (let j = arrayIndex; j < segments.length; j++) { subCurrent = subCurrent[segments[j]]; } return subCurrent; }); } return []; } // 转换主函数 function transformResponse(data: any, fields: string[]): Record<string, FieldValue>[] { // 收集所有字段对应的值数组 const fieldValues: FieldValuesMap = {}; fields.forEach(field => { fieldValues[field] = getFieldValues(data, field); }); // 获取目标数组长度(根据补充说明,所有数组长度一致) const targetLength = Object.values(fieldValues).find(v => v.length > 1)?.length || 1; // 按索引组合成最终对象数组 return Array.from({ length: targetLength }, (_, idx) => { const record: Record<string, FieldValue> = {}; fields.forEach(field => { const values = fieldValues[field]; record[field] = values.length === 1 ? values[0] : values[idx]; }); return record; }); } // 测试调用 const result = transformResponse(response, fields); console.log(result);
实现优势
- 逻辑清晰:拆分字段提取与对象组合两个核心步骤,代码可读性更强
- 类型安全:通过类型定义明确数据结构,减少任意类型
any的滥用 - 简洁高效:利用数组原生方法替代复杂的Map操作,性能更优
- 扩展性强:天然支持任意层级的嵌套结构,无需修改核心逻辑适配不同场景
内容的提问来源于stack exchange,提问作者Dammi
相关产品推荐
相关产品推荐

