Superset 展示 JSONB 中文变天书?真相竟然是 Python 的“过度保护”

在 Superset 里给运营同学搭看板,想展示一下用户的对话日志。数据从 PostgreSQL 视图里一查,DBeaver 里看着一切正常。

结果一放到 Superset 图表上,傻眼了:

json 复制代码
[{"role": "user", "content": "\u4eca\u5929\u5929\u6c14"}]

好好的中文全变成了 \uXXXX 这种天书。

案发现场

背景是这样的:我用 PostgreSQL 的 FDW 做跨库查询,专门写了个视图 v_conversation_summary 投喂给 Superset。为了把多轮对话聚合在一行显示,我用了 jsonb_agg

sql 复制代码
-- 原始 SQL 片段
SELECT jsonb_agg(
    jsonb_build_object('role', tm.role, 'content', tm.content)
    ORDER BY tm.message_order
) AS 对话记录
...

逻辑没毛病,数据库里查出来的结果也是标准的 JSON 数组。怎么到了 Superset 前端就"乱码"了呢?

插播:为什么一开始要用 JSONB?

这里稍微解释下,为什么我习惯性地选了 JSONB 而不是普通的文本拼接。

PostgreSQL 的 JSONB (Binary JSON) 是个非常强大的特性。不同于普通的 JSON 类型(纯文本存储),JSONB 在存储时会把数据解析并转换成二进制格式

  • 存的时候:它会把键值对排序、去重、压缩。
  • 取的时候:不需要再次解析文本,直接就能拿来用,性能极高。
  • 最重要的是 :它支持 GIN 索引 。如果你想在几百万条数据里搜 {"role": "user"},用 JSONB 是毫秒级的,用文本存就是全表扫描。

因为习惯了它的高性能和规范性,我下意识地就用了 jsonb_agg。没想到,正是这个"二进制"的特性,配合上 Python 的严谨,搞出了这次的乱码乌龙。

破案过程

第一反应通常是:"是不是数据库编码不对?" 或者 "Superset 的编码设置有问题?"

但仔细一对比发现:

  1. DBeaver 查询:显示完美中文。
  2. Superset 展示:显示 Unicode 转义符。

这说明数据库里存的数据本身没问题。问题出在从数据库取出来,到发给浏览器的这段路上。

真凶:Python 的 json.dumps

Superset 后端是 Python 写的。当它从 PostgreSQL (通过 psycopg2 驱动) 拿到 JSONB 类型的数据时,Python 会把它转换成字典(Dict)。

关键一步来了:Superset 要把这个字典传给前端,需要把它序列化成字符串。这时候它(或者底层的库)大概率调用了 Python 标准库的 json.dumps

在 Python 里,json.dumps 有个默认行为:

python 复制代码
import json
data = {"msg": "你好"}

# 默认 ensure_ascii=True
print(json.dumps(data))
# 输出: {"msg": "\u4f60\u597d"}  <-- 也就是我们看到的"乱码"

这个 ensure_ascii=True 默认开启,原本是为了兼容性------确保不管对方系统多老旧,只要能认 ASCII 码就不会崩。但在我们这个场景下,它就成了"好心办坏事",把原本能正常显示的 UTF-8 中文强行转义了。

为什么 DBeaver 没事? 因为 DBeaver 是个成熟的客户端,它拿到数据后在本地做了解析和渲染,它"认识"这些 Unicode 字符,所以贴心地帮你转回了中文。而 Superset 把它当成了普通字符串直接吐了出来。

怎么解?

既然知道了是序列化的问题,改 Superset 源码去关掉 ensure_ascii 显然动静太大,容易"此时一时爽,升级火葬场"。

最优雅的办法,其实是在数据库层就把格式修好

既然我们的目标是"给人看"(运营看板),而不是给程序读,那为什么要执着于传 JSON 格式呢?直接拼成易读的文本不更香吗?

于是我把 jsonb_agg 换成了 STRING_AGG,顺手还加了点 Emoji 做美化:

