0

0

JavaScript中将扁平化表单数据转换为结构化嵌套数组的教程

DDD

DDD

发布时间:2025-11-27 16:04:02

|

237人浏览过

|

来源于php中文网

原创

JavaScript中将扁平化表单数据转换为结构化嵌套数组的教程

本教程详细阐述了如何利用javascript将`formdata`或类似扁平对象中,以`key[index][property]`格式存储的数据转换为结构化的嵌套数组。文章将分步介绍基础的键值对转换和包含多层数组的复杂结构转换,通过清晰的代码示例和深入解析,帮助开发者掌握高效处理此类数据重构的专业技巧。

引言:理解扁平化数据结构

在Web开发中,尤其是在处理HTML表单提交时,我们经常会遇到一种扁平化的数据结构。例如,当表单中包含多个同名输入字段(如name="timeslots[0][start]"、name="timeslots[1][end]")时,使用FormData对象或将其解析为普通JavaScript对象后,其键名会呈现出类似"key[index][property]"或"key[index]nestedKey[nestedIndex]"的扁平化形式。

这种结构虽然便于表单数据的收集和传输,但在前端进行数据处理、展示或进一步操作时,通常需要将其转换为更具层级感、更易于操作的嵌套对象或数组。例如,将上述扁平数据转换为如下结构:

{
  id: "id1",
  timeslots: [
    { start: "2023-06-10", end: "2023-06-09" },
    { start: "2024-06-10", end: "2024-06-09" }
  ]
}

本文将详细介绍如何利用JavaScript的强大功能,结合正则表达式,高效地实现这种数据结构的转换。

基础转换:构建对象数组

首先,我们来看一个相对简单的场景:将扁平化的"timeslots[index][property]"形式的键值对,转换为一个包含start和end属性的对象数组。

立即学习Java免费学习笔记(深入)”;

原始数据示例:

const inputObject = {
  id: "id1",
  "timeslots[0][start]": "2023-06-10",
  "timeslots[0][end]": "2023-06-09",
  "timeslots[1][start]": "2024-06-10",
  "timeslots[1][end]": "2024-06-09"
};

目标数据结构:

{
  id: "id1",
  timeslots: [
    { start: "2023-06-10", end: "2023-06-09" },
    { start: "2024-06-10", end: "2024-06-09" }
  ]
}

转换步骤与代码实现:

我们可以利用Object.entries()遍历对象的键值对,并通过filter()筛选出与timeslots相关的键。随后,使用map()和正则表达式提取出数组索引和属性名,最后通过reduce()将这些分散的属性合并到对应的数组元素中。

const inputObject = {
  id: "id1",
  "timeslots[0][start]": "2023-06-10",
  "timeslots[0][end]": "2023-06-09",
  "timeslots[1][start]": "2024-06-10",
  "timeslots[1][end]": "2024-06-09"
};

const transformedObject = {
  id: inputObject.id, // 保留id属性
  timeslots: Object.entries(inputObject) // 获取所有键值对
    .filter(([key]) => key.startsWith("timeslots")) // 筛选出以"timeslots"开头的键
    .map(([key, value]) => {
      // 使用正则表达式匹配键名,提取索引和属性名
      const [, index, property] = key.match(/timeslots\[(\d+)\]\[(start|end)\]/);
      return { [property]: value, index: parseInt(index) }; // 返回包含属性和索引的对象
    })
    .reduce((acc, { index, ...rest }) => {
      // 使用reduce将相同索引的属性合并到同一个对象中
      acc[index] = acc[index] || {}; // 如果该索引位置还没有对象,则初始化为空对象
      Object.assign(acc[index], rest); // 将当前属性合并到对应索引的对象中
      return acc;
    }, []) // 初始累加器为一个空数组
};

console.log(transformedObject);
/*
输出:
{
  id: 'id1',
  timeslots: [
    { start: '2023-06-10', end: '2023-06-09' },
    { start: '2024-06-10', end: '2024-06-09' }
  ]
}
*/

代码解析:

Vondy
Vondy

下一代AI应用平台,汇集了一流的工具/应用程序

