如何用 JavaScript 中的任意多个文档反序列化转储的 BSON?

fhf*_*uih 4 javascript mongodb node.js bson

我有一个BSON来自mongoexport数据库的文件。我们假设数据库是todo,集合是items。现在我想将数据离线加载到我的 RN 应用程序中。由于集合可能包含任意多个文档(目前假设有 2 个),因此我想使用一种方法来解析文件,无论它包含多少个文档。

我尝试过以下方法:

  1. 使用外部bsondump可执行文件。

JSON我们可以使用外部命令将文件转换为

bsondump --outFile items.json items.bson
Run Code Online (Sandbox Code Playgroud)

但我正在开发一个移动应用程序,因此在 shell 命令中调用第三方可执行文件并不理想。另外,输出包含多行单行 JSON 对象,因此从技术上讲,输出不是正确的 JSON 文件。所以事后解析并不优雅。

  1. deserializejs-bson图书馆使用

根据js-bson文档,我们可以这样做

const bson = require('bson')
const fs = require('fs')
bson.deserialize(fs.readFileSync(PATH_HERE))
Run Code Online (Sandbox Code Playgroud)

但这会引发错误

Error: buffer length 173 must === bson size 94
Run Code Online (Sandbox Code Playgroud)

并通过添加此选项,

bson.deserialize(fs.readFileSync(PATH_HERE), {
    allowObjectSmallerThanBufferSize: true
})
Run Code Online (Sandbox Code Playgroud)

错误已解决,但仅返回第一个文档。因为文档没有提到这个函数只能解析1个文档集合,所以我想知道是否有一些选项可以启用多个文档读取。

  1. 用于deserializeStreamjs-bson
let docs = []
bson.deserializeStream(fs.readFileSync(PATH_HERE), 0, 2, docs, 0)
Run Code Online (Sandbox Code Playgroud)

但此方法需要文档计数参数(此处为 2)。

  1. 使用bson-stream

我实际上使用react-native-fetch-blob的是fs,并且根据他们的文档,流对象没有方法pipe,这是 doc 中演示的唯一方法bson-stream。所以虽然这个方法不需要文档数量,但我很困惑如何使用它。

// fs
const BSONStream = require('bson-stream');
fs.createReadStream(PATH_HERE).pipe(new BSONStream()).on('data', callback);

// RNFetchBlob
const RNFetchBlob = require('react-native-fetch-blob');
RNFetchBlob.fs.readStream(PATH_HERE, ENCODING)
.then(stream => {
    stream.open();
    stream.can_we_pipe_here(new BSONStream())
    stream.onData(callback)
});
Run Code Online (Sandbox Code Playgroud)

另外我对上面的内容也不太确定ENCODING

fhf*_*uih 5

我阅读了源代码js-bson并找到了解决问题的方法。我觉得这里最好详细记录一下:

方法一

我们自己拆分文档,并将文档一一送入解析器。

BSON内部格式

假设.json我们的转储todo/items.bson

{_id: "someid#1", content: "Launch a manned rocket to the sun"}
{_id: "someid#2", content: "Wash my underwear"}
Run Code Online (Sandbox Code Playgroud)

这显然违反了JSON 语法,因为没有外部对象将事物包装在一起。

编辑:我后来遇到了术语“JSON流”,它似乎描述了这种格式。该术语由(用于 JSON 操作的命令行工具)使用jq,尽管该术语可能表明这些 JSON 文档旨在在流中逐一出现。

内部 BSON 具有类似的形状,但 BSON 似乎允许在一个文件中填充这种多对象。

那么对于每个文档,前四个字节表示这个文档的长度,包括这个前缀本身和后缀。后缀只是一个 0 字节。

最终的 BSON 文件类似于

LLLLDDDDDDD0LLLLDDD0LLLLDDDDDDDDDDDDDDDDDDDDDD0...
Run Code Online (Sandbox Code Playgroud)

其中L是长度,D是二进制数据,0字面意思是 0。

算法

因此,我们可以开发一个简单的算法来获取文档长度,执行该算法bson.deserialize将从allowObjectSmallerThanBufferSize缓冲区开始获取第一个文档,然后切掉该文档并重复。

关于编码

我提到的另一件事是在 React Native 上下文中进行编码。处理 React Native 持久性的库似乎都缺乏从文件读取原始缓冲区的支持。我们最接近的选择是,它是任何base64二进制文件的字符串表示形式。然后我们使用将字符串转换为缓冲区并输入到上面的算法中。Bufferbase64

代码

反序列化.js

{_id: "someid#1", content: "Launch a manned rocket to the sun"}
{_id: "someid#2", content: "Wash my underwear"}
Run Code Online (Sandbox Code Playgroud)

应用程序.js

LLLLDDDDDDD0LLLLDDD0LLLLDDDDDDDDDDDDDDDDDDDDDD0...
Run Code Online (Sandbox Code Playgroud)

方法2

上述方法将文件作为一个整体读取。有时,当我们有一个非常大的.bson文件时,应用程序可能会崩溃。当然,可以更改readFilereadStream上面的内容并添加各种检查以确定当前块是否包含文档的结尾。这可能会很麻烦,而且我们实际上正在重写bson-stream库!

因此,我们可以创建一个RNFetchBlob文件流和另一个bson-stream解析流。这让我们回到了问题中的尝试#4 。

看完源码,BSON解析流继承自一个node.js Transform stringpiping我们可以手动将块和事件转发到和onData,而不是。onEndon('data')on('end')

由于bson-stream不支持将选项传递给底层bson库调用,因此人们可能希望在自己的项目中稍微调整库源代码。