ข้ามไปยังเนื้อหา

encoding/json

Package encoding/json แปลงค่า Go เป็น JSON text (marshaling) และแปลง JSON text กลับเป็นค่า Go (unmarshaling) function หลักสองตัวคือ:

data, err := json.Marshal(v) // Go value → []byte (JSON)
err = json.Unmarshal(data, &v) // []byte (JSON) → Go value

Marshal รับค่าใดก็ได้และคืน []byte Unmarshal เขียนลงในค่าที่ชี้โดย argument ที่สอง — ส่ง pointer เสมอเพื่อให้ function แก้ไขตัวแปรได้

Struct tag คือ raw-string literal บน field ของ struct ที่ให้ metadata Package encoding/json อ่าน tag json:"name" เพื่อควบคุมการ serialize:

type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Age int `json:"age"`
}

syntax ของ tag คือ `json:"<name>[,options]"` option ที่พบบ่อย:

Tagผล
json:"name"ใช้ name เป็น JSON key แทนชื่อ field
json:"name,omitempty"ละเว้น field นี้เมื่อเป็น false, 0, nil pointer/interface หรือ empty string/slice/map/array — ไม่รวม zero struct หรือ zero time.Time
json:"name,omitzero" (Go 1.24)ละเว้น field นี้เมื่อเป็น zero value ของ type นั้น รวมถึง struct และ time.Time — เรียก method IsZero() bool ถ้า type มี method นี้
json:"-"ละเว้น field นี้เสมอ

omitempty เช็กแค่ความ “ว่าง” ระดับภาษา (zero value ของ basic type หรือความยาวเป็น 0 สำหรับ slice/map) struct — แม้ทุก field ข้างในจะเป็น zero — ไม่ถือว่า “ว่าง” ตามกฎนี้ omitempty จึงไม่มีวันละเว้น struct time.Time เองก็เป็น struct ผลจึงเหมือนกัน: time.Time{} ที่เป็น zero ยัง marshal ออกมาเป็น "0001-01-01T00:00:00Z"

type Location struct {
City string
}
type Event struct {
Name string `json:"name"`
Location Location `json:"location,omitempty"` // struct — NOT omitted when zero
When time.Time `json:"when,omitempty"` // time.Time — NOT omitted when zero
}
e := Event{Name: "party"} // Location and When left at their zero values
data, _ := json.Marshal(e)
fmt.Println(string(data))
// {"name":"party","location":{"City":""},"when":"0001-01-01T00:00:00Z"}

omitzero (Go 1.24) แก้ปัญหานี้ตรงๆ: มันละเว้น field ทุกครั้งที่ค่าเท่ากับ zero value ของ type นั้น รวมถึง struct ด้วย และเรียก IsZero() บน field เมื่อ type นั้นนิยาม method ไว้ — ซึ่งเป็นเหตุผลที่มันถือว่า time.Time{} เป็นค่าว่างได้ถูกต้อง

type Event2 struct {
Name string `json:"name"`
Location Location `json:"location,omitzero"` // omits zero structs too
When time.Time `json:"when,omitzero"` // uses time.Time.IsZero()
}
e2 := Event2{Name: "party"}
data2, _ := json.Marshal(e2)
fmt.Println(string(data2))
// {"name":"party"}

Field ของ struct สามารถเป็น struct ได้เช่นกัน json.Marshal จะ recurse เข้าไปโดยอัตโนมัติ:

type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"`
}

JSON ที่ได้จะฝัง address object ไว้ภายใต้ key "address"

เมื่อยังไม่มี struct type ล่วงหน้า ให้ unmarshal ลงใน map[string]any แต่ละค่า JSON จะกลายเป็น Go counterpart ที่เป็นธรรมชาติ: string, float64, bool, []any, map[string]any หรือ nil

