如何以最少字符数将JSON对象编码为URL搜索参数
问题描述
我开发了一款网页应用,支持用户调整参数创建、修改生成式艺术作品,完成后可以通过链接分享自己的创作(示例链接)。我的目标是把复现作品需要的所有数据都存在URL里,且URL总长度足够短,能满足推文280字符的长度限制。
朴素实现方案
最直接的实现思路是用JSON.stringify/JSON.parse处理配置数据,再把生成的字符串做base64编码。这个方案可以正常运行,示例如下:
https://generativestudios.app/stained-glass?version=1&artwork=eyJzZWVkIjoiMjg5NjI1NDIiLCJzcGxpdHRpbmdTdHJhdGVneSI6IlNwbGl0IFJhbmRvbSBCYWxhbmNlZCIsImRlcHRoU3RyYXRlZ3kiOnsia2luZCI6IkluaGVyaXRlZCBEZXB0aCIsImRlcHRoIjo0fSwiZGlzdFN0cmF0ZWd5Ijp7ImtpbmQiOiJYIENlbnRyb2lkIn0sImppdHRlciI6MCwicGFsZXR0ZSI6eyJyZWQiOnsiYSI6MC40LCJiIjowLjU3OTAyMTk5Nzg4MjMxMTUsImMiOjAuNCwiZCI6MH0sImdyZWVuIjp7ImEiOjAuMiwiYiI6MC42MTE4ODc4NDg2MTc1MDUyLCJjIjowLjgsImQiOjB9LCJibHVlIjp7ImEiOjAuNiwiYiI6MC45MzA0MzA2NDU0OTUzOTQ4LCJjIjoxLCJkIjowLjZ9LCJtb2RlIjoiU01PT1RIIn0sInN5bW1ldHJ5IjpmYWxzZX0%3D
但这个方案生成的URL总长度有560字符,达不到要求。
现有优化实现方案
首先梳理待编码数据的类型定义:
// 需要编码到URL中的配置结构 type Settings = { seed: string; splittingStrategy: SplittingStrategy; depthStrategy: DepthStrategy; distanceStrategy: DistanceStrategy; jitter: number; palette: Palette; symmetry: boolean; }; // 辅助类型定义 type SplittingStrategy = | "Split Random" | "Split Random Balanced" | "Split Middle"; type DepthStrategy = | { kind: "Max Depth"; maxDepth: number; } | { kind: "Flip Depth"; maxDepth: number; p: number; } | { kind: "Inherited Depth"; minDepth: number; }; type DistanceStrategy = | { kind: "XCentroid" } | { kind: "YCentroid" } | { kind: "DistanceToPoint"; x: number; y: number }; type Color = { a: Number; b: Number; c: Number; d: Number; }; type Palette = { red: Color; green: Color; blue: Color; mode: "MOD" | "SMOOTH"; };
目前已经落地的优化方向有四个:
- 将对象键名缩短为单字符
- 将字符串枚举值映射为单字符编码
- 小数按实际业务需求保留1-2位精度,砍掉多余的无效小数位
- 用CBOR替代JSON做序列化,减少格式冗余
经过以上优化后,编码部分的长度(不含http://...域名段)从498字符降到了170字符,已经满足当前需求:
# 朴素方案编码结果 eyJzZWVkIjoiMjg5NjI1NDIiLCJzcGxpdHRpbmdTdHJhdGVneSI6IlNwbGl0IFJhbmRvbSBCYWxhbmNlZCIsImRlcHRoU3RyYXRlZ3kiOnsia2luZCI6IkluaGVyaXRlZCBEZXB0aCIsImRlcHRoIjo0fSwiZGlzdFN0cmF0ZWd5Ijp7ImtpbmQiOiJYIENlbnRyb2lkIn0sImppdHRlciI6MCwicGFsZXR0ZSI6eyJyZWQiOnsiYSI6MC40LCJiIjowLjU3OTAyMTk5Nzg4MjMxMTUsImMiOjAuNCwiZCI6MH0sImdyZWVuIjp7ImEiOjAuMiwiYiI6MC42MTE4ODc4NDg2MTc1MDUyLCJjIjowLjgsImQiOjB9LCJibHVlIjp7ImEiOjAuNiwiYiI6MC45MzA0MzA2NDU0OTUzOTQ4LCJjIjoxLCJkIjowLjZ9LCJtb2RlIjoiU01PT1RIIn0sInN5bW1ldHJ5IjpmYWxzZX0%3D # 优化方案编码结果 p2EAomF2omEAYQBhAQNhaQJhAaJhdqFhAGEAYWkAYQL7QBwAAAAAAAFhA6RhAKRhAABhARgjYQIYZGEDGChhAaRhABRhARg%2BYQIYZGEDGFBhAmEBYQOkYQAYGWEBGD5hAhg8YQMYUGEEaDE2MTA1NjQ3YQVhAWEG9A%3D%3D
进阶优化方案解答
你现在已经把通用序列化层面的优化做的比较充分了,后续如果需要在URL里存更多数据,还有这些可落地的手段:
额外压缩技巧
- 位级硬编码:你的数据结构是完全固定的,根本不需要通用序列化附带的结构描述字段,可以直接给每个字段分配合适的比特位:比如3种分割策略只占2比特,对称性开关只占1比特,深度值如果上限不超过16只占4比特,0-1范围的小数如果保留2位精度,映射到0-99的整数只占7比特,所有字段按位拼接几乎零冗余,压缩率比CBOR这类通用格式高一大截。
- 叠加通用压缩:二进制序列化完成后,可以直接用浏览器原生支持的DEFLATE算法再压一遍(对应
CompressionStream/DecompressionStreamAPI,不需要引入第三方库),结构化数据通常能再缩短30%-50%。 - 差分存储预设:如果分割策略、调色板这类选项有内置默认值或者官方预设,URL里不用存全量参数,只存和预设不一样的字段即可,能省掉很多固定值占用的空间。
- 替换编码格式:普通base64里的
+、/、=三个字符在URL里会被百分号编码,一个字符占3个位置,换成base64url编码(用-替换+,_替换/,去掉末尾补位的=),能直接消掉这部分无效长度。
替代解决思路
如果后续数据量涨到实在塞不下280字符限制,也可以换方案绕开URL存数据的限制:
- 短链方案:把完整作品配置存在服务端数据库,URL里只放一个短ID,用户访问时服务端拉取对应配置渲染即可,完全不受长度限制,缺点是需要后端支持,链接的可用性依赖服务存续。
- 本地存储方案:如果不需要跨设备分享,也可以把配置存在用户浏览器的本地存储里,URL只放配置的哈希索引,本地打开直接读缓存就行,缺点是换设备/清缓存就会失效,适合临时保存场景。
base64编码选型结论
普通base64不是URL场景下的最优选择:
- 普通base64的编码效率是3字节转4字符,空间利用率75%,而且存在需要URL转义的特殊字符,会额外增加长度。
- 改造成本最低的替换项是base64url,和base64编解码逻辑几乎一致,没有需要转义的特殊字符,利用率同样75%,浏览器原生API就能支持,适配成本极低。
- 追求更高空间利用率可以选自定义URL安全字符集的base85,编码效率是4字节转5字符,空间利用率80%,比base64多存5%左右的数据,只要避开URL保留字符设计字符集,实际表现会优于base64。
- 不建议尝试base91、base122这类理论利用率更高的编码,其中包含大量URL保留字符,会被强制百分号编码,实际长度反而更长。
内容的提问来源于stack exchange,提问作者Frank Weslien
相关产品推荐
相关产品推荐

