JSON数据转换为URL编码的原理与实现

11 次阅读

JSON和URL编码解决的是两个不同层面的问题:JSON负责结构化描述数据,URL编码负责让数据安全地出现在URL中。实际开发中,经常需要把JSON对象作为查询参数、接口参数或跳转地址的一部分传递,这时不能简单地将JSON字符串直接拼接到URL中,而应该先完成JSON序列化,再进行URL编码。

JSON数据为什么需要进行URL编码

JSON通常包含大括号、双引号、冒号、逗号等特殊字符,例如:

JSON
{"name":"张三","age":18}

如果直接拼接到URL中:

https://example.com/api?data={"name":"张三","age":18}

虽然部分浏览器或服务端可能能够正常处理,但这种方式存在明显风险。

URL本身具有特定的语法结构,例如?用于开始查询参数,&用于分隔参数,=用于连接参数名和参数值。如果JSON内容中恰好包含这些特殊字符,就可能导致服务端错误解析。

例如:

JSON
{"name":"Tom&Jerry","age":18}

直接放进URL:

https://example.com/api?data={"name":"Tom&Jerry","age":18}

其中的&可能被解析为新的查询参数分隔符,从而破坏原有数据结构。

URL编码的作用,就是将这些可能产生歧义的字符转换成安全的百分号编码形式。

例如:

{"name":"Tom&Jerry","age":18}

编码后类似于:

%7B%22name%22%3A%22Tom%26Jerry%22%2C%22age%22%3A18%7D

这样数据就可以作为URL参数安全传输。

JSON转换为URL编码的基本原理

完整过程实际上可以拆分为两个步骤:

JSON对象
   ↓
JSON序列化
   ↓
JSON字符串
   ↓
URL编码
   ↓
URL安全字符串

例如JavaScript中:

JavaScript
const data = {
    name: "张三",
    age: 18
};

const json = JSON.stringify(data);
const encoded = encodeURIComponent(json);

console.log(encoded);

JSON.stringify()负责把JavaScript对象转换成JSON字符串,而encodeURIComponent()负责对字符串进行URL编码。

最终可以得到类似:

%7B%22name%22%3A%22%E5%BC%A0%E4%B8%89%22%2C%22age%22%3A18%7D

如果需要将它放入完整URL,可以这样处理:

JavaScript
const url = "/api/user?data=" + encodeURIComponent(JSON.stringify(data));

console.log(url);

这种写法更加安全,也更符合URL参数的处理方式。

URL编码到底转换了什么

URL编码通常采用百分号编码,也就是将特定字符转换成%加两个十六进制字符。

例如:

{  → %7B
}  → %7D
"  → %22
:  → %3A
,  → %2C
&  → %26

中文等非ASCII字符则通常按照UTF-8编码后,再转换成百分号形式。

例如:

张

UTF-8对应的字节经过URL编码后会表现为:

%E5%BC%A0

因此,JSON中的中文内容经过URL编码后,长度通常会明显增加。

需要注意的是,URL编码并不是加密。即使JSON被转换成了:

%7B%22username%22%3A%22admin%22%7D

任何人都可以通过URL解码还原出原始内容。因此,不能把URL编码当作数据加密或安全保护手段。

JavaScript实现JSON转URL编码

JavaScript是Web开发中处理JSON和URL编码最常见的语言之一。

使用encodeURIComponent

推荐的方式是:

JavaScript
const user = {
    id: 1001,
    username: "张三",
    role: "admin"
};

const jsonString = JSON.stringify(user);
const encodedData = encodeURIComponent(jsonString);

console.log(encodedData);

如果需要反向恢复:

JavaScript
const jsonString = decodeURIComponent(encodedData);
const user = JSON.parse(jsonString);

console.log(user);

整个转换过程具有对称关系:

JSON.stringify()
        ↓
encodeURIComponent()
        ↓
URL传输
        ↓
decodeURIComponent()
        ↓
JSON.parse()

这种方式特别适合前端页面跳转、查询参数以及简单的GET接口调用。

encodeURI和encodeURIComponent有什么区别

实际开发中经常有人把encodeURI()和encodeURIComponent()混在一起使用,两者用途并不相同。

encodeURI()主要用于编码完整URI,但会保留URL结构所需要的一些字符,例如:

:
/
?
&
=

而encodeURIComponent()用于编码URL中的某一个组件,也就是参数值,因此会编码更多特殊字符。

假设JSON作为data参数:

JavaScript
const json = JSON.stringify({
    name: "Tom&Jerry",
    age: 18
});

const url = "/api?data=" + encodeURIComponent(json);

这里应该优先使用encodeURIComponent()。

原因很简单:JSON是参数值,而不是完整URL。

