1. 案发现场:价值十万的 [object Object]
那是一个宁静的周五下午,大家正准备划水下班。突然,监控群里警报狂响,核心业务列表页的 API 开始疯狂抛出 500 错误。
查日志只用了两分钟,但查出原因后大家都沉默了。
后端的报错是:SyntaxError: Unexpected token o in JSON at position 1。
再看前端发出的请求 URL,赫然写着:
https://api.example.com/v1/users?page=1&filter=[object Object]
罪魁祸首是一个刚入职的实习生。他在处理一个复杂的组合搜索表单时,为了图省事,直接写了这样的代码:
const filterObj = { status: 'active', roles: ['admin', 'user'] };
const url = `/v1/users?page=1&filter=${filterObj}`; // 灾难的开始
在 JavaScript 的隐式类型转换机制下,任何纯对象与字符串拼接,都会变成那个臭名昭著的 [object Object]。后端尝试将这串字符塞进 JSON.parse(),系统当场暴毙。
这件事促使我们在 daima.life 开发了“JSON/GET 参数互转”工具。今天,我们就来彻底把 URL 参数这个看似简单、实则暗礁密布的领域扒个底朝天。
2. 降维打击:为什么 GET 参数这么难搞?
把 JSON 转换成 GET 参数(Query String),本质上是一场降维打击。
JSON 是一种支持无限嵌套的树状结构,它拥有明确的数据类型(String, Number, Boolean, Null, Array, Object)。而 HTTP GET 的 Query String 是一个一维的、扁平的、纯文本的键值对列表(key=value&key2=value2)。
试图把高维数据硬塞进低维容器,必然面临信息丢失。这不仅是前端的问题,更是一个全栈的协议难题。
3. 技术解剖:三种主流的转换流派
当我们需要在 GET 请求中传递复杂数据时,业界通常有三种做法:
流派一:序列化 + URL 编码(最稳妥,但不可读)
逻辑:把整个 JSON stringify 成字符串,然后用 encodeURIComponent 编码。
const query = { status: 'active', age: 25 };
const url = `?data=${encodeURIComponent(JSON.stringify(query))}`;
// 结果: ?data=%7B%22status%22%3A%22active%22%2C%22age%22%3A25%7D
优点:完美保留了所有层级关系和数据类型(后端 JSON.parse 出来连数字和布尔值都不会变)。
踩坑点:绝对不能漏掉 encodeURIComponent! 如果你的 JSON 里包含 &、= 或者 +,不编码的话会直接破坏 URL 的结构,导致参数被截断。
流派二:原生 URLSearchParams(适合扁平数据)
这是现代浏览器提供的原生 API。
const params = new URLSearchParams();
params.append('status', 'active');
params.append('role', 'admin');
params.append('role', 'user'); // 处理数组的标准做法
// 结果: ?status=active&role=admin&role=user
踩坑点:它对深层嵌套对象无能为力。如果你 params.append('user', {name: 'Alice'}),恭喜你,又会得到一个 user=[object+Object]。
流派三:深度扁平化(以 qs 库为代表)
这是前端生态中最常见的做法。将嵌套结构通过中括号语法展平。
// JSON: { a: { b: 1, c: 2 }, d: [3, 4] }
// 转换后: a[b]=1&a[c]=2&d[0]=3&d[1]=4
踩坑点:这种语法并不是 HTTP 标准!它只是 PHP、Ruby on Rails 和 Express.js 等后端框架默契形成的一种潜规则。如果你对接的后端用的是 Go 或 Java 的某些原生库,它们可能根本不认识 a[b]=1 这种语法,只会把它当成一个完整的字符串键名。
4. 暗黑陷阱:你迟早会踩的坑
陷阱 A:布尔值与数字的类型丢失
在 Query String 中,一切皆字符串。?isVip=true&age=18,后端拿到的其实是 "true" 和 "18"。
如果后端写了严格的类型校验 if (isVip === true),这段代码永远不会执行。在转换工具中,我们通常需要提供“自动尝试类型推断”的选项。
陷阱 B:可怕的加号(+)
在 URL 标准中,空格可以被编码为 %20,在某些历史遗留的表单提交标准中,空格会被编码为 +。
如果你的密码或者搜索词里恰好包含真正的 + 号,而你没有使用严格的 encodeURIComponent,传到后端就会变成一个空格!这就是为什么有些用户的带加号密码永远登录失败的原因。
陷阱 C:414 URI Too Long
即便你把 JSON 转换得再完美,也要记住:URL 是有长度限制的。
虽然 HTTP 规范没有规定 URL 的最大长度,但所有的浏览器和 Web 服务器(Nginx, Apache)都有默认限制,通常在 2048 到 8192 个字符之间。一旦你把一个拥有几百个节点的巨大 JSON 拼进了 GET 参数,迎接你的就是一个冷冰冰的 414 Request-URI Too Long 报错。
解决方案:只要 JSON 超过 1KB,别犹豫,立刻把 GET 请求改成 POST,把数据塞进 Request Body 里。
5. 结尾
HTTP 协议看似简单,实则处处是草蛇灰线。一个小小的 [object Object],折射出的是数据在传输层降维时面临的种种妥协。
在 daima.life,我们在设计“JSON/GET 参数互转”工具时,特意做了一个功能:不仅仅是简单的格式化,还会高亮显示出那些可能导致类型丢失、未经过 URL 编码的危险字符,甚至提供 qs 模式和原生模式的切换。
不要去盲目相信框架的黑盒魔法。把那些底层的字符串拼接、转义规则弄清楚,才是资深工程师的修养。毕竟,你永远不知道下一个会把 toString() 塞进 URL 的同事,坐在你左边还是右边。