web-dev-qa-db-ja.com

GoでカスタムMarshalJSON()が呼び出されることはありません

MarshalJSONUnmarshalJSONのカスタムバージョンを作成しました。私のUnmarshalJSONは思い通りに呼び出されますが、MarshalJSONを使用できません。これが私の問題を要約したコードです:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "os"
)

type myStruct struct {
    Data string `json:"data"`
}

func (s *myStruct) MarshalJSON() ([]byte, error) {
    return []byte(`{"data":"charlie"}`), nil
}

func (s *myStruct) UnmarshalJSON(b []byte) error {
    // Insert the string directly into the Data member
    return json.Unmarshal(b, &s.Data)
}

func main() {
    // Create a struct with initial content "alpha"
    ms := myStruct{"alpha"}

    // Replace content with "bravo" using custom UnmarshalJSON() (SUCCESSFUL)
    if err := json.NewDecoder(bytes.NewBufferString(`"bravo"`)).Decode(&ms); err != nil {
        log.Fatal(err)
    }

    // Use custom MarshalJSON() to get "charlie" back (UNSUCCESSFUL)
    if err := json.NewEncoder(os.Stdout).Encode(ms); err != nil {
        log.Fatal(err)
    }

    // Trying another method (UNSUCCESSFUL)
    if ret, err := json.Marshal(ms); err != nil {
        log.Fatal(err)
    } else {
        fmt.Println(string(ret))
    }

    // Verify that the Marshaler interface is correctly implemented
    var marsh json.Marshaler
    marsh = &ms
    ret, _ := marsh.MarshalJSON()
    fmt.Println(string(ret)) // Prints "charlie"
}

つまり、プログラムはstructを2つの方法で「自動的に」エンコードし、最後にMarshalJSONを手動で呼び出します。私が欲しい応答は"charlie"。コードを実行すると、次の出力が生成されます。

{"data":"bravo"}
{"data":"bravo"}
{"data":"charlie"}

Go Playgroundでお試しください: http://play.golang.org/p/SJ05S8rAYN

30

コードのこの部分では、msinterface{}変数にコピーされます。

// Trying another method (UNSUCCESSFUL)
if ret, err := json.Marshal(ms); err != nil {

問題は、MarshalJSONmyStructの-​​ method set にないため(json.Marshalerの場合のみ)、この変数が*myStructインターフェースを実装しないことです。

修正は、(a)MarshalJSONメソッドに非ポインターレシーバーを取得させる(つまり、構造体のコピーを取得することを意味します:構造体のコピーは、サイズが大きい場合はコストがかかる可能性があります)、または(b)ポインターをマーシャリングします。構造体に(Kavuがコメントで述べたように)。

この動作の理由は、Goではインターフェイス変数内に格納されている値へのポインターを取得できないため、アクセスするたびに値のコピーを作成する必要があるためです。言語には、ポインターレシーバーを使用してメソッドにアクセスする方法としてms.MarshalJSON()(&ms).MarshalJSON()に変換する構文シュガーがありますが、これは、インターフェイス変数に格納されている値に対しては実行できません。このため、メソッドはそのメソッドセットに含まれているとは見なされません。

37