Skip to content

实现 请求参数互转 工具

分类:developer | 标签:参数、Cookie、Header Cookie/Header/Query/form-data 与 JSON 四向互转

2.1 功能说明

Cookie/Header/Query/form-data 与 JSON 四向互转

2.1.1 使用指南

功能说明

在四种常见的 HTTP 请求参数写法与 JSON 之间双向转换。从浏览器开发者工具复制出来的 Cookie、请求头、查询字符串或表单体,可以一键变成结构化 JSON,方便填进接口测试工具或代码里;反过来也能把 JSON 还原成请求里能直接用的文本。转换全部在本地完成,不会发起任何网络请求。

四种参数格式

  • Cookie:形如 name=value; token=abc,用分号分隔,也支持每行一条。值中包含等号时只按第一个等号切分。
  • Header:每行一条,形如 Content-Type: application/json,用第一个冒号切分键与值,因此值里的冒号(例如 URL)会被完整保留。以冒号开头的 HTTP/2 伪头部(如 :authority)也能正确识别。
  • Query:形如 a=1&b=2。允许直接粘贴完整 URL 或带问号的片段,工具会自动截取问号后面的部分。
  • form-data:指 application/x-www-form-urlencoded 的请求体,形如 a=1&b=2,与 Query 的区别是空格用加号表示。

细节处理

  • 重复键:Query 和 form-data 中同名参数出现多次时,会自动合并成 JSON 数组;反向转换时数组会重新展开成多个同名参数。
  • URL 编码:Query 与 form-data 默认对值做解码与编码,可通过复选框关闭。form-data 方向会把空格编码成加号。解码失败时保留原始文本,不会中断转换。
  • 无值参数:形如 flag 这种没有等号的项,会转成值为空字符串的键。
  • 反向转换:输入必须是合法的 JSON 对象(最外层为花括号),值支持字符串、数字、布尔和字符串数组。

使用建议

在浏览器开发者工具的网络面板里对某个请求点「复制为 cURL」或直接复制请求头,把对应片段粘进来即可。转换后可以用「结果转回输入框」按钮做一次往返校验,确认解析没有丢失信息。

2.2 代码实现

2.2.1 组件结构

本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-inputel-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。

2.2.2 核心逻辑概览

<script setup> 中定义的主要函数/方法:

  • safeDecode()
  • splitFirst()
  • addPair()
  • parseCookie()
  • parseHeader()
  • parseQueryLike()
  • stringifyCookie()
  • stringifyHeader()
  • stringifyQueryLike()
  • convert()
  • swap()
  • loadSample()
  • clearAll()
  • copyOutput()
  • downloadOutput()

2.2.3 关键实现代码

以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。

vue
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import ToolPageShell from '@/components/ToolPageShell.vue'

const paramType = ref('cookie')
const direction = ref('toJson')
const urlDecode = ref(true)
const input = ref('')
const output = ref('')
const errorMsg = ref('')
const pairCount = ref(0)

const typeLabels = {
  cookie: 'Cookie 文本',
  header: 'Header 文本',
  query: 'Query 字符串',
  formdata: 'form-data 文本'
}

const inputLabel = computed(() => typeLabels[paramType.value])
const needDecodeOption = computed(() => paramType.value === 'query' || paramType.value === 'formdata')

const hints = {
  cookie: '用分号分隔的键值对,例如:sessionid=abc123; theme=dark;也支持每行一条',
  header: '每行一条请求头,用第一个冒号分隔,例如:Content-Type: application/json',
  query: '查询字符串,例如:a=1&b=2;也可以直接粘贴完整 URL',
  formdata: '表单体,例如:name=张三&age=20;空格以加号表示'
}
const hintText = computed(() => hints[paramType.value])

const placeholders = {
  cookie: 'sessionid=abc123; theme=dark; lang=zh-CN',
  header: 'Content-Type: application/json\nAuthorization: Bearer token123',
  query: 'https://example.com/list?page=1&size=20&tag=a&tag=b',
  formdata: 'username=admin&password=123456&remember=true'
}
const placeholderText = computed(() =>
  direction.value === 'toJson' ? placeholders[paramType.value] : '{\n  "key": "value"\n}'
)

function safeDecode(value) {
  try {
    return decodeURIComponent(value)
  } catch {
    return value
  }
}

function splitFirst(str, marker) {
  const idx = str.indexOf(marker)
  if (idx === -1) return [str, '']
  return [str.slice(0, idx), str.slice(idx + marker.length)]
}

function addPair(target, key, value) {
  if (!key) return
  if (Object.prototype.hasOwnProperty.call(target, key)) {
    if (Array.isArray(target[key])) target[key].push(value)
    else target[key] = [target[key], value]
  } else {
    target[key] = value
  }
}

function parseCookie(text) {
  const result = {}
  text
    .split(/[;\n\r]+/)
    .map(s => s.trim())
    .filter(Boolean)
    .forEach(item => {
      const [k, v] = splitFirst(item, '=')
      addPair(result, k.trim(), v.trim())
    })
  return result
}

function parseHeader(text) {
  const result = {}
  text
    .split(/\r?\n/)
    .map(s => s.trim())
    .filter(Boolean)
    .forEach(line => {
      // HTTP/2 伪头部以冒号开头,需保留首字符
      const isPseudo = line.startsWith(':')
      const body = isPseudo ? line.slice(1) : line
      const [k, v] = splitFirst(body, ':')
      const key = (isPseudo ? ':' : '') + k.trim()
      addPair(result, key, v.trim())
    })
  return result
}

