You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于指定字段路径将不等长嵌套对象转换为对象数组

问题描述

给定嵌套结构的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
  }

现需更优雅的实现思路和代码。


优化实现方案

核心思路

  1. 遍历字段路径,区分包含数组节点和不包含数组节点的路径:
    • 含数组节点的路径:遍历数组每个元素,提取对应层级的字段值
    • 不含数组节点的路径:提取单一值后,复制为与目标数组长度一致的数组
  2. 以数组节点的长度为基准,将各字段的数组按索引对齐,组合成目标对象数组

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 10:36:28