Goで書いたMessagePackパッケージの紹介
MessagePackパッケージのgo-msgpackをgithubに公開しました。
ここでは、go-msgpack
の主な機能について紹介します。
arrayにするかmapにするか
goの構造体をMessagePackにエンコードすると、arrayにするかmapにするか任意性が存在します。そのため、Unmarshal
の2番目の引数でarrayとしてするかどうかのフラグがついています。簡単な例を挙げます。
package main
import (
"encoding/hex"
"flag"
"fmt"
gomsgpack "github.com/nnabeyang/go-msgpack"
)
type Pair struct {
X int
Y int
}
func main() {
isArray := flag.Bool("array", false, "decode as array format")
flag.Parse()
v := Pair{X: 1, Y: 2}
data, _ := gomsgpack.Marshal(v, *isArray)
fmt.Println(hex.EncodeToString(data))
}
上記コードを実行すると、次のような結果を得ます。
$ go run .
82a15801a15902
$ go run . -array
920102
一般にarrayにした方がキーを含まないので、容量を抑えられ通信には有利になります。
構造体のフィールド内の構造体をarrayにするかどうか
このパッケージを書く主なモチベーションとして、フィールド内の構造体をどちらとするか設定できるようにするというものがあります。これはタグで指定できるようにしました。例としては、次のようなものです。
package main
import (
"encoding/hex"
"fmt"
gomsgpack "github.com/nnabeyang/go-msgpack"
)
type Pair struct {
X int
Y int
}
type Record struct {
ID uint
Value Pair
}
type RecordArray struct {
ID uint
Value Pair `msgpack:"value,array"`
}
func main() {
v1 := Record{ID: 1, Value: Pair{X: 1, Y: 2}}
v2 := RecordArray{ID: 1, Value: Pair{X: 1, Y: 2}}
data1, _ := gomsgpack.Marshal(v1, false)
fmt.Println("Record : ", hex.EncodeToString(data1))
data2, _ := gomsgpack.Marshal(v2, false)
fmt.Println("RecordArray: ", hex.EncodeToString(data2))
}
実行結果は次の通りです。
$ go run .
Record : 82a2494401a556616c756582a15801a15902
RecordArray: 82a2494401a576616c7565920102
Record
はPair
の部分がmapにRecordArray
はarrayとしてエンコードしていることが分かります。この機能のおかげで構造体の中の構造体の中の構造体だけmapにエンコードしないといけないとかいう場合でも対応できます(デコードについても同様)。
mapのキーの名前を変更する
これはencoding/json
と同様の機能です。次のコードで確かめることができます。
package main
import (
"encoding/hex"
"fmt"
gomsgpack "github.com/nnabeyang/go-msgpack"
)
type Pair1 struct {
X int
Y int
}
type Pair2 struct {
X int `msgpack:"x"`
Y int
}
func main() {
v1 := Pair1{X: 1, Y: 2}
data1, _ := gomsgpack.Marshal(v1, false)
fmt.Println("pair1:", hex.EncodeToString(data1))
v2 := Pair2{X: 1, Y: 2}
data2, _ := gomsgpack.Marshal(v2, false)
fmt.Println("pair2:", hex.EncodeToString(data2))
}
結果は次の通りです。
$ go run .
pair1: 82a15801a15902
pair2: 82a17801a15902
上の58
が78
に変わっていると思いますが、意味的にはX
がx
に変わっています。
Timestamp
MessagePackには拡張型としてTimestampが定義されています。拡張型は拡張タイプという番号で識別されます。MessagePackとして定義されるものは-1
から-128
が割り当て可能で、アプリケーション独自に使える拡張タイプは0
から127
です。Timestampの拡張タイプは-1
です。
簡単な例は次の通りです。
package main
import (
"encoding/hex"
"fmt"
"time"
gomsgpack "github.com/nnabeyang/go-msgpack"
)
func main() {
t := gomsgpack.NewTimestamp(time.Date(2022, 07, 10, 15, 04, 05, 129, time.UTC))
data, _ := gomsgpack.Marshal(t, false)
fmt.Println(hex.EncodeToString(data))
// => d7ff0000020462caea65
var tt gomsgpack.Timestamp
gomsgpack.Unmarshal(data, &tt)
fmt.Println(time.Time(tt).Format("2006-01-02 15:04:05.000000000"))
// => 2022-07-10 15:04:05.000000129
}
拡張型の定義
Marshaler
のインターフェイスを実装するとエンコードできるようになり、Unmarshaler
を実装するとデコードできるようになります。この辺りはencoding/json
と同様ですが、上で説明したように拡張タイプ番号を決める必要があります。
type Marshaler interface {
MarshalMsgPack() ([]byte, error)
Type() int8
}
type Unmarshaler interface {
UnmarshalMsgPack([]byte) error
Type() int8
}
簡単な例を挙げておきます。
package main
import (
"encoding/hex"
"fmt"
gomsgpack "github.com/nnabeyang/go-msgpack"
)
type position struct {
X int8
Y int8
}
func (o *position) Type() int8 {
return 1
}
func (p *position) MarshalMsgPack() ([]byte, error) {
return []byte{byte(p.X), byte(p.Y)}, nil
}
func (o *position) UnmarshalMsgPack(data []byte) error {
*o = position{
X: int8(data[0]),
Y: int8(data[1]),
}
return nil
}
func main() {
in := &position{X: 1, Y: 2}
data, _ := gomsgpack.Marshal(in, false)
fmt.Println(hex.EncodeToString(data))
// => d5010102
var out position
gomsgpack.Unmarshal(data, &out)
fmt.Printf("X:%d, Y:%d\n", out.X, out.Y)
// => X:1, Y:2
}
パッケージの方に拡張型を登録する仕組みがあり、拡張タイプの一意性を保証するような作りが多いように思いますが、このパッケージはそこは何も管理しないつくりです。
Discussion