var m map[string]any
json.Unmarshal([]byte(`{"lang":"Go","version":1.22}`), &m)
fmt.Println(m["lang"]) // Go
fmt.Println(m["version"]) // 1.22 (float64)
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
Address Address `json:"address"`
}
func main() {
// Marshal a struct — Email is set, so it appears
u := User{
Name: "Alice",
Age: 30,
Address: Address{
City: "Bangkok",
Country: "Thailand",
},
}
data, err := json.Marshal(u)
if err != nil {
fmt.Println("marshal error:", err)
return
}
fmt.Println(string(data))
// omitempty: Email is empty, so it is omitted
u2 := User{Name: "Bob", Age: 25, Address: Address{City: "London", Country: "UK"}}
data2, _ := json.Marshal(u2)
fmt.Println(string(data2))
// Unmarshal back into a struct
var result User
err = json.Unmarshal(data, &result)
if err != nil {
fmt.Println("unmarshal error:", err)
return
}
fmt.Printf("Name: %s, City: %s\n", result.Name, result.Address.City)
// map[string]any — flexible unmarshaling
raw := `{"lang":"Go","version":1.22,"stable":true}`
var m map[string]any
_ = json.Unmarshal([]byte(raw), &m)
fmt.Println(m["lang"], m["version"], m["stable"])
}

field ที่ unexported (ขึ้นต้นด้วยตัวพิมพ์เล็ก) ไม่ได้แค่ถูกข้ามด้วย semantics ของ omitempty — encoding/json มองไม่เห็นมันเลยไม่ว่าจะผ่าน reflection หรือทางไหนก็ตาม ใส่ tag ยังไงก็ไม่ช่วย:

type Account struct {
Name string `json:"name"`
secret string // never marshaled, never populated by Unmarshal, no matter what
}

json.Marshal(Account{Name: "root", secret: "swordfish"}) จะได้ {"name":"root"} — field secret ไม่มีตัวตนเลยเท่าที่ package นี้เกี่ยวข้อง นี่คือสาเหตุที่พบบ่อยของความสับสน “ทำไม field ของฉันไม่ขึ้น” เมื่อโค้ดที่ AI เขียน copy ชื่อ field ตัวพิมพ์เล็กมาจากที่อื่นในโค้ดเบส

เมื่อไม่มี struct type เป้าหมาย Unmarshal/Decode จะแปลงตัวเลข JSON ทุกตัวเป็น float64 ของ Go — รวมถึงจำนวนเต็มด้วย float64 มี mantissa แค่ 53 bit ดังนั้นจำนวนเต็มที่มากกว่า 2^53 (9007199254740992) จะเสียความละเอียดเงียบๆ ทันทีที่กลายเป็น any:

var v any
json.Unmarshal([]byte(`{"n": 9007199254740993}`), &v)
// v["n"] is 9.007199254740992e+15 — the trailing 3 became a 2

json.Decoder.UseNumber() แก้ปัญหานี้: มัน decode ตัวเลข JSON เป็น json.Number ซึ่งเป็น string type ที่เก็บ text ต้นฉบับไว้ แล้วค่อยแปลงเป็นตัวเลขเองด้วย .Int64() หรือ .Float64() เมื่อต้องการ numeric type จริงๆ — field ID ที่เป็นเลขใหญ่คือกรณีคลาสสิกที่วิธีนี้ช่วยได้

json.Unmarshal(data, &v) ต้องการ payload ทั้งหมดในหน่วยความจำเป็น []byte json.NewDecoder(r).Decode(&v) อ่านตรงจาก io.Reader — ไม่ต้องมี buffer เต็มก่อน และรองรับสองอย่างที่ Unmarshal ไม่มี:

  • dec.DisallowUnknownFields() ปฏิเสธ JSON key ใดๆ ที่ไม่มี field ของ struct ตรงกัน แทนที่จะเพิกเฉยเงียบๆ มีประโยชน์ที่ขอบของ API ที่ field พิมพ์ผิดหรือไม่คาดคิดควรเป็น error ที่ชัดเจน
  • dec.More() ให้ decode stream ของค่าทีละตัว — เช่น element ของ JSON array — โดยไม่ต้องเก็บ array ทั้งหมดไว้ในหน่วยความจำ
dec := json.NewDecoder(r)
dec.Token() // consume the opening '['
for dec.More() {
var item Item
dec.Decode(&item) // one element at a time
}

