go言語標準encoding/jsonパッケージのUnmarshalの挙動が面白かったので調べてみた

goでjsonを扱う時のjsonパッケージの挙動が面白かったので記事にまとめてみます。

## 鍵が全て一致する場合

```main.go
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	foo := type1{2, 3}
	fmt.Printf("foo: %v\n", foo)
	b, _ := json.Marshal(foo)
	fmt.Printf("json: %s\n", b)
	var bar type2
	err := json.Unmarshal(b, &bar)
	fmt.Printf("bar: %v\nerror: %v\n", bar, err)
}

type type1 struct {
	A int
	B int
}

type type2 struct {
	A int
	B int
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {2 3}
error: <nil>
```

jsonの鍵を指定せずにjson.Marshalすると、structにつけた名前がそのまま利用されます。

## 鍵が全て一致する場合2

以下、基本的にmain関数は使いまわし、type文のみ変更していきます。

```go
type type1 struct {
	A int `json:"a"`
	B int `json:"b"`
}

type type2 struct {
	A int `json:"a"`
	B int `json:"b"`
}
```

```
foo: {2 3}
json: {"a":2,"b":3}
bar: {2 3}
error: <nil>
```

jsonの鍵を指定するとそちらが優先されます。

## 鍵がすべて一致しない場合

```go
type type1 struct {
	A int
	B int
}

type type2 struct {
	C int
	D int
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {0 0}
error: <nil>
```

鍵が一致しないので、CとDは初期化された値(0)になります。重要な点はエラーがnilとなっていることです。Unmarshalは一致する鍵にデータを格納するだけなので、型が違っても問題ないようです。

## 鍵がすべて一致しない場合2

```go
type type1 struct {
	A int `json:"a"`
	B int `json:"b"`
}

type type2 struct {
	C int `json:"c"`
	D int `json:"d"`
}
```

```
foo: {2 3}
json: {"a":2,"b":3}
bar: {0 0}
error: <nil>
```

当然こちらも同じ挙動

## 一部の鍵だけ違う場合

```go
type type1 struct {
	A int
	B int
}

type type2 struct {
	A int
	D int
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {2 0}
error: <nil>
```

鍵が一致していれば一致しているところのみ値が入ります。残りは初期値。

## 一部の鍵だけ違う場合2

```go
type type1 struct {
	A int `json:"a"`
	B int `json:"b"`
}

type type2 struct {
	C int `json:"a"`
	D int `json:"d"`
}
```

```
foo: {2 3}
json: {"a":2,"b":3}
bar: {2 0}
error: <nil>
```

同様です。

## type2の鍵がかぶっている場合

ここから本題になります。

```go
type type1 struct {
	A int `json:"A"`
	B int
}

type type2 struct {
	C int `json:"A"`
	A int
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {2 0}
error: <nil>
```
逆パターン

```go
type type1 struct {
	A int `json:"A"`
	B int
}

type type2 struct {
	A int
	D int `json:"A"`
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {0 2}
error: <nil>
```

構造体の鍵とjsonの鍵で同じものをつけた場合、jsonの鍵へのUnmarshalが優先されるようです。

## type2の鍵がかぶっている場合2

```go
type type1 struct {
	A int `json:"A"`
	B int
}

type type2 struct {
	C int `json:"A"`
	D int `json:"A"`
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {0 0}
error: <nil>
```

jsonの鍵がかぶっていた場合どちらにも値が入らなくなります。重要なのは、エラー無しでこの処理が行われることです。

## type2の鍵がかぶっている場合3

```go
type type2 struct {
	A int
	C int `json:"A"`
	D int `json:"A"`
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {0 0 0}
error: <nil>
```

いろいろくっつけてみてもやはりエラー無しで何も代入されないようです。

## 型が違う場合

```go
type type1 struct {
	A int `json:"A"`
	B int
}

type type2 struct {
	C bool `json:"A"`
	D int
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {false 0}
error: json: cannot unmarshal number into Go struct field type2.A of type bool
```

やっとエラーが出ました。型が誤っている場合にはエラーとなります。

## 型が違う場合2

ちなみに

```go
type type1 struct {
	A int `json:"A"`
	B int
}

type type2 struct {
	C float64 `json:"A"`
	D int
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {2 0}
error: <nil>
```

確かjsonはintとfloatの区別をしないので、floatでUnmarshalできます。

## 型が違う場合3

```go
type type1 struct {
	A int `json:"A"`
	B int
}

type type2 struct {
	C int    `json:"A"`
	D string `json:"A"`
}
```

```
foo: {2 3}
json: {"A":2,"B":3}
bar: {0 false}
error: <nil>
```

jsonの鍵がかぶっている場合は、型の判定はされずエラーが出ないようです。

## まとめ

encoding/jsonはうっかりミスしても、Unmarshalするときにエラーを出してくれないことがあるので注意が必要です。