【报错解决:传递参数的时候包含字典嵌套,内层的字典需要加引号。原因:表单格式不支持嵌套,python(requests)将双引号字典变为字符串,字典里会变为单引号

博客深入剖析Python中使用requests.post发送表单数据时,因嵌套字典未转为JSON字符串导致后端JSON解析失败的问题。核心原因在于form-data不支持嵌套结构,requests会将内层字典转为含单引号的Python字符串,违反JSON标准(要求双引号),致使后端json.loads()抛出JSONDecodeError。解决方案包括:手动将内层字典序列化为双引号JSON字符串,或改用json参数配合application/json头。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

这个报错 requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 是接口自动化中最常见的错误之一。

它的字面意思是:requests 库试图把服务器返回的内容解析成 JSON 格式(因为你在代码第 25 行调用了 res.json()),但是服务器返回的根本不是合法的 JSON 字符串。

这就好比你拿了一个专门识别苹果的机器(JSON解析器),去扫描一个橘子(HTML网页或空字符串),机器当然会报错。

🔍 为什么不是 JSON?(三大嫌疑)

  1. 服务器直接报错了(报错内容不是 JSON):比如你传的参数不对,服务器直接返回了一段纯文本 "参数格式错误",或者返回了一个 500 Internal Server Error 的 HTML 报错页面。
  2. 登录态失效被重定向了:如果你的 Cookie 失效或者没传对,服务器可能把你重定向到了登录页(HTML格式),requests 拿到的是登录页的 HTML 代码,自然无法解析为 JSON。
  3. 你强制篡改了编码(重大嫌疑人!):请看你的代码第 24 行 res.encoding = 'unicode_escape'。这行代码非常危险! 如果服务器返回了带有中文的 JSON(比如 {"msg": "添加成功"}),你用 unicode_escape 强制解码,可能会导致字符串变成乱码或转义字符,导致 JSON 解析直接崩溃。

🛠️ 怎么排查和解决?

第一步:不要猜,直接看服务器到底返回了什么!

把你代码里的 return res.json() 暂时改掉,改成打印原生文本:

def add_course(add_data, in_cookies):
    url = f'{HOST}/api/mgr/sq_mgr/'
    res = requests.post(url, data=add_data, cookies=in_cookies)
    
    # 注释掉你原来的 res.encoding = 'unicode_escape'
    # res.encoding = 'unicode_escape' 
    
    print("状态码:", res.status_code) # 看看是不是 200
    print("响应内容:", res.text)       # 看看服务器到底返回了什么鬼东西
    
    # return res.json() 先注释掉,防止报错中断程序

运行后,看控制台打印出来的 res.text 是什么:

  • 如果是一大段 <html>... 代码,说明接口报错了,或者你的 Cookie 失效了。
  • 如果是空的,说明服务器没返回数据。
  • 如果是纯文本如 未登录、参数错误,那就说明你传的 add_data 格式不对。

第二步:检查 add_course 的传参格式