กฎการ encode สามข้อที่พลาดได้ง่ายเมื่อเขียน API contract เอง:

  • []byte marshal เป็น JSON string แบบ base64 (RFC 4648) ไม่ใช่ JSON array ของตัวเลข
  • time.Time marshal เป็น string แบบ RFC 3339 โดย default (รูปแบบเวลาในตัวรูปแบบเดียวของ json.Marshal)
  • slice ที่เป็น nil marshal เป็น null; slice ว่างที่ไม่ใช่ nil ([]T{}) marshal เป็น [] endpoint GET /items ที่ลืม initialize slice ผลลัพธ์จะส่ง "items":null ให้ client ที่คาดหวัง "items":[] — เป็น bug ที่กระทบ API-compatibility จริงๆ ไม่ใช่แค่เรื่องความสวยงาม

type สามารถควบคุมการ encode JSON ของตัวเองได้โดย implement MarshalJSON() ([]byte, error) กับดักคือ: การเรียก json.Marshal บน receiver ข้างใน method นั้นจะเรียก MarshalJSON ซ้ำอีกครั้ง วนซ้ำไม่รู้จบ วิธีแก้คือ alias trick — นิยาม type ใหม่ที่มี field เดิมทุกตัวแต่ไม่มี method ของ type เดิมเลย แล้ว marshal type ใหม่นั้นแทน:

type Node struct {
Value int `json:"value"`
Children []Node `json:"children"`
}
func (n Node) MarshalJSON() ([]byte, error) {
type alias Node // same fields, no methods — breaks the recursive call
return json.Marshal(struct {
Total int `json:"total"`
alias
}{
Total: n.Value + len(n.Children),
alias: alias(n),
})
}

type alias Node สร้าง named type ใหม่ที่มี field layout ตรงกับ Node เป๊ะ แต่มี method set ว่างเปล่าใหม่ ดังนั้น json.Marshal(alias(n)) จึงไปใช้ default struct encoder แทนที่จะเรียก MarshalJSON ซ้ำอีกครั้ง

package main
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
)
// secret is unexported — encoding/json can never see it, tag or no tag
type Account struct {
Name string `json:"name"`
secret string
}
// Node uses a MarshalJSON with the "alias trick" to avoid infinite recursion:
// aliasing Node to a new type with the same fields drops the MarshalJSON method,
// so encoding it with the alias type calls the default struct encoder, not itself.
type Node struct {
Value int `json:"value"`
Children []Node `json:"children"`
}
func (n Node) MarshalJSON() ([]byte, error) {
type alias Node // same fields, no methods — breaks the recursive call
return json.Marshal(struct {
Total int `json:"total"`
alias
}{
Total: n.Value + len(n.Children),
alias: alias(n),
})
}
type Blob struct {
Raw []byte `json:"raw"`
When time.Time `json:"when"`
Tags []string `json:"tags"`
Empty []string `json:"empty"`
}
func main() {
// 1. unexported field is invisible to encoding/json
a := Account{Name: "root", secret: "swordfish"}
data, _ := json.Marshal(a)
fmt.Println("unexported dropped:", string(data))
// 2. decoding into any: numbers become float64, large ints lose precision
var any1 any
json.Unmarshal([]byte(`{"n": 9007199254740993}`), &any1)
m := any1.(map[string]any)
fmt.Printf("as any: %v (%T)\n", m["n"], m["n"])
// json.Number keeps the exact text instead
dec := json.NewDecoder(strings.NewReader(`{"n": 9007199254740993}`))
dec.UseNumber()
var any2 any
dec.Decode(&any2)
m2 := any2.(map[string]any)
num := m2["n"].(json.Number)
fmt.Printf("as json.Number: %v (%T) -> Int64: %v\n", num, num, func() string {
i, err := num.Int64()
return fmt.Sprintf("%d err=%v", i, err)
}())
// 3. DisallowUnknownFields rejects fields the struct doesn't declare
type Strict struct {
Name string `json:"name"`
}
var s Strict
dec2 := json.NewDecoder(strings.NewReader(`{"name":"Ada","extra":1}`))
dec2.DisallowUnknownFields()
err := dec2.Decode(&s)
fmt.Println("DisallowUnknownFields error:", err)
// 4. Decoder streaming with dec.More() over a JSON array, one element at a time
dec3 := json.NewDecoder(strings.NewReader(`[1,2,3]`))
dec3.Token() // consume the opening '['
var nums []int
for dec3.More() {
var n int
dec3.Decode(&n)
nums = append(nums, n)
}
fmt.Println("streamed with dec.More():", nums)
// 5. []byte marshals as base64, time.Time as RFC3339
b := Blob{
Raw: []byte("hi"),
When: time.Date(2026, 9, 13, 12, 0, 0, 0, time.UTC),
Tags: []string{"a"},
Empty: []string{},
}
var nilTags []string
b.Tags = nilTags // nil slice
data2, _ := json.Marshal(b)
fmt.Println("[]byte + time.Time:", string(data2))
// 6. custom MarshalJSON with the alias trick against infinite recursion
tree := Node{Value: 1, Children: []Node{{Value: 2}, {Value: 3}}}
data3, _ := json.Marshal(tree)
fmt.Println("custom MarshalJSON:", string(data3))
// 7. nil slice encodes as null, empty slice encodes as []
type Lists struct {
Nil []int `json:"nil"`
Empty []int `json:"empty"`
}
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(Lists{Nil: nil, Empty: []int{}})
fmt.Print("nil vs empty slice: ", buf.String())
}