下载
  1. Object.entries(inputObject): 将输入对象转换为一个包含[key, value]对的数组。
  2. .filter(([key]) => key.startsWith("timeslots")): 过滤掉所有不以"timeslots"开头的键,只保留我们关注的时间段数据。
  3. .map(([key, value]) => { ... }): 遍历过滤后的键值对。
    • key.match(/timeslots\[(\d+)\]\[(start|end)\]/): 这是核心部分,使用正则表达式来解析键名。
      • timeslots\[(\d+)\]: 匹配"timeslots[",然后捕获括号内的数字(作为索引)。
      • \[(start|end)\]: 匹配"[",然后捕获"start"或"end"(作为属性名)。
      • match的结果是一个数组,[0]是完整匹配的字符串,[1]是第一个捕获组(索引),[2]是第二个捕获组(属性名)。
    • return { [property]: value, index: parseInt(index) }: 创建一个临时对象,包含提取出的属性名、对应的值以及转换后的数字索引。
  4. .reduce((acc, { index, ...rest }) => { ... }, []): 这是一个强大的方法,用于将一个数组的所有元素归约为一个单一的值(在这里是一个数组)。
    • acc是累加器,初始值是一个空数组[]。
    • { index, ...rest }:解构map阶段返回的临时对象,将index单独取出,其余属性(如{ start: "..." }或{ end: "..." })打包到rest中。
    • acc[index] = acc[index] || {};: 确保acc数组的index位置有一个对象。如果该位置为空(undefined),则初始化为一个空对象。
    • Object.assign(acc[index], rest);: 将rest中的属性合并到acc[index]对应的对象中。这样,start和end属性最终会合并到同一个timeslots子对象中。

进阶转换:处理嵌套数组

现在,考虑一个更复杂的场景:除了start和end,timeslots对象中还可能包含一个嵌套的disability数组,其键名形式为"timeslots[index]disability[nestedIndex]"。

增强型原始数据示例:

const inputObject = {
    id: "id1",
    "timeslots[0][start]": "2023-06-10",
    "timeslots[0][end]": "2023-06-09",
    "timeslots[0]disability[3]": "D3", // 注意这里的键名格式
    "timeslots[0]disability[4]": "D4",
    "timeslots[1][start]": "2024-06-10",
    "timeslots[1][end]": "2024-06-09",
    "timeslots[1]disability[1]": "D1",
    "timeslots[1]disability[5]": "D5"
};

目标数据结构:

{
  id: "id1",
  timeslots:[
   { start: "2023-06-10", end: "2023-06-09", disability: ['D3', 'D4'] },
   { start: "2024-06-10", end: "2024-06-09", disability: ['D1', 'D5'] }
  ]
 }

转换步骤与代码实现:

对于这种更复杂的结构,我们需要更精细地处理键名匹配。一种直观的方法是遍历所有键,然后根据不同的正则表达式模式进行条件判断和数据填充。

const inputObject = {
    id: "id1",
    "timeslots[0][start]": "2023-06-10",
    "timeslots[0][end]": "2023-06-09",
    "timeslots[0]disability[3]": "D3",
    "timeslots[0]disability[4]": "D4",
    "timeslots[1][start]": "2024-06-10",
    "timeslots[1][end]": "2024-06-09",
    "timeslots[1]disability[1]": "D1",
    "timeslots[1]disability[5]": "D5"
};

const timeslots = []; // 用于存储最终的timeslots数组
const keys = Object.keys(inputObject); // 获取所有键名

keys.forEach(key => {
    // 尝试匹配 "timeslots[index][start|end]" 模式
    const matchStartEnd = key.match(/timeslots\[(\d+)\]\[(start|end)\]/);

    if (matchStartEnd) {
        const index = parseInt(matchStartEnd[1]); // 提取索引
        const prop = matchStartEnd[2]; // 提取属性名 (start或end)

        if (!timeslots[index]) {
            timeslots[index] = {}; // 如果该索引位置还没有对象,则初始化
        }
        timeslots[index][prop] = inputObject[key]; // 赋值
    } else {
        // 如果不是 start/end 模式,尝试匹配 "timeslots[index]disability[nestedIndex]" 模式
        const matchDisability = key.match(/timeslots\[(\d+)\]disability\[(\d+)\]/);

        if (matchDisability) {
            const index = parseInt(matchDisability[1]); // 提取 timeslots 的索引
            const disabilityValue = inputObject[key]; // 获取 disability 的值

            if (!timeslots[index]) {
                timeslots[index] = {}; // 确保 timeslots 对象存在
            }
            if (!timeslots[index].disability) {
                timeslots[index].disability = []; // 如果 disability 数组不存在,则初始化
            }
            timeslots[index].disability.push(disabilityValue); // 将值添加到 disability 数组中
        }
    }
});

