bsonとjsonでnil値をMarshalする時の挙動が違う


golangのライブラリでbson(gopkg.in/mgo.v2/bson)もjson(encoding/json)も基本的に同じ(もちろん、出力データフォーマットが構造化テキストかbinaryかという違いはありますが)挙動だろうと思っていましたが、やはり思い込みは良くないですね。

ライブラリの実装短いんだし読めよって話もありますが、それはまた次回

```go
package main

import (
	"encoding/json"
	"fmt"
	"gopkg.in/mgo.v2/bson"
	//"labix.org/v2/mgo/bson"
)

type Person struct {
	Name     string
	Metadata map[string]string
}

func main() {
	person := Person{Name: "Yuki"}
	fmt.Printf("Is person.Metadata null: %s\n", person.Metadata == nil)

	bsonData, err := bson.Marshal(&person)
	if err != nil {
		panic(err)
	}
	personFromBson := Person{}
	err = bson.Unmarshal(bsonData, &personFromBson)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Bson: %q\n", bsonData)
	fmt.Printf("Is personFromBson.Metadata null: %s\n", personFromBson.Metadata == nil)

	jsonData, err := json.Marshal(&person)
	if err != nil {
		panic(err)
	}
	personFromJson := Person{}
	err = json.Unmarshal(jsonData, &personFromJson)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Json: %q\n", jsonData)
	fmt.Printf("Is personFromJson.Metadata null: %s\n", personFromJson.Metadata == nil)
}
```

このプログラムでは、MetadataというattributeをもつPerson structを用意し、Metadataにnilを入れたままjsonとbsonでMarshalしております。でMarshalしたバイナリ, 構造化テキストをUnmarshalしその結果がnilかどうかを調べております

```bash
Yuki-no-MacBook-Pro-17795:gobson ukinau$ go run mgobson.go
Is person.Metadata null: %!s(bool=true)
Bson: "#\x00\x00\x00\x02name\x00\x05\x00\x00\x00Yuki\x00\x03metadata\x00\x05\x00\x00\x00\x00\x00"
Is personFromBson.Metadata null: %!s(bool=false)
Json: "{\"Name\":\"Yuki\",\"Metadata\":null}"
Is personFromJson.Metadata null: %!s(bool=true)
```

上記を見ての通り、

- **bson** の場合は、nil値を初期化してserialise
- **json** の場合は、nil値をnullとしてそのままserialise

していることがわかります。

ただ、上記の結果からのみだと、Data -> Marshal -> Unmarshal -> Dataした結果を見てどこかで、nil値がmapで初期化されていることしか確認できないので、Marshalで出力したbsonが本当にnullでなく空のmap相当の値が入っているいるのかわからないです。なので、

# bsonで確かに、{} mapが初期化されていて、null値でないことを確かめる

specificationを読んで出力されたbsonを読み解いていこうと思います。
http://bsonspec.org/spec.html

まず、下記が今回出力されたbsonデータです。上の結果よりコピーしてます。一部１６進数がUTF-8として文字評価されているので、#やらYukiやらmetadataという文字列が入っていますがこれらはUTF-8の文字列になります。

>"#\x00\x00\x00\x02name\x00\x05\x00\x00\x00Yuki\x00\x03metadata\x00\x05\x00\x00\x00\x00\x00"

別記事で全体像を含めた解説をもう少し詳しく書きますが(まぁ短いspecificationをただ、長ったらしく説明するだけですが)、今回注目すべきは後半の下記の記述です

>\x03metadata\x00\x05\x00\x00\x00\x00\x00"

まずbsonでは、mapの各キーとバリューのpairをelementと呼びそのelementにもいくつか種類がありelementの種類によって、接頭子に使う値や後続する置くべき値が違います。

>\03

ここではmetadataが、\03から始まるので、bsonのelementのspecificationから該当するelementの種類を探すと

>"\x03" e_name document 	Embedded document

この種類に該当することがわかります。
e_nameがキーの名前のUTF-8文字列 + \x00 を示しその後にdocument(golangでmap)が続くと決められているので、documentを示すbinary文字列(リトルエンディアンで最初4byteがdocumentのサイズ、5byte目がdocumentの終了を示す値\x00)が入っています。なので、golang のbson(gopkg.in/mgo.v2/bson) ライブラリはMarshal時に、mapを初期化してbsonデータにしていることがわかります。

# bsonというdata formatにそもそもnull値がないんじゃないか？

とすると、次に疑うのがそもそもbsonというデータフォーマットがnull値をサポートしていないんじゃないか？
いえ、そんなことはありません。

> 	"\x0A" e_name 	Null value

上記、specificationからの引用ですがきちんとnull値というelementのもあるのbsonとしてはnullを指定することはできます。

# golangのbson(gopkg.in/mgo.v2/bson)でnull値を含むbsonをdecodeすると？

出力時(Marshal)にnull値を含むbsonを出力しないので、単純な興味として、Unmarshalしてnull値としてloadできるのかなというのを試したいと思います。

Bsonを下記のように変えてみます

>"\x1e\x00\x00\x00\x02name\x00\x05\x00\x00\x00Yuki\x00\0Ametadata\x00\x00"

上記の説明の通り、\03から始まるdictionaryを示すelementだったものを\0Aから始まるnullを示すelementに変えて、全体のbyte数が変わったので、一番先頭の\x23から5byte引いて\x1eにしてみました。
これをMarshalして、Metadataがnullかどうかを調べると

```go
package main

import (
	"encoding/json"
	"fmt"
	"gopkg.in/mgo.v2/bson"
	//"labix.org/v2/mgo/bson"
)

type Person struct {
	Name     string
	Metadata map[string]string
}

func main() {
	bsonData = []byte("\x1e\x00\x00\x00\x02name\x00\x05\x00\x00\x00Yuki\x00\x0Ametadata\x00\x00")
	personFromBson := Person{}
	err := bson.Unmarshal(bsonData, &personFromBson)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Is personFromBson.Metadata null: %s\n", personFromBson.Metadata == nil)
}
```

結果

```bash
Yuki-no-MacBook-Pro-17795:gobson ukinau$ go run mgobson.go
Is personFromBson.Metadata null: %!s(bool=true)
```

となるので、きちんとnull値をUnmarshalできていることがわかります。


# 結論と疑問

bsonデータを直接見ても分かる通り、golangのbsonライブラリはnilをnullと解釈せず、各typeで初期化してserialiseするようです。これは意図しているのか、実装上そうなったのかはまだ追えてませんが、個人的な感想としては、jsonライブラリの挙動に合わせていった方がわかりやすいなーと思います。


golangの勉強も兼ねて、bsonのライブラリは今度読んで見たいと思います。