如何在R中生成可被标准gzip解压的Base64编码gzip压缩JSON字符串?
问题
我希望将R对象序列化为JSON供其他程序使用,并通过gzip压缩以实现高效传输,执行了以下代码:
data <- list(foo = "bar") readr::write_file(jsonlite::as_gzjson_b64(data), "foo")
(实际需求是将该Base64字符串作为日志消息的一部分输出,这里以文件操作为例。)
但解压操作失败:
❯ base64 -d < foo | gzip --decompress gzip: stdin: not in gzip format
我将问题简化为测试代码:
readr::write_file(memCompress("foobar", "gzip"), "foo.gz")
执行解压后得到乱码:
❯ gzip --decompress < foo.gz x�K��OJ�z ❯ gzip --decompress < foo.gz | hexyl ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ │00000000│ 78 9c 4b cb cf 4f 4a 2c ┊ 02 00 08 ab 02 7a │x×K××OJ,┊•⋄•וz │ └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
查阅memCompress文档发现:
R 4.4.0新增了对libdeflate库的支持。该库针对RFC 1950‘zlib’格式(以及用于解压的RFC 1952)使用不同的代码,预计比参考(或系统)zlib库快得多。若可用,会在type = "gzip"时使用。
尝试用libdeflate-gunzip替代gzip --decompress,输出完全相同。
请问这是什么原因?更重要的是,如何在R中生成正确的、可被标准解压的Base64编码gzip压缩JSON字符串?
版本信息:
- R 4.5.1
- jsonlite 2.0.0
- readr 2.1.5
- gzip 1.14-modified
原因分析
问题核心在于**memCompress(type = "gzip")实际生成的是zlib格式数据(RFC 1950),而非标准gzip格式(RFC 1952)**。从hexyl输出的78 9c可以验证这一点——这是zlib格式的默认头部,而标准gzip格式的头部是1f 8b。
即便文档提到libdeflate支持RFC 1952,但memCompress的type = "gzip"参数并未生成完整的gzip封装(缺少gzip的文件头、校验和等结构),本质还是zlib压缩流,因此无法被标准gzip命令识别解压。
解决方案
要生成标准gzip格式的压缩数据并转为Base64,有两种可靠方式:
方式1:结合系统gzip命令实现
先序列化JSON到临时文件,通过系统gzip命令生成标准gzip压缩文件,再读取二进制数据转为Base64:
library(jsonlite) library(readr) library(base64enc) data <- list(foo = "bar") # 序列化JSON到临时文件 tmp_json <- tempfile(fileext = ".json") write_file(toJSON(data, auto_unbox = TRUE), tmp_json) # 用系统gzip命令生成标准gzip压缩文件 tmp_gz <- paste0(tmp_json, ".gz") system2("gzip", c("-c", tmp_json), stdout = tmp_gz) # 读取压缩二进制数据并转为Base64 gz_bin <- read_file_raw(tmp_gz) base64_str <- base64encode(gz_bin) # 验证:写入文件后可正常解压 write_file(base64_str, "foo")
执行以下命令即可得到正常JSON:
base64 -d < foo | gzip --decompress
方式2:纯R环境实现(依赖openssl包)
利用openssl::zlib_compress的gzip = TRUE参数,直接生成符合RFC 1952的标准gzip格式数据:
library(jsonlite) library(openssl) library(base64enc) library(readr) data <- list(foo = "bar") # 序列化JSON为字符串 json_str <- toJSON(data, auto_unbox = TRUE) # 生成标准gzip压缩二进制数据 gz_bin <- zlib_compress(charToRaw(json_str), gzip = TRUE) # 转为Base64字符串 base64_str <- base64encode(gz_bin) # 验证 write_file(base64_str, "foo")
此方式无需依赖系统gzip命令,纯R环境即可完成。
关于jsonlite::as_gzjson_b64的说明
as_gzjson_b64内部调用了memCompress(type = "gzip"),因此同样存在生成zlib格式而非标准gzip格式的问题。若要使用jsonlite的封装,可以修改其内部逻辑,将memCompress替换为openssl::zlib_compress(gzip = TRUE)。
内容的提问来源于stack exchange,提问作者Raphael