function parseQueryLike(text, isForm) {
  const result = {}
  let body = text.trim()
  const qIndex = body.indexOf('?')
  if (qIndex !== -1) body = body.slice(qIndex + 1)
  const hashIndex = body.indexOf('#')
  if (hashIndex !== -1) body = body.slice(0, hashIndex)
  body
    .split(/[&\n\r]+/)
    .map(s => s.trim())
    .filter(Boolean)
    .forEach(item => {
      const [rawKey, rawValue] = splitFirst(item, '=')
      let key = rawKey
      let value = rawValue
      if (isForm) {
        key = key.replace(/\+/g, ' ')
        value = value.replace(/\+/g, ' ')
      }
      if (urlDecode.value) {
        key = safeDecode(key)
        value = safeDecode(value)
      }
      addPair(result, key, value)
    })
  return result
}

function stringifyCookie(obj) {
  const parts = []
  Object.entries(obj).forEach(([k, v]) => {
    const values = Array.isArray(v) ? v : [v]
    values.forEach(item => parts.push(`${k}=${item === null || item === undefined ? '' : item}`))
  })
  return parts.join('; ')
}

function stringifyHeader(obj) {
  const lines = []
  Object.entries(obj).forEach(([k, v]) => {
    const values = Array.isArray(v) ? v : [v]
    values.forEach(item => lines.push(`${k}: ${item === null || item === undefined ? '' : item}`))
  })
  return lines.join('\n')
}

function stringifyQueryLike(obj, isForm) {
  const parts = []
  Object.entries(obj).forEach(([k, v]) => {
    const values = Array.isArray(v) ? v : [v]
    values.forEach(item => {
      const raw = item === null || item === undefined ? '' : String(item)
      let key = k
      let value = raw
      if (urlDecode.value) {
        key = encodeURIComponent(k)
        value = encodeURIComponent(raw)
        if (isForm) {
          key = key.replace(/%20/g, '+')
          value = value.replace(/%20/g, '+')
        }
      }
      parts.push(`${key}=${value}`)
    })
  })
  return parts.join('&')
}

function convert() {
  errorMsg.value = ''
  pairCount.value = 0
  const text = input.value.trim()
  if (!text) {
    output.value = ''
    ElMessage.warning('请先输入要转换的内容')
    return
  }

  try {
    if (direction.value === 'toJson') {
      let obj
      if (paramType.value === 'cookie') obj = parseCookie(text)
      else if (paramType.value === 'header') obj = parseHeader(text)
      else obj = parseQueryLike(text, paramType.value === 'formdata')

      const keys = Object.keys(obj)
      if (!keys.length) {
        output.value = ''
        errorMsg.value = '没有解析出任何键值对,请检查输入格式是否与所选类型一致'
        return
      }
      pairCount.value = keys.length
      output.value = JSON.stringify(obj, null, 2)
    } else {
      let obj
      try {
        obj = JSON.parse(text)
      } catch (e) {
        output.value = ''
        errorMsg.value = 'JSON 解析失败:' + e.message
        return
      }
      if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
        output.value = ''
        errorMsg.value = '请输入最外层为对象(花括号)的 JSON'
        return
      }
      pairCount.value = Object.keys(obj).length
      if (paramType.value === 'cookie') output.value = stringifyCookie(obj)
      else if (paramType.value === 'header') output.value = stringifyHeader(obj)
      else output.value = stringifyQueryLike(obj, paramType.value === 'formdata')
    }
    ElMessage.success('转换完成')
  } catch (e) {
    output.value = ''
    errorMsg.value = '转换失败:' + e.message
  }
}

function swap() {
  if (!output.value) return
  input.value = output.value
  direction.value = direction.value === 'toJson' ? 'toText' : 'toJson'
  output.value = ''
  errorMsg.value = ''
}

function loadSample() {
  if (direction.value === 'toJson') {
    input.value = placeholders[paramType.value]
  } else {
    const samples = {
      cookie: '{\n  "sessionid": "abc123",\n  "theme": "dark"\n}',
      header: '{\n  "Content-Type": "application/json",\n  "Authorization": "Bearer token123"\n}',
      query: '{\n  "page": "1",\n  "size": "20",\n  "tag": ["a", "b"]\n}',
      formdata: '{\n  "username": "admin",\n  "remember": "true"\n}'
    }
    input.value = samples[paramType.value]
  }
  convert()
}

function clearAll() {
  input.value = ''
  output.value = ''
  errorMsg.value = ''
  pairCount.value = 0
}

async function copyOutput() {
  if (!output.value) return
  try {
    await navigator.clipboard.writeText(output.value)
    ElMessage.success('已复制到剪贴板')
  } catch {
    ElMessage.warning('复制失败,请手动选择文本复制')
  }
}

function downloadOutput() {
  if (!output.value) return
  const isJson = direction.value === 'toJson'
  const blob = new Blob([output.value], {
    type: isJson ? 'application/json;charset=utf-8' : 'text/plain;charset=utf-8'
  })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `${paramType.value}-params.${isJson ? 'json' : 'txt'}`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
  ElMessage.success('已开始下载')
}

watch([paramType, direction], () => {
  output.value = ''
  errorMsg.value = ''
  pairCount.value = 0
})

2.3 效果截图

请求参数互转 效果截图

工具访问地址:https://www.i91tools.com/tools/param-convert