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

在Deno/Oak中如何读取含数组的多部分表单数据?

Oak 表单数据数组处理方案

问题根源

Oak 默认对表单数据的解析逻辑和 Express 不同:

  • application/x-www-form-urlencoded 编码返回 URLSearchParams 对象,需手动提取多字段值为数组
  • multipart/form-data 编码需遍历所有 parts 收集同名字段,默认不会自动组装数组

1. 处理 URL 编码表单(application/x-www-form-urlencoded)

直接使用 URLSearchParams.getAll() 可获取同名字段的数组值,也可以封装一个转换函数,自动将多字段转为数组,实现类似 Express 的 req.body 格式:

转换函数实现

function parseUrlencodedBody(params: URLSearchParams): Record<string, string | string[]> {
  const body: Record<string, string | string[]> = {};
  for (const [key, value] of params.entries()) {
    if (body[key]) {
      // 已有值则转为数组或追加元素
      body[key] = Array.isArray(body[key]) 
        ? [...(body[key] as string[]), value] 
        : [body[key] as string, value];
    } else {
      body[key] = value;
    }
  }
  return body;
}

路由中使用

import { Application, Router, Context } from "https://deno.land/x/oak@v12.6.0/mod.ts";

const router = new Router();

router.post("/submit-urlencoded", async (ctx: Context) => {
  // 解析URL编码表单
  const params = await ctx.request.body({ type: "form" }).value;
  // 转换为类似Express的body格式
  const body = parseUrlencodedBody(params);
  
  // 示例:body.genre 会是数组(如果勾选多个复选框)
  ctx.response.body = { success: true, data: body };
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

2. 处理 Multipart 表单(multipart/form-data)

Multipart 表单的每个字段是独立的 Part 对象,需遍历所有 parts 收集同名字段值:

解析函数实现

async function parseMultipartBody(ctx: Context): Promise<Record<string, string | string[]>> {
  const body: Record<string, string | string[]> = {};
  const parts = ctx.request.body({ type: "form-data" }).value;

  for await (const part of parts) {
    if (!part.filename) { // 跳过文件字段,如需处理可单独添加逻辑
      const value = await part.text();
      if (body[part.name]) {
        body[part.name] = Array.isArray(body[part.name])
          ? [...(body[part.name] as string[]), value]
          : [body[part.name] as string, value];
      } else {
        body[part.name] = value;
      }
    } else {
      // 处理文件字段示例:可保存文件到本地或获取文件信息
      body[part.name] = {
        filename: part.filename,
        contentType: part.contentType,
        // 如需读取文件内容:await part.arrayBuffer()
      };
    }
  }
  return body;
}

路由中使用

router.post("/submit-multipart", async (ctx: Context) => {
  const body = await parseMultipartBody(ctx);
  ctx.response.body = { success: true, data: body };
});

3. 统一中间件(自动适配编码类型)

封装一个中间件,自动根据请求的 Content-Type 解析表单,直接在 ctx.state.body 中获取类似 Express 的格式化数据:

async function formBodyMiddleware(ctx: Context, next: () => Promise<unknown>) {
  const contentType = ctx.request.headers.get("content-type") || "";

  if (contentType.includes("application/x-www-form-urlencoded")) {
    const params = await ctx.request.body({ type: "form" }).value;
    ctx.state.body = parseUrlencodedBody(params);
  } else if (contentType.includes("multipart/form-data")) {
    ctx.state.body = await parseMultipartBody(ctx);
  }

  await next();
}

// 注册中间件
const app = new Application();
app.use(formBodyMiddleware);
app.use(router.routes());
app.use(router.allowedMethods());

// 路由中直接使用
router.post("/submit", async (ctx: Context) => {
  ctx.response.body = { success: true, data: ctx.state.body };
});

这样处理后,你就能像在 Express 中使用 req.body 一样,直接获取包含字符串和数组的表单数据,无需手动处理不同编码的差异。

内容的提问来源于stack exchange,提问作者Sachin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 01:20:28