const outputObject = { id: inputObject.id, timeslots }; // 构建最终输出对象
console.log(outputObject);
/*
输出:
{
  id: 'id1',
  timeslots: [
    { start: '2023-06-10', end: '2023-06-09', disability: [ 'D3', 'D4' ] },
    { start: '2024-06-10', end: '2024-06-09', disability: [ 'D1', 'D5' ] }
  ]
}
*/

代码解析:

  1. timeslots = []: 初始化一个空数组,用于存储最终的timeslots数组。
  2. keys.forEach(key => { ... }): 遍历输入对象的所有键。
  3. matchStartEnd = key.match(/timeslots\[(\d+)\]\[(start|end)\]/): 尝试匹配start或end属性的键名模式。
    • 如果匹配成功,提取index和prop,并将其值赋给timeslots[index][prop]。在赋值前,会检查timeslots[index]是否存在,不存在则初始化为 {}。
  4. else { ... }: 如果第一个模式不匹配,则进入else分支,尝试匹配disability属性的键名模式。
    • matchDisability = key.match(/timeslots\[(\d+)\]disability\[(\d+)\]/): 匹配disability数组元素的键名。这里捕获了timeslots的索引和disability内部的索引(尽管disability内部索引在此例中未直接使用,但其存在表明这是一个数组元素)。
    • 如果匹配成功,提取timeslots的index和disabilityValue。
    • 同样,确保timeslots[index]和timeslots[index].disability都已初始化,然后使用push()将值添加到disability数组中。

这种forEach加条件判断的方法在处理多种不同键名模式时更为直观和灵活,易于扩展以支持更多复杂的嵌套结构。

注意事项与最佳实践

  1. 正则表达式的精确性: 正则表达式是解析键名的核心。请确保你的正则表达式能够准确匹配目标键名模式,并且捕获到正确的索引和属性名。不同的键名模式需要不同的正则表达式。
  2. 数据类型转换: 从正则表达式捕获到的索引通常是字符串类型。在将其用作数组索引或进行数值运算时,务必使用parseInt()将其转换为数字类型。
  3. 健壮性与错误处理: 实际应用中,输入数据可能不完全符合预期格式。可以添加额外的检查,例如,如果match结果为null,或者parseInt失败,如何处理这些情况。
  4. 可读性与维护性: 对于非常复杂的嵌套结构,一个巨大的forEach循环可能会变得难以维护。考虑将不同模式的解析逻辑封装成独立的辅助函数,提高代码的可读性和模块化。
  5. 性能考量: 对于大规模数据集,Object.entries()、filter()、map()和reduce()的链式调用可能会在性能上有所损耗,尤其是在重复创建中间数组时。然而,对于大多数Web应用场景,这种开销通常可以忽略不计。如果性能成为瓶颈,可以考虑更底层的循环优化。
  6. 通用性封装: 为了代码复用,可以将上述转换逻辑封装成一个通用函数,接收扁平对象和一组解析规则(例如,正则表达式和对应的处理逻辑)作为参数。

总结

通过本教程,我们学习了如何使用JavaScript,结合正则表达式和数组/对象操作方法(如Object.entries(), filter(), map(), reduce(), forEach()),将扁平化的FormData或类似对象转换为结构化的嵌套数组。无论是简单的键值对转换,还是包含多层嵌套数组的复杂结构,JavaScript都提供了强大的工具来高效地完成数据重构任务。掌握这些技巧,将有助于开发者更好地处理前端数据,提升应用的数据管理能力和用户体验。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

531

2023.06.20

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

258

2023.07.05

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

766

2023.07.05

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

219

2023.08.11

正则表达式空格
正则表达式空格

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。本专题为大家提供正则表达式相关的文章、下载、课程内容,供大家免费下载体验。

357

2023.08.31

Python爬虫获取数据的方法
Python爬虫获取数据的方法

Python爬虫可以通过请求库发送HTTP请求、解析库解析HTML、正则表达式提取数据,或使用数据抓取框架来获取数据。更多关于Python爬虫相关知识。详情阅读本专题下面的文章。php中文网欢迎大家前来学习。

293

2023.11.13

正则表达式空格如何表示
正则表达式空格如何表示

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。想了解更多正则表达式空格怎么表示的内容,可以访问下面的文章。

245

2023.11.17

正则表达式中如何匹配数字
正则表达式中如何匹配数字

正则表达式中可以通过匹配单个数字、匹配多个数字、匹配固定长度的数字、匹配整数和小数、匹配负数和匹配科学计数法表示的数字的方法匹配数字。更多关于正则表达式的相关知识详情请看本专题下面的文章。php中文网欢迎大家前来学习。

548

2023.12.06

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号