BSONとundefined

備忘録です

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オブジェクト編に続きます。