output จริงจากโปรแกรมนี้บน Go 1.26.5:

unexported dropped: {"name":"root"}
as any: 9.007199254740992e+15 (float64)
as json.Number: 9007199254740993 (json.Number) -> Int64: 9007199254740993 err=<nil>
DisallowUnknownFields error: json: unknown field "extra"
streamed with dec.More(): [1 2 3]
[]byte + time.Time: {"raw":"aGk=","when":"2026-09-13T12:00:00Z","tags":null,"empty":[]}
custom MarshalJSON: {"total":3,"value":1,"children":[{"total":2,"value":2,"children":null},{"total":3,"value":3,"children":null}]}
nil vs empty slice: {"nil":null,"empty":[]}

สังเกตบรรทัดสุดท้ายของ children ใน output ของ tree: {Value: 3} ไม่มีการตั้งค่า Children จึงเป็น nil slice และ marshal เป็น null — กฎเดียวกับข้อ 7 ที่ปรากฏขึ้นในผลลัพธ์ของ MarshalJSON แบบ custom ด้วย

Go 1.27 มาพร้อม encoding/json/v2 และ package ระดับล่างคู่กัน encoding/json/jsontext ในสถานะ stable ทุกอย่างข้างบนยังใช้ได้: API ของ v1 encoding/json ไม่เปลี่ยน แต่ตอนนี้ สร้างอยู่บน v2 โปรแกรมเดิมจึงได้ decoder ที่เร็วขึ้นฟรี ๆ (ปิดได้ด้วย GOEXPERIMENT=nojsonv2 ถ้าเจอพฤติกรรมต่างกัน)

ให้ import v2 เมื่อต้องการ API และ default ที่เข้มงวดกว่า:

package main
import (
"encoding/json/v2"
"fmt"
"os"
)
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Age int `json:"age"`
}
func main() {
u := User{Name: "Ada", Age: 36}
data, err := json.Marshal(u) // same shape as v1 …
fmt.Println(string(data), err)
var back User
err = json.Unmarshal(data, &back)
fmt.Printf("%+v %v\n", back, err)
data, _ = json.Marshal(map[string]int{"b": 2, "a": 1}, json.Deterministic(true)) // … plus variadic Options
fmt.Println(string(data))
_ = json.MarshalWrite(os.Stdout, u) // straight to an io.Writer
fmt.Println()
// stricter defaults: duplicate keys are rejected
err = json.Unmarshal([]byte(`{"name":"A","name":"B"}`), &back)
fmt.Println("duplicate key:", err)
}

json.UnmarshalRead(r, &u) คือด้านกลับของ MarshalWrite — decode จาก io.Reader อย่าง resp.Body ได้ตรง ๆ

สิ่งที่ต่างจาก v1:

