js-bson
シリアライゼーションライブラリ
js-bson
概要
js-bsonは、MongoDB公式のBSON(Binary JSON)パーサーです。Node.jsとブラウザ環境の両方で動作し、JSONライクなドキュメントを効率的なバイナリフォーマットで処理します。ObjectId、Long、Decimal128など、BSONの豊富なデータ型をサポートし、Extended JSON(EJSON)機能も提供しています。
詳細
BSONは「Binary JSON」の略で、JSONライクなドキュメントをバイナリエンコードするフォーマットです。MongoDBの内部データ形式として使用され、js-bsonはこのフォーマットの公式TypeScript/JavaScript実装を提供しています。
主な特徴:
- クロスプラットフォーム: Node.jsとブラウザ環境の両方をサポート
- 豊富なデータ型: ObjectId、Long、Decimal128、Binary、UUIDなど
- Extended JSON: 人間が読める形式との相互変換
- TypeScript対応: 完全な型定義とTypeScript支援
- toBSON()フック: カスタムシリアライゼーションロジック
技術的詳細:
- コア関数: serialize()、deserialize()、calculateObjectSize()
- ストリーム対応: deserializeStream()によるストリーム処理
- 設定オプション: checkKeys、serializeFunctions、ignoreUndefined
- エラーハンドリング: BSONError.isBSONError()による型安全なエラーチェック
BSONデータ型:
- Binary(バイナリデータ、UUID)
- ObjectId(ユニークID)
- Long(64bit整数)
- Decimal128(高精度小数)
- Double、Int32、Timestamp
- Code(JavaScriptコード)
- DBRef(参照)
メリット・デメリット
メリット
- MongoDBエコシステムとの完全な互換性
- JSONより効率的なバイナリ表現
- 豊富なデータ型によるスキーマレス設計
- TypeScript完全対応による型安全性
- Node.jsとブラウザの両方で動作
- Extended JSONによる人間が読める形式
デメリット
- JSONと比べて人間が読みにくい
- MongoDBに特化したフォーマット
- ファイルサイズがやや大きい
- デバッグ時のデータ確認が困難
- 一般的なJSONツールとの互換性がない
参考ページ
- GitHubリポジトリ: https://github.com/mongodb/js-bson
- ドキュメント: https://mongodb.github.io/js-bson/
- MongoDB公式サイト: https://docs.mongodb.com/
- BSON仕様: http://bsonspec.org/
書き方の例
基本的な使用方法
import { BSON } from 'bson';
interface User {
name: string;
age: number;
email: string;
createdAt: Date;
}
const user: User = {
name: 'Alice',
age: 30,
email: '[email protected]',
createdAt: new Date()
};
// BSONにシリアライズ
const serialized = BSON.serialize(user);
console.log('Serialized:', serialized.length, 'bytes');
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized) as User;
console.log('Deserialized:', deserialized);
ObjectIdの使用
import { BSON, ObjectId } from 'bson';
interface Document {
_id: ObjectId;
title: string;
content: string;
tags: string[];
}
const doc: Document = {
_id: new ObjectId(),
title: 'My Document',
content: 'This is the content of my document.',
tags: ['typescript', 'mongodb', 'bson']
};
// BSONにシリアライズ
const serialized = BSON.serialize(doc);
console.log('Document ID:', doc._id.toHexString());
console.log('Serialized size:', serialized.length);
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized) as Document;
console.log('Deserialized ID:', deserialized._id.toHexString());
Long型(64bit整数)の使用
import { BSON, Long } from 'bson';
interface Statistics {
totalViews: Long;
totalUsers: Long;
revenue: number;
}
const stats: Statistics = {
totalViews: Long.fromNumber(1234567890123),
totalUsers: Long.fromNumber(987654321),
revenue: 12345.67
};
// BSONにシリアライズ
const serialized = BSON.serialize(stats);
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized) as Statistics;
console.log('Total views:', deserialized.totalViews.toString());
console.log('Total users:', deserialized.totalUsers.toNumber());
Decimal128の使用
import { BSON, Decimal128 } from 'bson';
interface FinancialData {
productId: string;
price: Decimal128;
tax: Decimal128;
total: Decimal128;
}
const data: FinancialData = {
productId: 'PROD-001',
price: Decimal128.fromString('99.99'),
tax: Decimal128.fromString('8.00'),
total: Decimal128.fromString('107.99')
};
// BSONにシリアライズ
const serialized = BSON.serialize(data);
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized) as FinancialData;
console.log('Price:', deserialized.price.toString());
console.log('Tax:', deserialized.tax.toString());
console.log('Total:', deserialized.total.toString());
バイナリデータの処理
import { BSON, Binary } from 'bson';
interface FileDocument {
filename: string;
contentType: string;
data: Binary;
size: number;
}
// バイナリデータを作成
const imageData = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
const binary = new Binary(imageData);
const file: FileDocument = {
filename: 'image.png',
contentType: 'image/png',
data: binary,
size: imageData.length
};
// BSONにシリアライズ
const serialized = BSON.serialize(file);
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized) as FileDocument;
console.log('Filename:', deserialized.filename);
console.log('Size:', deserialized.size);
console.log('Binary data length:', deserialized.data.buffer.length);
Extended JSON(EJSON)の使用
import { BSON, EJSON, ObjectId, Long } from 'bson';
interface ComplexDocument {
_id: ObjectId;
timestamp: Date;
count: Long;
metadata: {
version: string;
flags: boolean[];
};
}
const doc: ComplexDocument = {
_id: new ObjectId(),
timestamp: new Date(),
count: Long.fromNumber(1234567890),
metadata: {
version: '1.0.0',
flags: [true, false, true]
}
};
// Extended JSONに変換
const ejson = EJSON.stringify(doc, null, 2);
console.log('Extended JSON:');
console.log(ejson);
// Extended JSONからパース
const parsed = EJSON.parse(ejson) as ComplexDocument;
console.log('Parsed ID:', parsed._id.toHexString());
console.log('Parsed timestamp:', parsed.timestamp);
console.log('Parsed count:', parsed.count.toString());
カスタムシリアライゼーション(toBSON)
import { BSON, ObjectId } from 'bson';
class User {
public _id: ObjectId;
public name: string;
public email: string;
private password: string;
constructor(name: string, email: string, password: string) {
this._id = new ObjectId();
this.name = name;
this.email = email;
this.password = password;
}
// カスタムシリアライゼーション
toBSON() {
return {
_id: this._id,
name: this.name,
email: this.email,
// パスワードは除外
hasPassword: !!this.password
};
}
}
const user = new User('Alice', '[email protected]', 'secret123');
// BSONにシリアライズ(toBSONが自動的に呼ばれる)
const serialized = BSON.serialize(user);
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized);
console.log('Deserialized:', deserialized);
// パスワードは含まれない
ストリーム処理
import { BSON } from 'bson';
interface LogEntry {
timestamp: Date;
level: string;
message: string;
metadata?: Record<string, any>;
}
const logEntries: LogEntry[] = [
{
timestamp: new Date(),
level: 'INFO',
message: 'Application started',
metadata: { pid: 12345 }
},
{
timestamp: new Date(),
level: 'ERROR',
message: 'Database connection failed',
metadata: { error: 'ECONNREFUSED' }
}
];
// 複数のドキュメントをシリアライズ
const serializedEntries = logEntries.map(entry => BSON.serialize(entry));
const totalSize = serializedEntries.reduce((sum, data) => sum + data.length, 0);
console.log(`Serialized ${logEntries.length} entries in ${totalSize} bytes`);
// ストリームからデシリアライズ
const deserializedEntries = serializedEntries.map(data =>
BSON.deserialize(data) as LogEntry
);
deserializedEntries.forEach((entry, index) => {
console.log(`Entry ${index}: ${entry.level} - ${entry.message}`);
});
エラーハンドリング
import { BSON, BSONError } from 'bson';
interface SafeDocument {
name: string;
value: number;
}
function safeBSONOperation(doc: SafeDocument): boolean {
try {
// BSONにシリアライズ
const serialized = BSON.serialize(doc);
// BSONからデシリアライズ
const deserialized = BSON.deserialize(serialized) as SafeDocument;
// バリデーション
if (!deserialized.name || deserialized.value < 0) {
throw new Error('Invalid document data');
}
console.log('Operation successful:', deserialized);
return true;
} catch (error) {
if (BSONError.isBSONError(error)) {
console.error('BSON error:', error.message);
} else {
console.error('General error:', error);
}
return false;
}
}
// 正常なケース
safeBSONOperation({ name: 'Test', value: 42 });
// 異常なケース
safeBSONOperation({ name: '', value: -1 });
パフォーマンス測定
import { BSON } from 'bson';
interface BenchmarkData {
id: number;
values: number[];
metadata: string;
}
function benchmarkBSON(data: BenchmarkData, iterations: number): void {
const start = Date.now();
for (let i = 0; i < iterations; i++) {
// シリアライズ
const serialized = BSON.serialize(data);
// デシリアライズ
const deserialized = BSON.deserialize(serialized);
}
const duration = Date.now() - start;
console.log(`BSON: ${iterations} iterations in ${duration}ms`);
console.log(`Average: ${duration / iterations}ms per iteration`);
}
const testData: BenchmarkData = {
id: 12345,
values: Array.from({ length: 1000 }, (_, i) => i * 0.1),
metadata: 'benchmark_data'.repeat(10)
};
benchmarkBSON(testData, 1000);
設定オプション
import { BSON } from 'bson';
interface ConfigurableDocument {
name: string;
value: number;
func?: Function;
optional?: string;
}
const doc: ConfigurableDocument = {
name: 'Test',
value: 42,
func: function() { return 'hello'; },
optional: undefined
};
// カスタム設定でシリアライズ
const serialized = BSON.serialize(doc, {
checkKeys: true, // キーの検証
serializeFunctions: true, // 関数のシリアライズ
ignoreUndefined: true // undefinedの無視
});
// カスタム設定でデシリアライズ
const deserialized = BSON.deserialize(serialized, {
promoteBuffers: true, // Bufferの昇格
promoteLongs: true, // Longの昇格
promoteValues: true // 値の昇格
});
console.log('Deserialized:', deserialized);