Go วิธี return errorใน json.Unmarshaler

เวลาเราสร้าง type ขึ้นมาใหม่ แล้วจะ implement interface json.Unmarshaler เราจะ return error ยังไง ?

Photo by Erik-Jan Leusink on Unsplash (รูปไม่เกี่ยวกับบทความ เอามาใส่ไว้เฉย ๆ)

ลองดูตัวอย่างนี้

type Status int
const (
_ Status = iota
Active
Ban
)
func (s *Status) UnmarshalJSON(b []byte) error {
var p string
err := json.Unmarshal(b, &p)
if err != nil {
return err
}
    switch p {
case "active":
*s = Active
case "ban":
*s = Ban
default:
return fmt.Errorf("unknown status '%s'", p)
}
return nil
}

เวลาเราจะเอา Status ไปใช้ ใน struct เช่น

type User struct {
Username string `json:"username"`
Status Status `json:"status"`
}

แล้วเราลอง unmarshal ด้วยค่าที่นอกเหนือจากค่าที่รับดู

func main() {
a := `{"username":"test", "status": "invalid"}`
var u User
err := json.Unmarshal([]byte(a), &u)
if err != nil {
fmt.Println(err)
}
}

จะได้ error เป็น

unknown status 'invalid'

แน่นอนว่าตอนนี้ใน User เรามี Status แค่ field เดียว แต่ถ้ามีมากกว่า 1 field หล่ะ ?

type User struct {
Username string `json:"username"`
Status1 Status `json:"status1"`
Status2 Status `json:"status2"`
}

และลอง unmarshal ด้วยค่า

{"username":"test", "status1": "active", "status2": "invalid"}

แน่นอนว่าเราจะได้ error

unknown status 'invalid'

คำถามคือ error นี้มาจาก field ไหน ?


วิธีที่ทำให้เรารู้มี 2 ขั้นตอนคือ

  1. return json.UnmarshalTypeError แทน
func (s *Status) UnmarshalJSON(b []byte) error {
var p string
err := json.Unmarshal(b, &p)
if err != nil {
return err
}
    switch p {
case "active":
*s = Active
case "ban":
*s = Ban
default:
return &json.UnmarshalTypeError{
Type: reflect.TypeOf(Status(0)),
Value: string(b),
}
}
return nil
}

ตอนนี้เราก็จะได้ error แบบนี้

json: cannot unmarshal "invalid" into Go value of type main.Status

2. รอ Go 1.12 ออก 😳

แต่ถ้าอยากลองแบบเร็ว ๆ ให้เราเข้าไปแก้ไฟล์
$GOROOT/src/encoding/json/decode.go 
บรรทัดที่ 182 จาก

180:   err := d.value(rv)
181: if err != nil {
182: return err
183: }
184: return d.savedError

เป็น

180:   err := d.value(rv)
181: if err != nil {
182: return d.addErrorContext(err)
183: }
184: return d.savedError

หลังจากนั้นพอเรารันดู จะได้ error เป็น

json: cannot unmarshal "invalid" into Go struct field User.status2 of type main.Status

เราสามารถดึงชื่อ field ออกมาจาก error ได้

err := json.Unmarshal([]byte(a), &u)
if err != nil {
switch err := err.(type) {
case *json.UnmarshalTypeError:
fmt.Printf("field: %s\nstruct: %s\ntype: %s\nvalue: %s\n", err.Field, err.Struct, err.Type, err.Value)
default:
fmt.Println(err)
}
}

จะได้

field: status2
struct: User
type: main.Status
value: "invalid"

เท่านี้เราก็รู้แล้วว่า error มาจาก field ไหน เย่~~~!! 😆