BSONとundefined

JavaScript, MongoDB

BSON とは

BSON (Binary JSON) Serialization

Binary JSON の略で、その名の通り Binary の JSON みたいな何か。
JSON の上位種でもあり、バイト配列や Date 型を持てたりもする。
主に MongoDB で使う。

undefined とは

未定義の変数とか未定義を表すために使われることがあったりするよくわからないプリミティブ値。

foo.ts

let foo
let bar = undefined

JSON と undefined

まず、JSON の場合…
RFC 8259 の項目を読む限り、JSON の値に undefined を含めることはできない。
JSON.stringify では、値が undefined の Key を省略するようになっている。

foo.ts

const foo = {
  hoge: undefined,
  fuga: void 0,
}

console.log(JSON.stringify(foo)) // "{}"

foo.hoge = 'bar'
console.log(JSON.stringify(foo)) // "{"hoge":"bar"}"

ただし、配列の中に含まれる undefined は、nullに変換される。

foo.ts

const foo = ['hoge', undefined, 'bar']
JSON.stringify(foo) // "["hoge",null,"bar"]"

const baz = [,,]
JSON.stringify(baz) // "[null,null]"

BSON と undefined

BSON では null になる。
以下のコードは、未定義、明示したもの、void 0、配列内の undefined な値を insert したもの。
console.log()では配列内の値も含めて、全て null に変換されていることがわかる。

mongo.ts

import mongodb from 'mongodb'

const MongoClient = mongodb.MongoClient
const URL = 'mongodb://127.0.0.1:27017/myDB'
const DB_NAME = 'mydb'
const COLLECTION_NAME = 'foo'

const client = new MongoClient(URL, {
  useNewUrlParser: true,
})

const connect = async () => {
  try {
    await client.connect()

    const collection = client.db(DB_NAME).collection(COLLECTION_NAME)

    let hoge

    await collection.insertOne({
      hoge,
      fuga: undefined,
      piyo: void 0,
      baz: [, undefined, void 0],
    })

    const itemList = await collection.find().toArray()

    console.log(itemList)
    // { hoge: null, fuga: null, piyo: null, baz: [ null, null, null ]}

  } catch (error) {}

  client.close()
}

connect()

MongoDB Node.js Driver ではこれを回避するために、ignoreUndefinedオプションを指定できる。
ignoreUndefinedが true の場合、JSON.stringify()のように undefined のプロパティを無視する。

mongo.ts

// 略

const client = new MongoClient(URL, {
  useNewUrlParser: true,
  ignoreUndefined: true,
})

// 略

console.log(itemList)
//  { baz: [ null, null, null ] }

// 略

BSON と undefined 2

しかし、spec をみる限りだと、どうやら undefined 自体は受け入れている。
例えば、以下のような insert query を実行すると

mongodb

db.foo.insert({poge: undefined, puga: [void 0, , undefined]})

普通に入ってしまう。

mongodb

{
  _id: 5e9afd45ff583d0116194c00,
  poge: undefined,
  puga: [ undefined, undefined, undefined ]
}

本当なのか。
BSON を JSON に Dump してみる。

shell

mongodump --port 27017 --db mydb
bsondump --outFile=foo.json foo.bson

この通り。

foo.json

{
  "_id": {
    "$oid": "5e9afd45ff583d0116194c00"
  },
  "poge": {
    "$undefined": true
  },
  "puga": [
    {
      "$undefined": true
    },
    {
      "$undefined": true
    },
    {
      "$undefined": true
    }
  ]
}

Deprecated だけど、MongoDB Node.js Driver でも一応読める。

mongo.ts

{
  _id: 5e9afd45ff583d0116194c00,
  poge: undefined,
  puga: [ undefined, undefined, undefined ]
},

もともと、MongoDB は undefinednull を持ちながらも、Shell では両方を null にしていた。
これが本当に未定義だった時に混乱するので、undefinedへデシリアライズするようになった。
という解釈をした。それに関する修正がこちら

終わり

次: Map オブジェクト編