Goで[]byte
をjsonパッケージで扱うときの挙動につまったので調べました。
不思議な挙動
以下をみてください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package main
import (
"encoding/json"
"fmt"
"log"
)
type A struct {
Hoge []byte `json:"hoge"`
}
var data = []byte(`{"hoge": "fuga"}`)
func main() {
a := A{}
if err := json.Unmarshal(data, &a); err != nil {
log.Fatal(err)
}
fmt.Println(string(a.Hoge))
}
|
感覚では、stringはバイト列なので、
と出力しそうに思えるんですが、実際には謎の文字列が表示されます (playground)
理由
jsonパッケージのコードを追っていってわかりました。
1
| func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) error
|
の中に、以下のような処理があります。
1
2
3
4
5
6
7
8
9
10
11
12
| case reflect.Slice:
if v.Type().Elem().Kind() != reflect.Uint8 {
d.saveError(&UnmarshalTypeError{Value: "string", Type:v.Type(), Offset: int64(d.readIndex())})
break
}
b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
n, err := base64.StdEncoding.Decode(b, s)
if err != nil {
d.saveError(err)
break
}
v.SetBytes(b[:n])
|
view on GitHub
文字列をセットするときに、セットする対象がreflect.Uint8
の型のスライスならばbase64.StdEncoding
でエンコードしてからセットされるらしいです。byte
はuint8
へのエイリアスなので、最初に挙げた例ではhoge
がエンコードされた~�
が出力されたんだと思います。
ちなみに、ちゃんとドキュメントにも書いてありました。ドキュメントを読みましょう。ごめんなさい。
Unmarshal
Unmarshal uses the inverse of the encodings that Marshal uses, …
Marshal
Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON value.
対処法
stringを使う
型を[]byte
ではなくてstringにすると動きます。[]byte(s)
でコピーなしで変換できるので良さそうと思ったんですが実はコピーが走るらしいです(!?)
以下の記事ではunsafe
パッケージを使用したノーコピーのやり方が書いてありました。
[]byte と string のキャスト - tocsatoの備忘録
RawMessageを使う
jsonパッケージにRawMessageという[]byte
へのエイリアスの型があって、UnmarshalJSON
が実装されているので、たとえば最初の例では"
付きで"fuga"
がセットされます。[]byte
に変換して適切にカットしてあげれば$O(1)$でお目当ての[]byte
が得られるはずです(多分)。
自分でUnmarshalJSONを書く
たとえば以下のようなコードを書けます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package main
import (
"encoding/json"
"fmt"
"log"
)
type A struct {
Hoge []byte `json:"hoge"`
}
func (a *A) UnmarshalJSON(data []byte) error {
type A2 struct {
Hoge string `json:"hoge"`
}
a2 := new(A2)
if err := json.Unmarshal(data, a2); err != nil {
return err
}
a.Hoge = []byte(a2.Hoge)
return nil
}
|
感想
なぜbase64でエンコード/デコードするのか、理由があるのでしょうか。不思議。
ライセンス表記
以下のライセンスに基づきGoのソースコードを引用しています。
LICENSE - The Go Programming Language