Code Fix

中級

json.Unmarshalで非公開(小文字始まり)フィールドが無視される

encoding/jsonパッケージは、構造体の小文字始まりの非公開フィールドを読み書きできません。JSONのキーに対応するフィールド名を小文字で書いてしまうと、値が入らないままゼロ値になります。

なぜエラーが出ないのか

出力: {name:}
(エラーなし)
Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: {name:}
実際の出力 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  package main
 2  
 3  import (
 4  	"encoding/json"
 5  	"fmt"
 6  )
 7  
 8  type User struct {
 9  	name  string
       ^
10  }
11  
12  func main() {
13  	var u User
14  	json.Unmarshal([]byte(`{"name":"go"}`), &u)
15  	fmt.Printf("%+v\n", u)
16  }
出力: {name:}JSONは{"name":"go"}

小文字始まりのnameフィールドはencoding/jsonからアクセスできず、値が設定されないまま空文字列で残ります。

直し方: nameName にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  package main
 2  
 3  import (
 4  	"encoding/json"
 5  	"fmt"
 6  )
 7  
 8  type Account struct {
 9  	age  int
       ^
10  }
11  
12  func main() {
13  	var a Account
14  	json.Unmarshal([]byte(`{"age":30}`), &a)
15  	fmt.Printf("%+v\n", a)
16  }
出力: {age:0}JSONは{"age":30}

数値フィールドでも同様で、非公開フィールドにはリフレクション経由で値を書き込めずゼロ値の0のまま残ります。

直し方: ageAge にします。

この問題を解いてみる →

パターン3

 1  package main
 2  
 3  import (
 4  	"encoding/json"
 5  	"fmt"
 6  )
 7  
 8  type Contact struct {
 9  	email  string
        ^
10  }
11  
12  func main() {
13  	var c Contact
14  	json.Unmarshal([]byte(`{"email":"go@example.com"}`), &c)
15  	fmt.Printf("%+v\n", c)
16  }
出力: {email:}JSONは{"email":"go@example.com"}

JSONのキーとフィールド名の文字列としての一致だけでは不十分で、Go側は公開フィールドである必要があります。

直し方: emailEmail にします。

この問題を解いてみる →

パターン4

 1  package main
 2  
 3  import (
 4  	"encoding/json"
 5  	"fmt"
 6  )
 7  
 8  type Flag struct {
 9  	active  bool
        ^
10  }
11  
12  func main() {
13  	var f Flag
14  	json.Unmarshal([]byte(`{"active":true}`), &f)
15  	fmt.Printf("%+v\n", f)
16  }
出力: {active:false}JSONは{"active":true}

bool型でもゼロ値であるfalseのまま残るため、パースが成功したのか失敗したのか気づきにくいバグになります。

直し方: activeActive にします。

この問題を解いてみる →

パターン5

 1  package main
 2  
 3  import (
 4  	"encoding/json"
 5  	"fmt"
 6  )
 7  
 8  type Product struct {
 9  	price  float64
        ^
10  }
11  
12  func main() {
13  	var p Product
14  	json.Unmarshal([]byte(`{"price":9.99}`), &p)
15  	fmt.Printf("%+v\n", p)
16  }
出力: {price:0}JSONは{"price":9.99}

フィールド名を大文字始まりに変更するだけで、JSONのキーとは大文字小文字の違いがあっても自動的にマッチします。

直し方: pricePrice にします。

この問題を解いてみる →

よくある誤解

「フィールド名がJSONのキーと文字列として一致していれば、大文字小文字に関わらず値が入るはず」という思い込みは誤りです。encoding/jsonはリフレクションを使うため、パッケージ外からもアクセスできる大文字始まりの公開フィールドでなければ値を設定できません。

まとめ

json.Unmarshalで非公開(小文字始まり)フィールドが無視されるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)