可以记住一个实用规则:

编码完整URL → encodeURI()
编码URL参数值 → encodeURIComponent()

Java实现JSON转URL编码

Java项目中通常会先使用JSON库完成序列化,再通过URL编码工具处理字符串。

以常见的Jackson为例:

Java
ObjectMapper mapper = new ObjectMapper();

Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 18);

String json = mapper.writeValueAsString(data);
String encoded = URLEncoder.encode(json, StandardCharsets.UTF_8);

System.out.println(encoded);

如果项目使用Gson,也可以采用类似思路:

Java
Gson gson = new Gson();

Map<String, Object> data = new HashMap<>();
data.put("name", "张三");
data.put("age", 18);

String json = gson.toJson(data);
String encoded = URLEncoder.encode(json, StandardCharsets.UTF_8);

需要特别注意,Java中的URLEncoder主要针对application/x-www-form-urlencoded场景。它与JavaScript的encodeURIComponent()并不是完全相同的实现语义。

例如空格在表单编码中通常会转换成:

+

而标准百分号编码中常见的形式是:

%20

因此,如果前后端需要严格匹配编码规则,就应该提前确定接口所采用的编码规范。

Python实现JSON转URL编码

Python可以直接使用标准库完成JSON序列化和URL编码。

Python
运行
import json
from urllib.parse import quote

data = {
    "name": "张三",
    "age": 18
}

json_string = json.dumps(data, ensure_ascii=False)
encoded = quote(json_string)

print(encoded)

如果需要还原:

Python
运行
from urllib.parse import unquote
import json

json_string = unquote(encoded)
data = json.loads(json_string)

print(data)

这里的处理顺序同样是:

dict
 ↓
json.dumps()
 ↓
JSON字符串
 ↓
quote()
 ↓
URL编码

PHP实现JSON转URL编码

PHP中可以使用json_encode()生成JSON,再通过urlencode()或者rawurlencode()进行编码。

PHP
$data = [
    "name" => "张三",
    "age" => 18
];

$json = json_encode($data, JSON_UNESCAPED_UNICODE);
$encoded = rawurlencode($json);

echo $encoded;

通常情况下,如果目标是RFC 3986风格的百分号编码,可以优先考虑rawurlencode()。

反向转换:

PHP
$json = rawurldecode($encoded);
$data = json_decode($json, true);

URL参数中传递JSON的完整示例

假设有一个搜索接口,需要将多个筛选条件封装成JSON:

JSON
{
    "keyword": "数据库",
    "page": 1,
    "pageSize": 20,
    "sort": "desc"
}

前端可以这样构造URL:

JavaScript
const filter = {
    keyword: "数据库",
    page: 1,
    pageSize: 20,
    sort: "desc"
};

const params = encodeURIComponent(JSON.stringify(filter));

const url = `/search?filter=${params}`;

console.log(url);

服务端收到filter后,首先进行URL解码,然后解析JSON:

URL参数
  ↓
URL Decode
  ↓
JSON字符串
  ↓
JSON Parse
  ↓
业务对象

这也是JSON作为URL参数最典型的应用模式。

不要重复编码

JSON URL编码中另一个非常常见的问题是重复编码。

例如第一次:

JavaScript
const encoded = encodeURIComponent(json);

已经完成编码。

如果后续又执行:

JavaScript
const encodedAgain = encodeURIComponent(encoded);

就会产生二次编码。

例如原本的:

%7B

再次编码后可能变成:

%257B

因为%本身又被编码成了%25。

最终服务端如果只执行一次解码,得到的仍然不是原始JSON。

因此,应尽量保证:

JSON → 编码一次 → 传输 → 解码一次 → JSON

而不是:

JSON → 编码 → 再编码 → 传输

JSON URL编码后的数据如何解码

编码和解码必须保持对应关系。

JavaScript:

JavaScript
const encoded = encodeURIComponent(JSON.stringify(data));

const json = decodeURIComponent(encoded);
const result = JSON.parse(json);

Python:

Python
运行
decoded = unquote(encoded)
data = json.loads(decoded)

Java:

Java
String json = URLDecoder.decode(encoded, StandardCharsets.UTF_8);

解码后得到JSON字符串,还需要继续执行JSON解析,才能恢复为对象。

因此:

URL解码 ≠ JSON解析

这是理解整个过程非常重要的一点。

URL解码只能把:

%7B%22name%22%3A%22%E5%BC%A0%E4%B8%89%22%7D

恢复成:

JSON
{"name":"张三"}

而JSON.parse()、Jackson或Gson等工具才负责将JSON字符串转换成程序中的对象。

JSON放在URL中需要注意长度问题

虽然将JSON编码后放入URL非常方便,但并不意味着所有JSON数据都适合放到URL中。

