Как десериализовать дамп BSON с произвольным количеством документов в JavaScript? - PullRequest
0 голосов
/ 13 июня 2019

У меня есть BSON файл, полученный из mongoexport базы данных.Давайте предположим, что база данных todo, а коллекция items.Теперь я хочу загрузить данные в автономном режиме в мое приложение RN.Поскольку коллекция может содержать произвольное количество документов (скажем, на данный момент 2), я хочу использовать метод для анализа файла, сколько бы документов в нем ни было.

Я пробовал следующие методы:

  1. Использовать внешний bsondump исполняемый файл.

Мы можем преобразовать файл в JSON с помощью внешней команды

bsondump --outFile items.json items.bson

Но я разрабатываю мобильное приложение, поэтомувызов стороннего исполняемого файла в команде оболочки не идеален.Кроме того, выходные данные содержат несколько строк однострочных объектов JSON , поэтому технически выходные данные не являются правильным файлом JSON.Поэтому последующий разбор не изящен.

Используйте deserialize в js-bson библиотеке

В соответствии с js-bson документацией мы можем сделать

const bson = require('bson')
const fs = require('fs')
bson.deserialize(fs.readFileSync(PATH_HERE))

Но это поднимаетошибка

Error: buffer length 173 must === bson size 94

и добавлением этой опции

bson.deserialize(fs.readFileSync(PATH_HERE), {
    allowObjectSmallerThanBufferSize: true
})

ошибка устранена, но возвращается только первый документ.Поскольку в документации не упоминается, что эта функция может анализировать только коллекцию из 1 документа, мне интересно, есть ли какая-либо опция, позволяющая читать несколько документов.

Используйте deserializeStream in js-bson
let docs = []
bson.deserializeStream(fs.readFileSync(PATH_HERE), 0, 2, docs, 0)

Но для этого метода требуется параметр количества документов (2 здесь).

Использовать bson-stream библиотеку

Я на самом деле использую react-native-fetch-blob вместо fs, и согласно их документации, у объекта потока нет метода pipe, которыйединственный метод, продемонстрированный в 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)
});

Также я не уверен насчет вышеуказанного ENCODING выше.

1 Ответ

0 голосов
/ 24 июня 2019

Я прочитал исходный код 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"}

Что явно нарушает синтаксис JSON. Внутренний BSON имеет аналогичную форму, но кажется, что BSON позволяет этот вид многообъектного заполнения в одном файле.

Затем для каждого документа четыре старших байта указывают длину этого документа, включая сам этот префикс и суффикс. Суффикс просто 0 байт.

Окончательный файл BSON напоминает

LLLLDDDDDDD0LLLLDDD0LLLLDDDDDDDDDDDDDDDDDDDDDD0...

где L - длина, D - двоичные данные, 0 - буквально 0.

Алгоритм

Следовательно, мы можем разработать простой алгоритм для получения длины документа, сделать bson.deserialize с allowObjectSmallerThanBufferSize, который получит первый документ из начала буфера, затем отрежет этот документ и повторите.

О кодировке

Еще одна вещь, которую я упомянул, - это кодирование в контексте React Native. Похоже, что библиотекам, работающим с React Native persistent, не хватает поддержки чтения raw buffer из файла. Ближайший выбор у нас есть base64, который является строковым представлением любого двоичного файла. Затем мы используем Buffer для преобразования base64 строк в буферы и подачи в алгоритм выше.

код

deserialize.js

const BSON = require('bson');

function _getNextObjectSize(buffer) {
    // this is how BSON 
    return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
}

function deserialize(buffer, options) {
    let _buffer = buffer;
    let _result = [];

    while (_buffer.length > 0) {
        let nextSize = _getNextObjectSize(_buffer);
        if (_buffer.length < nextSize) {
            throw new Error("Corrupted BSON file: the last object is incomplete.");
        }
        else if (_buffer[nextSize - 1] !== 0) {
            throw new Error(`Corrupted BSON file: the ${_result.length + 1}-th object does not end with 0.`);
        }

        let obj = BSON.deserialize(_buffer, {
            ...options,
            allowObjectSmallerThanBufferSize: true,
            promoteBuffers: true // Since BSON support raw buffer as data type, this config allows
            // these buffers as is, which is valid in JS object but not in JSON
        });
        _result.push(obj);
        _buffer = _buffer.slice(nextSize);
    }

    return _result;
}

module.exports = deserialize;

App.js

import RNFetchBlob from `rn-fetch-blob`;
const deserialize = require('./deserialize.js');
const Buffer = require('buffer/').Buffer;

RNFetchBlob.fs.readFile('...', 'base64')
    .then(b64Data => Buffer.from(b64Data, 'base64'))
    .then(bufferData => deserialize(bufferData))
    .then(jsData => {/* Do anything here */})
...