在浏览器中使用 gzip 和 Compression Streams API 进行 JSON 压缩。
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
编辑:
只是 Firefox 还不支持。
介绍
在构建 Web 应用程序时,我们有时需要持久化大型 JSON 对象。这可以通过Web Storage直接保存到浏览器,也可以通过File或Fetch API 从外部保存。
这个问题让我不禁思考:存储原始JSON还是编码后的JSON,有没有更好的解决方案?我们能否实现一定程度的压缩?
事实证明,答案是肯定的,而且使用压缩流 API实现起来出奇地简单。
此示例是为满足Web Storage、File和Solid Pod持久化的特定需求而创建的,用于扩展 JSON 应用程序状态的表示。
背景
简单介绍一下背景,我目前负责的项目不与服务器通信。应用程序数据以事务方式组织在默克尔树(类似 Git 或区块链)中。每次与应用程序交互时,树状结构都会自然增长,内存和存储空间占用也会随之增加。
为了密切关注数据增长情况,我在控制栏中添加了一个小指示器来可视化数据大小。我发现随着 JSON 对象不断增长,数据量开始达到一个让我不太放心的程度,所以我决定研究一下,看看能否找到更好的解决方案。
这让我接触到了压缩流 API。这是一个浏览器 API,可以直接在浏览器中gzip对流媒体进行压缩。ReadableStream
TL;DRgzip启用JSON 数据压缩后,我发现数据大小减少了 98%,从943kb减少到10kb。
这并非科学测试,数据也无法像在应用程序中自然扩展那样进行扩展,但它可以作为一个公平的基准,表明我可以通过此 API 获得我想要的结果。
解决方案
由于压缩流 API是流 API的一部分,我们首先需要将 JSON 数据对象转换为ReadableStream。
可以从文本构建 Blob,也可以将 JSON 序列化为文本,因此我们可以从 JSON 创建一个Blob ,并从中获取我们的流。
// Convert JSON to Stream
const stream = new Blob([JSON.stringify(data)], {
type: 'application/json',
}).stream();
现在我们有了数据ReadableStream,我们可以用这个ReadableStream.pipeThrough方法将数据通过gzip CompressionStream转换进行传输。
// gzip stream
const compressedReadableStream = stream.pipeThrough(
new CompressionStream("gzip")
);
然后,为了将此压缩流返回到我们的主代码,我们可以创建一个新的响应,并像处理从Fetch API收到的任何其他数据一样处理数据。
// create Response
const compressedResponse =
await new Response(compressedReadableStream);
现在数据已经压缩完毕,我们该如何处理它呢?
实际上,相当多。
我们可以将其进行 base64 编码,然后存储起来localStorage。
// Get response Blob
const blob = await compressedResponsed.blob();
// Get the ArrayBuffer
const buffer = await blob.arrayBuffer();
// convert ArrayBuffer to base64 encoded string
const compressedBase64 = btoa(
String.fromCharCode(
...new Uint8Array(buffer)
)
);
// Set in localStorage
localStorage.setItem('compressedData', compressedBase64);
我们可以将其保存为文件。
// Get response Blob
const blob = await compressedResponse.blob();
// Create a programmatic download link
const elem = window.document.createElement("a");
elem.href = window.URL.createObjectURL(blob);
elem.download = '';
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
或者我们可以采用更传统的方法,通过 HTTPS 和Fetch API发送。
// Get response Blob
const blob = await compressedResponse.blob();
await fetch(url, {
method: 'POST',
body: blob,
headers: {
'Content-Encoding': 'gzip',
}
});
我相信还有许多其他技术可以用于我们新压缩的 JSON 对象。
很酷吧?但是我们如何将数据转换成浏览器中可读的形式呢?
要解压缩,我们首先需要将压缩后的数据放回一个文件中ReadableStream。
const stream = compressedResponse.blob().stream();
或者从 base64 解码回来。
// base64 encoding to Blob
const stream = new Blob([b64decode(compressedBase64)], {
type: "application/json",
}).stream();
编辑:我使用的 b64 解码函数如下所示。
export function b64decode(str: string): ArrayBuffer {
const binary_string = window.atob(str);
const len = binary_string.length;
const bytes = new Uint8Array(new ArrayBuffer(len));
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
}
然后,这次我们使用压缩后的数据ReadableStream,再次执行该ReadableStream.pipeThrough方法,将数据通过管道传递给它gzip DecompressionStream。
const compressedReadableStream = stream.pipeThrough(
new DecompressionStream("gzip")
);
然后我们回到响应中,获取Blob并调用该Blob.text()方法以字符串格式获取内容。
const resp = await new Response(compressedReadableStream);
const blob = await resp.blob();
现在只需将此字符串解析回 JSON,我们就得到了解压缩后的对象,它又回到了 Javascript 中,可以正常使用了。
const data = JSON.parse(await blob.text());
演示和结果
我搭建了一个小型CodeSandbox,它接收一些在线 JSON 源,并按照gzip CompressionStream上述解决方案中描述的转换方法运行它们。
哦,对了。❤️ VueJS。
结论
首先,我非常享受撰写这篇文章和探索压缩流 API 的过程。它使用起来很简单,感觉像是对 Javascript 生态系统的一个非常好的补充。
就 API 本身的实用性而言,它确实帮我解决了减少持久数据占用空间的问题。
我已经把这个功能集成到我的应用中,但我计划很快将其更深入地整合到核心功能中。这应该会是另一个有趣的问题,因为该应用已经使用Age(rage(rage-wasm))集成了客户端加密。不过,那是以后的事了……
根据CanIUse 的数据,该 API 目前的支持率仅为 72.53%,尚未完全支持,这确实有点令人失望。但我仍然认为它非常值得开发。我们可以实现当压缩流 API不受支持时优雅地失败,从而放弃性能提升,所以这对我来说并非致命缺陷。
我不确定压缩流 API会成为我使用频率多高的工具,但我很高兴学习了它,而且我相信这不会是我最后一次使用它,或者至少会考虑使用它。
文章来源:https://dev.to/ternentdotdev/json-compression-in-the-browser-with-gzip-and-the-compression-streams-api-4135