BSON

シリアライゼーションJavaScriptTypeScriptMongoDBバイナリ形式NoSQL

ライブラリ

BSON

概要

BSON (Binary JSON)は、JSONのバイナリエンコード形式で、MongoDBの主要なデータ保存・転送フォーマットとして開発されました。JSONの利便性を保ちながら、バイナリ形式による高速なパース・トラバーサル、拡張データ型のサポート(日付、バイナリデータ、ObjectId等)を提供します。JavaScript/TypeScript環境では、MongoDBドライバーと統合されており、データベース操作において透過的に使用されます。

詳細

BSONは、JSONの表現力を拡張しつつ、効率的なデータ保存と処理を実現するために設計されました。各要素に長さ情報を含むことで、データ全体をパースすることなく特定の要素にアクセスできます。MongoDBでは内部的にすべてのドキュメントをBSON形式で保存し、インデックス作成やクエリ処理を最適化しています。標準的なJSONでは表現できない、日付、正規表現、バイナリデータなどの型をネイティブサポートすることで、実用的なアプリケーション開発を支援します。

主な特徴

  • 拡張データ型: ObjectId、Date、Binary、Decimal128など12種類以上の型をサポート
  • 効率的なトラバーサル: 長さプレフィックスによる高速なデータアクセス
  • MongoDB統合: MongoDBの標準データフォーマットとして完全対応
  • 言語中立性: 多言語対応の公式ドライバーを提供
  • スキーマレス: JSONと同様の柔軟なデータ構造
  • バイナリ安全: バイナリデータの直接保存が可能

メリット・デメリット

メリット

  • 日付やバイナリデータなど拡張型のネイティブサポート
  • JSONより高速なパース・シリアライゼーション処理
  • MongoDBとのシームレスな統合
  • 一意のObjectId生成機能による分散システム対応
  • ネストされた構造の効率的なクエリ処理
  • 型情報を保持したまま保存・復元が可能

デメリット

  • JSONよりファイルサイズが大きくなる傾向
  • 人間が直接読み書きできないバイナリ形式
  • MongoDB以外での採用例が限定的
  • テキストエディタでの編集・デバッグが困難
  • 学習コストがJSONより高い
  • 一部のプログラミング言語でのサポートが限定的

参考ページ

書き方の例

基本的な使用方法

// BSONライブラリのインポート
import { BSON, ObjectId } from 'bson';

// BSONドキュメントの作成
const document = {
  _id: new ObjectId(),
  name: '田中太郎',
  age: 30,
  createdAt: new Date(),
  isActive: true,
  scores: [85, 90, 78],
  profile: {
    bio: 'ソフトウェアエンジニア',
    avatar: Buffer.from('バイナリデータ')
  }
};

// BSONへのシリアライズ
const bsonData = BSON.serialize(document);
console.log('BSON size:', bsonData.length, 'bytes');

// BSONからのデシリアライズ
const parsedDoc = BSON.deserialize(bsonData);
console.log('Parsed:', parsedDoc);

Extended JSON (EJSON) の使用

import { EJSON } from 'bson';

// 拡張型を含むドキュメント
const doc = {
  _id: new ObjectId(),
  price: BSON.Decimal128.fromString('99.99'),
  pattern: /^test$/i,
  binary: new BSON.Binary(Buffer.from('hello')),
  timestamp: new Date()
};

// EJSONへの変換(文字列形式で拡張型を表現)
const ejsonString = EJSON.stringify(doc, { relaxed: false });
console.log('EJSON:', ejsonString);

// EJSONからの復元
const restored = EJSON.parse(ejsonString);
console.log('Restored types:', {
  isObjectId: restored._id instanceof ObjectId,
  isDecimal: restored.price instanceof BSON.Decimal128,
  isRegExp: restored.pattern instanceof RegExp
});

MongoDBとの統合使用

import { MongoClient, ObjectId } from 'mongodb';

async function mongoExample() {
  const client = new MongoClient('mongodb://localhost:27017');
  await client.connect();
  
  const db = client.db('myapp');
  const users = db.collection('users');
  
  // BSONの型を活用したドキュメント挿入
  const result = await users.insertOne({
    _id: new ObjectId(),
    name: '山田花子',
    birthDate: new Date('1990-05-15'),
    tags: ['developer', 'tokyo'],
    metadata: new BSON.Binary(Buffer.from('メタデータ')),
    lastLogin: new Date()
  });
  
  // ObjectIdを使用したクエリ
  const user = await users.findOne({ 
    _id: new ObjectId(result.insertedId) 
  });
  
  // 日付型を使用した範囲クエリ
  const recentUsers = await users.find({
    lastLogin: { $gte: new Date(Date.now() - 24*60*60*1000) }
  }).toArray();
  
  await client.close();
}

カスタム型の処理

import { BSON } from 'bson';

// カスタムクラス
class Location {
  constructor(lat, lng) {
    this.latitude = lat;
    this.longitude = lng;
  }
  
  toBSON() {
    return {
      type: 'Point',
      coordinates: [this.longitude, this.latitude]
    };
  }
  
  static fromBSON(doc) {
    if (doc.type === 'Point') {
      return new Location(doc.coordinates[1], doc.coordinates[0]);
    }
    return doc;
  }
}

// 使用例
const location = new Location(35.6762, 139.6503); // 東京
const bson = BSON.serialize({ location });
const parsed = BSON.deserialize(bson);
console.log('Location:', parsed.location);

TypeScriptでの型安全な使用

import { ObjectId, Decimal128 } from 'bson';

// 型定義
interface Product {
  _id: ObjectId;
  name: string;
  price: Decimal128;
  categories: string[];
  inStock: boolean;
  createdAt: Date;
  image?: Buffer;
}

// 型安全なBSON操作
function createProduct(data: Omit<Product, '_id'>): Product {
  return {
    _id: new ObjectId(),
    ...data
  };
}

const product = createProduct({
  name: 'TypeScript入門',
  price: Decimal128.fromString('2980'),
  categories: ['書籍', 'プログラミング'],
  inStock: true,
  createdAt: new Date()
});

// シリアライズ/デシリアライズ
const bsonData = BSON.serialize(product);
const restored = BSON.deserialize(bsonData) as Product;

// 型チェックが有効
console.log(restored.price.toString()); // "2980"

バイナリデータの処理

import { BSON } from 'bson';
import fs from 'fs';

// 画像ファイルの保存
async function saveImageToBSON(imagePath, outputPath) {
  const imageBuffer = await fs.promises.readFile(imagePath);
  
  const document = {
    filename: imagePath.split('/').pop(),
    contentType: 'image/jpeg',
    data: new BSON.Binary(imageBuffer),
    uploadedAt: new Date(),
    size: imageBuffer.length
  };
  
  const bsonData = BSON.serialize(document);
  await fs.promises.writeFile(outputPath, bsonData);
  
  return bsonData.length;
}

// 画像の復元
async function loadImageFromBSON(bsonPath) {
  const bsonData = await fs.promises.readFile(bsonPath);
  const document = BSON.deserialize(bsonData);
  
  // バイナリデータをBufferとして取得
  const imageBuffer = document.data.buffer;
  
  return {
    filename: document.filename,
    contentType: document.contentType,
    data: imageBuffer,
    size: document.size
  };
}