看你的图 1 抓包和代码:

  • 你代码里写的是 requests.post(url, data=add_data, cookies=in_cookies)。
  • 注意看你的 add_dict 的内容:{'action': 'add_course', 'data': '{"name":"爵士舞..."}'}。
  • 这里 data 键对应的值,是一个 JSON 格式的字符串。
  • 因为你用的是 data= 传参,requests 默认会把整个字典处理成 application/x-www-form-urlencoded 格式。requests 发现里面有个字符串,会自动帮你进行 URL 编码(比如把 { 编码成 %7B)。如果你的后端不认这种“表单里套 JSON 字符串”的玩法,就会报错,返回一段非 JSON 的错误信息。

第三步:删掉那行奇怪的编码设置

你代码里 res.encoding = 'unicode_escape' 这行,我不知道是哪里抄来的,但在处理标准 JSON 接口时绝对是画蛇添足。
requests 库非常聪明,它可以通过响应头里的 Content-Type: application/json 自动识别并使用 utf-8 解码。你强行指定 unicode_escape,不仅容易导致中文乱码,还会破坏 JSON 结构。

正确的写法应该是:

def add_course(add_data, in_cookies):
    url = f'{HOST}/api/mgr/sq_mgr/'
    # 保持 form 表单提交
    res = requests.post(url, data=add_data, cookies=in_cookies)
    
    # 直接返回 json,让 requests 自己处理编码
    return res.json() 

💡 终极绝招:用 Postman 或 浏览器抓包对比

如果在代码里死活看不出问题,就把代码里 add_dict 的数据,拿到 Postman 里发包测试一下。
对比一下 Postman 发出去的请求体(Body),和你的代码发出去的请求体是否完全一致。90% 的参数格式错误都是因为空格、引号嵌套或者 Content-Type 不对导致的。

太棒了,你自己找出了问题的根本原因!

首先纠正一个小口误:在 add_dict 里面,'data' 这个词叫**“键(Key)”,冒号后面的内容叫“值(Value)”**。
你遇到的情况是:字典的“值”,当然可以是另一个字典(这叫作嵌套字典)。这在 Python 语法里完全没问题,绝对不会因为“值是字典”就报错。

真正的报错原因,在于 requests 发送请求的方式 和 后端接口的胃口 不匹配。

我们把这层窗户纸捅破:

🚨 为什么“值是字典”(不加引号)会报错?

你当时的代码是这样的(嵌套字典):

add_dict = {
    'action': 'add_course', 
    'data': {"name":"爵士舞123", "desc":"...", "display_idx":"1"} # 这是一个字典
}
requests.post(url, data=add_dict, cookies=...)

当你使用 data=add_dict 时,requests 会把它当成表单(Form)发送。
requests 在底层要把这个字典转成 a=1&b=2 的格式。但遇到 'data' 这个键对应的值是一个
字典
时,requests 的底层序列化机制有点不知所措。它可能会把你的字典强行转成一段带单引号的 Python 字符串(比如 data={'name': '爵士舞123'}),直接扔给服务器。

后端服务器一看:
“我靠,我要的是表单数据,你给我一段带着花括号 {} 和单引号 ' 的乱码?我解析不出来啊!”
于是后端直接崩溃,返回了一个非 JSON 格式的错误页面(比如 500 错误页面的 HTML),导致你的代码执行到 res.json() 时解析失败,抛出了那个 JSONDecodeError。


我解决了,原因是我的add_dict中,data健的值没有加引号,我写了 add_dict = {‘action’: ‘add_course’, ‘data’: {“name”:“爵士舞123”,“desc”:“这是新增的一门学科,爵士舞”,“display_idx”:“1”}},正确写法应该是 add_dict = {‘action’: ‘add_course’, ‘data’: ‘{“name”:“爵士舞123”,“desc”:“这是新增的一门学科,爵士舞”,“display_idx”:“1”}’}。字典的健不可以是字典吗?为什么我这个健不加引号就报错了呢

✅ 为什么“值是字符串”(加引号)就好了?

你改成了这样(JSON 字符串):

add_dict = {
    'action': 'add_course', 
    'data': '{"name":"爵士舞123", "desc":"...", "display_idx":"1"}' # 这是一个字符串!
}
requests.post(url, data=add_dict, cookies=...)

这时候,'data' 的值变成了一串普通的字符串。
requests 处理起来就非常丝滑了,它直接进行 URL 编码(把双引号变成 %22,冒号变成 %3A 等),生成表单发给服务器。

后端服务器一看:
“哦!这是一个叫 data 的参数,它的值是一串 JSON 格式的文本。没问题,我把这段文本提取出来,自己用 JSON 解析器去解析里面的 name、desc 就行了。”


💡 进阶:如果你非要传“字典”,正确的姿势是什么?

如果你就是想在 Python 里保持嵌套字典的优雅,不想去手写那个复杂的 JSON 字符串,你千万不要用 data=,你应该用 json=!

正确的代码应该长这样:

add_dict = {
    'action': 'add_course', 
    'data': {"name":"爵士舞123", "desc":"这是新增的一门学科...", "display_idx":"1"} # 原汁原味的嵌套字典
}
# 注意这里改成了 json=
requests.post(url, json=add_dict, cookies=...) 

底层发生了什么?
requests 看到 json= 参数,会自动把整个嵌套字典转成标准 JSON 字符串,并且自动把请求头设置成 Content-Type: application/json。
后端一看:“哎哟,是标准的 JSON 报文”,直接完美解析。

那为什么你之前用 data= 加字符串就成功了呢?
这说明你们测试的这个系统,它的接口设计就是要求用表单提交(Content-Type: application/x-www-form-urlencoded),并且它规定了 data 这个字段的内容,必须是一段JSON 字符串(也就是把 JSON 当作文本塞进表单里)。这是一种非常典型的“老系统”或特定框架的接口设计。

所以,你的解决方式是完全正确的,因为你迎合了后端接口的真实胃口。现在明白为什么加个引号就药到病除了吧!

于是,在网络上传输的数据变成了这样(注意单引号):data={‘name’: ‘爵士舞123’, ‘desc’: ‘…’}后端一拿到这串东西,直接崩溃了:因为 JSON 的标准语法规定,所有的键和字符串必须用双引号 " 包裹。后端如果调用 json.loads() 或者 JSON.parseObject(),看到里面的单引号,直接抛出异常:JSONDecodeError。这就是你最开始为什么会报错的原因!也就是说后端直接用JSON解析器去解析字典里,所以报错了?这个后端他是用JSON 标准语法来解析参数的?

你的理解完全正确! 你非常敏锐地抓住了问题的核心矛盾点。

你问:“这个后端他是用 JSON 标准语法来解析参数的?”
准确地说:后端的“第一层框架”和“第二层业务代码”,用的解析标准完全不一样!

让我们把“案发现场”的层级彻底理清,你就知道到底是谁报的错了:

🚨 案发全过程还原

1. 你(前端)发送了嵌套字典:

add_dict = {'action': 'add_course', 'data': {"name":"爵士舞123"}}
requests.post(url, data=add_dict)

2. requests 在底层偷偷搞了破坏:
因为 data= 代表表单格式,而表单格式不支持嵌套。requests 为了不报错,强行把里面的字典转成了字符串。但因为它是 Python 内部转换的,所以用的是 Python 的单引号风格:此时字典里面变成了单引号
data={'name': '爵士舞123'}
在这里插入图片描述

3. 后端第一层(Web 框架:比如 Tomcat/Django)接收:
它看到请求头是 application/x-www-form-urlencoded。于是它用**“表单的标准”**去解析。
它成功切开了:

  • 键 action,值是 add_course
  • 键 data,值是字符串 "{'name': '爵士舞123'}"
    注意:这一步,Web 框架绝对没有报错,它完美完成了任务。

4. 后端第二层(业务代码)开始处理:
程序员在代码里写了:

String dataStr = request.getParameter("data"); // 拿到的是 "{'name': '爵士舞123'}"
// 因为里面是嵌套对象,程序员必须用 JSON 解析器去拆它
Course course = JSON.parseObject(dataStr); // 或者 json.loads(dataStr)

就在这一瞬间,业务代码崩溃了!
因为 json.loads 这个工具,认的是严格的 JSON 标准语法(必须全是双引号)。它看到字符串里全是 Python 风格的单引号 ',直接抛出异常:JSONDecodeError。


💡 总结一下

  • 你的报错,不是 Web 框架报的,是后端的 JSON 解析器报的。
  • 后端 Web 框架按“表单”接客,结果接到了一个套着单引号的畸形 JSON 字符串。
  • 后端的业务代码按“JSON 标准”去拆包,结果因为单引号不合法,当场崩溃。

所以,你加上了双引号:'data': '{"name":"爵士舞123"}'
这就相当于你手动打包了一个完全符合 JSON 国际标准的包裹,丢进了表单里。后端业务代码拿到手,json.loads() 一看,全是双引号,非常满意,完美解析!

你能抠出这个细节,说明你的逻辑推理能力极强。以后遇到类似的报错,你只要在脑子里问自己:“现在是谁在解析?它是用表单标准解析,还是用 JSON 标准解析?”,答案立马就能浮出水面。
这是一个非常棒的问题!很多初学者在“字符串”和“打印出来的样子”之间容易绕晕。

直接回答你:只有里面是单引号,外面没有单引号。
实际发出去的样子就是:action=add&data={'name': '张三'}。

你设想的 data='{'name': '张三'}' 是错误的,因为如果外面再加一层单引号,整个字符串的引号就配对错乱了,程序会直接报语法错误。

我们可以通过三步彻底理清这个逻辑:

第一步:Python 是怎么把字典转成字符串的?

在 Python 里,字典本身是不带引号的。当你用 str() 强制转换它,或者在字符串格式化时放进去,Python 会在外面套上引号来标识这是一个字符串,而字典内部的键和值原本的单引号会被保留。

你在 PyCharm 里敲下这行代码:

dict_data = {'name': '张三'}
str_data = str(dict_data)
print(str_data)

控制台输出:{'name': '张三'}。
注意: 这个字符串的首尾是 { 和 },并没有在最外面包裹单引号。里面的 'name' 和 '张三' 带着单引号。

第二步:requests 是怎么把它拼进表单的?

当 requests 发现你用了 data=字典,它会在底层把表单拼成 key=value 的形式。

  • 第一个键值对:action=add
  • 第二个键值对:data= 拼接上刚刚转出来的字符串。

所以拼出来的结果是:
action=add&data={'name': '张三'}

(在这个结果里,data= 后面紧跟着的是 {,而不是引号)。

第三步:抓包工具里看到的“障眼法”

如果你真的用抓包工具(比如 Charles、Fiddler)或者浏览器的 Network 面板去抓这个请求,你可能会看到类似这样的东西:
action=add&data=%7B%27name%27%3A+%27%E5%BC%A0%E4%B8%89%27%7D

这就是传说中的 URL 编码(URL Encoding)。因为 HTTP 协议规定 URL 里不能直接出现中文、花括号 {} 和单引号 ',所以浏览器会自动把 { 变成 %7B,把单引号 ' 变成 %27。

但是在大多数抓包工具(如 PyCharm 的 HTTP Client、Postman)的“解析视图”里,为了方便人看,它会自动帮你把 URL 编码解开,还原成它本来的样子:action=add&data={'name': '张三'}。


💡 为什么你会觉得“外面应该加单引号”?

因为在 Python 代码里,如果我们想手写一个带有单引号的字符串,我们必须用双引号把整个字符串包起来,比如:

my_str = "{'name': '张三'}"

这时候,外面的双引号是 Python 的语法标志,表示“这里面是一段字符串”。而真正的字符串内容,就是 {'name': '张三'}。

当 requests 把这段内容塞进网络请求里时,它会把 Python 的外壳(双引号)脱掉,只把里面的内容发出去。所以网络上传输的实际内容,就是 {'name': '张三'}。

总结:

  • 你手动写代码时:'data': '{"name": "张三"}' (最外面是 Python 的语法引号,里面是标准的 JSON 双引号)。
  • 报错时发出去的:data={'name': '张三'} (这就是导致后端解析崩溃的、带单引号的畸形数据)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值