2019年8月30日金曜日

json: cannot unmarshal object into Go struct field Profile.error of type error

概要

error タイプは json.Marshal すると {} に変換されます
このまま json.Unmarshal すると {} は error タイプでないと言われてエラーになります
しかし JSON でエラーを管理したい場合があります
そんな場合はエラーメッセージだけを JSON で管理するようにしましょう

環境

  • macOS 10.14.6
  • go 1.11.5

エラーが発生するコード

package main

import (
    "encoding/json"
    "errors"
    "fmt"
)

type Profile struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Err  error  `json:"error"`
}

func main() {
    p := Profile{
        Name: "hawksnowlog",
        Age:  99,
        Err:  errors.New("test"),
    }
    j, err := json.Marshal(p)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(j))

    p2 := Profile{}
    err = json.Unmarshal(j, &p2)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(p2.Name)
    fmt.Println(p2.Age)
    fmt.Println(p2.Err)
}

=> {"name":"hawksnowlog","age":99,"error":{}}

素直に実行するとこんな感じで JSON が表示されそのあとパースエラーが表示されます

対応したコード

今回は ErrMsg というフィールドを構造体に一つ追加しそのフィールドを JSON 用のフィールドとして使います
そして MarshalJSONUnmarshalJSON を実装することで JSON <-> struct の相互変換時にエラーの情報を復元します

また json:"-" をフィールドに追加することでそのフィールドを JSON に含めないようにします

package main

import (
    "encoding/json"
    "errors"
    "fmt"
)

type Profile struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Err    error  `json:"-"`
    ErrMsg string `json:"error"`
}

func (p *Profile) UnmarshalJSON(b []byte) error {
    type Alias Profile
    p2 := Alias{}
    err := json.Unmarshal(b, &p2)
    if err != nil {
        return err
    }
    p.Name = p2.Name
    p.Age = p2.Age
    p.Err = errors.New(p2.ErrMsg)
    return nil
}

func (p Profile) MarshalJSON() ([]byte, error) {
    type Alias Profile
    p2 := Alias{
        Name:   p.Name,
        Age:    p.Age,
        Err:    p.Err,
        ErrMsg: p.Err.Error(),
    }
    ret, err := json.Marshal(p2)
    if err != nil {
        return nil, err
    }
    return ret, nil
}

func main() {
    p := Profile{
        Name: "hawksnowlog",
        Age:  99,
        Err:  errors.New("test"),
    }
    j, err := json.Marshal(p)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(j))

    p2 := Profile{}
    err = json.Unmarshal(j, &p2)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(p2.Name)
    fmt.Println(p2.Age)
    fmt.Println(p2.Err)
}

=> {"name":"hawksnowlog","age":99,"error":"test"}

となり構造体もちゃんと復元されます
また type をエイリアスしてあげることも大事でこれをしないと json.Marshaljson.Unmarshal をコールしたときに無限ループになるので注意しましょう

参考サイト

0 件のコメント:

コメントを投稿