เรื่องv1 encoding/jsonv2 encoding/json/v2
UTF-8 ผิดรูปใน stringแทนที่เงียบ ๆปฏิเสธ
key ซ้ำใน objectตัวหลังชนะปฏิเสธ
การจับคู่ชื่อ fieldไม่สนตัวพิมพ์ต้องตรงเป๊ะโดย default (json.MatchCaseInsensitiveNames(true) เพื่อผ่อน)
Optionssetter ระดับ Encoderค่า Options ต่อการเรียก ประกอบกันได้
ฝัง field ของ structanonymous fieldtag json:",embed" (ชื่อเก่า inline ถูกเปลี่ยน)
ความเร็ว Unmarshalbaselineเร็วขึ้นอย่างมีนัยสำคัญ Marshal เท่าเดิม

การจับคู่ชื่อ field แบบเข้มงวดของ v2 พลาดได้ง่ายแบบเงียบ ๆ เมื่อ import encoding/json/v2 แล้ว unmarshal key ตัวพิมพ์เล็กลง field ที่ไม่มี tag จะคืน error เป็น nil และปล่อย field ว่างไว้:

type User struct {
Name string // no tag
}
var u User
err := json.Unmarshal([]byte(`{"name":"Ada"}`), &u)
// v1: err == nil, u.Name == "Ada" — matched case-insensitively
// v2: err == nil, u.Name == "" — no exact match, field left alone

v1 จับคู่ name กับ Name แบบไม่สนตัวพิมพ์ แต่ v2 ต้องตรงตัวพิมพ์ (หรือมี tag json:"name") เป็นค่าเริ่มต้น ดังนั้นเวลาย้าย code มาใช้ v2 ให้ใส่ tag ให้ชัดเจนเสมอ

jsontext คือชั้น streaming ที่อยู่ข้างใต้: jsontext.Decoder และ jsontext.Encoder เดินผ่าน document เป็น Token และ Value พร้อมตรวจ syntax ไปด้วย เหมาะกับ payload ขนาดใหญ่มากหรือการเขียน JSON ใหม่โดยไม่ decode เป็นค่า Go

dec := jsontext.NewDecoder(r)
for {
tok, err := dec.ReadToken()
if err == io.EOF { break }
// inspect tok.Kind(): '{', '}', '"', '0', …
}

หลักคิดสำหรับปี 2026: โค้ดเดิมและ wire format ที่ยอมรับ input หลวม ๆ ใช้ v1 ต่อไป service ใหม่เริ่มที่ v2 เพื่อให้ JSON ผิดรูป fail เสียงดังตั้งแต่ขอบระบบ

flowchart LR
  GoValue["Go struct"] -- "json.Marshal" --> Bytes["[]byte JSON"]
  Bytes -- "json.Unmarshal" --> GoValue2["Go struct"]
  GoValue -- "MarshalJSON()" --> Custom["custom encoding"]
  Bytes -- "json.NewDecoder(r)" --> Dec["Decoder"]
  Dec -- "Decode() / More()" --> Stream["one value at a time"]
  Bytes -- "into map[string]any" --> Any["float64 / json.Number"]
Struct tag กับ MarshalJSON ควบคุมทิศทางเดียว; Decoder แบบ streaming ไม่ต้องโหลด payload ทั้งหมด
สิ่งที่ได้ประโยชน์ต้นทุน
json.Marshal/Unmarshalง่าย, ไม่ต้อง schemareflect-based — ช้ากว่า code-gen 2-5x
struct tags (json:“name”)control field names, omitemptyruntime-only — ไม่มี compile-time check ว่า tag ถูกต้อง
json.Decoder (streaming)decode large/stream JSON โดยไม่ load ทั้งหมดใน memoryAPI ซับซ้อนกว่า Unmarshal
omitemptyไม่ส่ง zero value ใน JSON0, false, "" ถือเป็น empty — อาจ surprise
  • json.Marshal/Unmarshal thread-safe — ใช่ — แต่ json.Decoder/Encoder แต่ละ instance ไม่ thread-safe อย่าแชร์ข้าม goroutine
  • unexported field ถูก marshal — ไม่ — unexported field ถูก ignore โดย encoding/json ไม่ว่าจะมี tag หรือไม่
  • omitempty ทำงานกับ pointer เหมือน value — omitempty บน *T: nil → omit, non-nil → include แม้ว่า value ข้างใน pointer เป็น zero
  • field ที่เป็น any ใน struct marshal เป็น null เสมอ — marshal ตาม concrete type ที่ store อยู่ใน interface ตอน runtime

