Appearance
实现 请求参数互转 工具
分类: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-input、el-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 效果截图