sql 复制代码
(
    SELECT STRING_AGG(
        CASE
            WHEN tm.role = 'user' THEN '👤 ' || tm.content      -- 用户说话加个小人
            WHEN tm.role = 'assistant' THEN '🤖 ' || tm.content -- AI 回复加个机器人
            ELSE tm.role || ': ' || tm.content
        END,
        E'\n\n' -- 用换行符隔开,清晰明了
        ORDER BY tm.message_order
    )
    FROM task_messages tm
    WHERE tm.task_id = t.id
) AS 对话记录

这样改的两个红利:

  1. 彻底绕过序列化坑:输出变成了纯文本(Text),Superset 拿到什么就显示什么,Python 再也不会自作多情去转义它。
  2. 运营体验拉满 :去掉了冷冰冰的 {"role":...} 花括号和引号,直接显示对话流,运营同学看数据眼睛也不累了。

附:深度科普 PostgreSQL 的 JSONB

既然提到了 JSONB,顺便多聊两句。很多同学从 MySQL 转过来,可能觉得"不就是存 JSON 嘛,有啥区别?"

其实 PG 里的 jsonjsonb 完全是两个物种。

1. 它是"熟"的,不是"生"的

  • JSON 类型就像把肉冻在冰箱里,你存进去是什么样(包括空格、键的顺序、重复键),取出来就是什么样。它本质上就是个带校验的文本
  • JSONB 则是把肉做成了熟食。存进去的时候,数据库会把它解析、去重、排序 ,转换成一种高效的二进制格式
    • 优点:读的时候不需要重新解析,速度飞快;支持索引。
    • 缺点:写入时多了一步解析,稍微慢一丢丢;原本的空格和格式会丢失。

2. 杀手锏:GIN 索引

JSONB 最大的威力在于它支持 GIN (Generalized Inverted Index) 索引

假设你有一亿行日志,存了个 payload 字段:

sql 复制代码
-- 查所有"用户ID为10086"的操作记录
SELECT * FROM logs WHERE payload @> '{"user_id": 10086}';
  • 如果是普通 JSON:数据库得一行行把文本读出来,解析,再比对------这就是灾难。
  • 如果是 JSONB + 索引:它可以直接定位到包含这个键值对的行,速度和查主键差不多。

3. 避坑指南:到底选谁?

场景 推荐类型 理由
只是做日志归档 JSON 写入快,原样保留原始请求体(哪怕格式很丑)
Web端透传参数 JSON 既然后端不处理,就别费劲转二进制了
经常要根据字段查询 JSONB 必选,为了用索引
频繁更新局部字段 JSONB jsonb_set 修改性能更好
给 BI 工具做展示 Text 就像本文遇到的坑,直接用 SQL 拼成字符串最安全!

小结

遇到这种"数据库查着对,前端显示乱"的问题,尤其是涉及 JSON 的时候,多半是中间层的序列化配置在作妖。

虽然技术上我们可以通过配置解决编码问题,但转换思维往往能找到更好的路:既然是做展示,不如直接在 SQL 里把数据加工成最适合阅读的样子。既解决了乱码,又优化了排版,一举两得。


如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:

Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):

vibe coding 原理学习

全栈项目(适合学习现代技术栈):

  • prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
  • chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB
相关推荐
古城小栈2 小时前
Rust语言:优势解析与擅长领域深度探索
开发语言·后端·rust
小楼v2 小时前
构建高效AI工作流:Java生态的LangGraph4j框架详解
java·后端·工作流·langgraph4j
superman超哥2 小时前
Rust Cargo.toml 配置文件详解:项目管理的核心枢纽
开发语言·后端·rust·rust cargo.toml·cargo.toml配置文件
开心猴爷2 小时前
Python网络爬虫中HTTP和HTTPS代理的完整使用教程
后端
ID_180079054732 小时前
Python采集闲鱼商品详情API:JSON数据解析与应用实践
数据库·python·json
白帽子凯哥哥2 小时前
2026零基础如何参与护网行动?(非常详细)
数据库·sql·学习·漏洞·xss
我爱学习好爱好爱2 小时前
Prometheus监控栈 监控Springboot2+Vue3+redis项目
数据库·redis·prometheus
初次攀爬者2 小时前
知识库-向量化功能-EXCEL文件向量化
后端·elasticsearch
不思念一个荒废的名字2 小时前
【黑马JavaWeb+AI知识梳理】Web后端开发07 - Maven高级
后端·maven