💡 ตัวอย่างจากของจริง

Kubernetes API: ทุก Kubernetes resource (Pod, Deployment, Service) marshal/unmarshal ผ่าน encoding/json — struct tags ควบคุม JSON key ทั้งหมด

Go REST API: json.NewDecoder(r.Body).Decode(&req) คือ pattern มาตรฐานที่ทุก Go API server ใช้สำหรับ parse request body

โค้ด encoding/json ที่ AI เขียนมักดูครบถ้วนแต่แอบสมมติ schema ที่ไม่เคยตรวจสอบจริง เช็กสิ่งเหล่านี้ก่อนเชื่อ:

  • field ที่ต้องปรากฏใน JSON ขึ้นต้นด้วยตัวพิมพ์ใหญ่หรือไม่? field ตัวพิมพ์เล็กจะไม่ serialize เลยเงียบๆ โดยไม่มี error จาก Marshal หรือ Unmarshal
  • มีการ type-assert ค่า any ที่ decode มาตรงๆ เป็น int, int64 หรือ numeric type อื่นหรือไม่? ตัวเลข JSON ทุกตัว decode เป็น float64 เว้นแต่ target เป็น struct field ที่เจาะจงหรือตั้ง UseNumber() ไว้
  • tag omitempty อยู่บน field ที่เป็น struct หรือ time.Time แล้วสมมติว่ามันจะถูกละเว้นเมื่อเป็น zero หรือไม่? มันจะไม่ถูกละเว้น — มีแค่ omitzero (Go 1.24) เท่านั้นที่ทำแบบนั้น
  • field ผลลัพธ์ที่เป็น []T ถูกปล่อยเป็น nil เมื่อผลลัพธ์ว่างหรือไม่ ในขณะที่ผู้ใช้ API คาดหวัง [] ไม่ใช่ null?
  • MarshalJSON แบบ custom เรียก json.Marshal บน n (receiver) ตรงๆ แทนที่จะเป็น alias type หรือไม่ — นี่คือ infinite recursion ที่จะโผล่ตอน runtime เท่านั้น?
  • โค้ดที่ต้องปฏิเสธ field ที่ไม่คาดคิดใช้ Unmarshal ธรรมดาแทนที่จะใช้ Decoder พร้อม DisallowUnknownFields() หรือไม่?

🐛 หา bug ในโค้ดนี้

func parseCount(body []byte) (int, error) {
var v map[string]any
if err := json.Unmarshal(body, &v); err != nil {
return 0, err
}
count := v["count"].(int) // ???
return count, nil
}
เฉลย

json.Unmarshal ลงใน map[string]any จะแปลงตัวเลข JSON ทุกตัวเป็น float64 ไม่เคยเป็น int — ดังนั้น v["count"].(int) จะ panic กับ input ที่ถูกต้องทุกตัว:

recovered: interface conversion: interface {} is float64, not int

แก้ได้ด้วย type assertion ที่ถูกต้องพร้อมแปลง type อย่างชัดเจน หรือหลีกเลี่ยง map ทั้งหมดโดย decode ลงใน struct ที่มี field Count int ที่กำหนด type ไว้ ให้ encoding/json แปลงตัวเลขให้เอง:

count, ok := v["count"].(float64)
if !ok {
return 0, fmt.Errorf("count: not a number")
}
return int(count), nil
json.Unmarshal ต้องการอะไรเป็น argument ที่สอง?
Field ของ struct ที่มี tag json:"email,omitempty" และมีค่าเป็น empty string จะ:
โค้ดนี้พิมพ์อะไร?
var v any
json.Unmarshal([]byte(`{"n": 9007199254740993}`), &v)
m := v.(map[string]any)
fmt.Println(m["n"])
Field ของ struct ชื่อ userID (u พิมพ์เล็ก) ที่มี tag json:"user_id" จะ:
โค้ดนี้พิมพ์อะไร?
type Result struct {
    Items []string `json:"items"`
}
r := Result{}
data, _ := json.Marshal(r)
fmt.Println(string(data))
ทำไม method MarshalJSON แบบ custom ต้องใช้ type alias T ก่อนเรียก json.Marshal บน field ของ receiver?