例如:

JSON
{
    "keyword": "产品搜索",
    "filters": [
        "手机",
        "电脑",
        "平板"
    ],
    "attributes": {
        "brand": "某品牌",
        "priceMin": 1000,
        "priceMax": 5000
    }
}

数据结构越复杂,编码后的URL越长。

过长的URL可能受到浏览器、Web服务器、代理服务器或网关的长度限制。因此,对于大量数据,不建议继续通过GET URL传输。

更合适的方式通常是使用POST请求,将JSON放在请求体中:

http
POST /api/search
Content-Type: application/json

请求体:

JSON
{
    "keyword": "数据库",
    "page": 1,
    "pageSize": 20
}

这样可以避免URL长度过长,同时也更适合复杂业务数据。

JSON编码和Base64不要混为一谈

开发中还有一种常见方案是:

JSON → Base64 → URL编码

例如:

JavaScript
const json = JSON.stringify(data);
const base64 = btoa(json);
const encoded = encodeURIComponent(base64);

Base64和URL编码解决的问题不同。

JSON序列化解决的是对象到字符串的问题;Base64解决的是二进制或文本安全表示问题;URL编码解决的是URL特殊字符冲突问题。

它们之间没有谁一定能够替代谁。

如果JSON本身已经经过encodeURIComponent()处理,通常没有必要为了“安全”再额外做Base64。是否使用Base64,应根据接口协议、数据格式和兼容性要求决定。

JSON转换为URL编码的常见错误

直接拼接JSON

错误示例:

JavaScript
const url = "/api?data=" + JSON.stringify(data);

JSON中的特殊字符可能影响URL解析。

更推荐:

JavaScript
const url = "/api?data=" + encodeURIComponent(JSON.stringify(data));

先URL编码再JSON序列化

不要这样:

JavaScript
JSON.stringify(encodeURIComponent(data));

因为URL编码应该作用于最终需要传输的JSON字符串,而不是直接作用于JavaScript对象。

正确顺序是:

JavaScript
encodeURIComponent(JSON.stringify(data));

编码后再次编码

避免:

JavaScript
encodeURIComponent(encodeURIComponent(json));

除非接口协议明确要求进行多次编码,否则这很容易造成服务端解码异常。

忽略字符集

中文、日文、韩文以及其他非ASCII字符必须正确按照UTF-8进行处理。

前后端如果采用不同字符集,可能出现乱码或JSON解析失败。

如何选择合适的传输方式

可以根据数据特点进行判断:

场景推荐方式
少量简单参数普通URL查询参数
少量结构化参数JSON序列化后URL编码
页面跳转携带少量状态URL参数
复杂筛选条件POST + JSON
大量结构化数据POST请求体
需要隐藏敏感信息不应依赖URL编码,应考虑HTTPS和合理的安全方案
需要短链接采用服务端ID映射,而不是直接塞入完整JSON

尤其需要注意,URL中的参数通常会出现在浏览器历史记录、服务器访问日志、代理日志等位置。因此,密码、访问令牌、身份证号码等敏感信息不应该仅仅通过URL编码后放入查询参数。

实际开发中的推荐写法

如果使用JavaScript,最直接、清晰的方式是:

JavaScript
const data = {
    userId: 1001,
    keyword: "数据库",
    page: 1
};

const query = encodeURIComponent(JSON.stringify(data));

const url = `/api/search?data=${query}`;

如果项目使用URLSearchParams,还可以进一步提高可读性:

JavaScript
const data = {
    userId: 1001,
    keyword: "数据库",
    page: 1
};

const params = new URLSearchParams({
    data: JSON.stringify(data)
});

const url = `/api/search?${params.toString()}`;

这种方式能够让参数构建更加规范,也减少手动处理?、&和=等URL结构字符的麻烦。

总结

JSON数据转换为URL编码,本质上是两个连续的数据处理过程:先通过JSON序列化将对象转换成字符串,再对字符串进行URL编码,使其中的特殊字符能够安全地作为URL参数传输。

最核心的处理链路可以概括为:

对象
 ↓
JSON序列化
 ↓
JSON字符串
 ↓
URL编码
 ↓
URL参数

恢复时则反过来:

URL参数
 ↓
URL解码
 ↓
JSON字符串
 ↓
JSON解析
 ↓
对象

开发过程中应重点关注编码方式、UTF-8字符集、重复编码、URL长度以及敏感数据泄露等问题。对于少量结构化参数,JSON配合URL编码非常实用;面对复杂或大体积数据,则更适合使用POST请求体传递JSON。理解这两种数据格式各自承担的职责,才能避免把JSON序列化、URL编码、Base64编码和数据加密混为一谈。