Json là một trong những định dạng serialization phổ biến nhất. Nó giúp chúng ta dễ dàng đọc được, ngắn gọn hợp lý, và dễ dàng được phân tích cú pháp bởi bất kỳ ứng dụng web nào sử dụng Javascript. Go là một ngôn ngữ lập trình hiện đại có fist-class hỗ trợ cho việc Json Serialization trong thư viện chuẩn của nó.
Json Package
Go hỗ trợ một vài định dạng serialization trong package mã hóa của thư viện chuẩn của nó. Một trong những định dạng này phổ biến nhất là Json. Bạn có thể serialize giá trị sử dụng hàm Marshal()
bên trong một slice của byte. Bạn có thể deserialize một slice của byte thành các giá trị của Go sử dụng hàm Unmarshal()
.
Những chỉ định sau là tương đương trong bài viết này:
- Serialization/Encoding/Marshalling
- Deserialization/Decoding/Unmarshalling
Marshal
Hàm Marshal()
có thể nhận bất cứ thứ gì, trong Go có nghĩa là một interface trống và trả về một slice của byte hoặc lỗi. Đây là cú pháp của hàm
func Marshal(v interface{}) ([]byte, error)
Nếu hàm Marshal()
lỗi khi serialize giá trị đầu vào, nó sẽ trả về môt lỗi non-nil
.
Marshal()
có một vài giới hạn nghiêm ngặt:
- Key của Map phải là string
- Giá trị của Map phải là kiểu có khả năng serialization bằng json package
- Các kiểu sau là không được hỗ trợ: Channel, complex và hàm
- Các cấu trúc dữ liệu chu kỳ không được hỗ trợ.
- Các con trỏ là được mã hóa (và sau đó được giải mã) dưới dạng các giá trị mà chúng đang trỏ đến (hoặc ‘null’ nếu con trỏ là nil)
Unmarshal
Hàm Unmarshal()
nhận một slice của byte đại diện cho một Json hợp lệ và một interface đích, mà thường là một con trỏ đến một struct hoặc kiểu cơ bản. Để giải mã ngược (deserialize) Json thành interface theo cách chung. Nếu việc giải mã ngược không thành công, nó sẽ trả về một lỗi theo cú pháp sau:
func Unmarshal(data []byte, v interface{}) error
Serialize kiểu đơn giản
Bạn có thể serialize các kiểu đơn giản sử dụng Json package, kết quả sẽ không phải là một object json đầy đủ, mà là một string đơn giản. Ví dụ chúng ta có một số int 5 được serialize thành một mảng byte [53], tương ứng là string “5”
// Serialize int var x = 5 bytes, err := json.Marshal(x) if err != nil { fmt.Println("Can't serislize", x) } fmt.Printf("%v => %v, '%v'\n", x, bytes, string(bytes)) // Deserialize int var r int err = json.Unmarshal(bytes, &r) if err != nil { fmt.Println("Can't deserislize", bytes) } fmt.Printf("%v => %v\n", bytes, r)
Kết quả
5 => [53], '5' [53] => 5
Nếu bạn thử serialize những kiểu không được hỗ trợ như là các hàm thì, bạn sẽ nhận một lỗi
// Trying to serialize a function foo := func() { fmt.Println("foo() here") } bytes, err = json.Marshal(foo) if err != nil { fmt.Println(err) }
Kết quả
json: unsupported type: func()
Serialize dữ liệu bất kỳ với Map
Điểm mạnh của Json là nó có thể diễn tả rất tốt dữ liệu cấp bậc bất kỳ. Package Json hỗ trợ và tận dụng kiểu interface trống (interface {}) để diễn tả cấp bậc cho Json bất kỳ. Sau đây là một ví dụ về deserialize và sau đó là serialize cây nhị phân mà mỗi node có một giá trị int và 2 nhánh, trái và phải, mà có thể chứa các node khác hoặc null.
Json null là tương đương với giá trị nil
trong Go. Như bạn đã thấy kết quả, hàm json.Unmarshal()
chuyển đổi thành công JSON blob thành struct dữ liệu Go bao gồm các map lồng nhau của các interface và lưu trữ kiểu dữ liệu là int. Hàm json.Marshal()
serialize thành công kết quả các object lồng nhau thành một Json tương ứng
// Json được lồng vào nhau dd := ` { "value": 3, "left": { "value": 1, "left": null, "right": { "value": 2, "left": null, "right": null } }, "right": { "value": 4, "left": null, "right": null } }` var obj interface{} err = json.Unmarshal([]byte(dd), &obj) if err != nil { fmt.Println(err) } else { fmt.Println("--------\n", obj) } data, err = json.Marshal(obj) if err != nil { fmt.Println(err) } else { fmt.Println("--------\n", string(data)) }
Kết quả
-------- map[right:map[value:4 left:<nil> right:<nil>] value:3 left:map[left:<nil> right:map[value:2 left:<nil> right:<nil>] value:1]] -------- {"left":{ "left":null, "right":{"left":null,"right":null,"value":2}, "value":1}, "right":{"left":null, "right":null, "value":4}, "value":3}
Để duyệt qua các map của interface chung, chúng ta cần sử dụng xác nhận kiểu. Ví dụ
func dump(obj interface{}) error { if obj == nil { fmt.Println("nil") return nil } switch obj.(type) { case bool: fmt.Println(obj.(bool)) case int: fmt.Println(obj.(int)) case float64: fmt.Println(obj.(float64)) case string: fmt.Println(obj.(string)) case map[string]interface{}: for k, v := range(obj.(map[string]interface{})) { fmt.Printf("%s: ", k) err := dump(v) if err != nil { return err } } default: return errors.New( fmt.Sprintf("Unsupported type: %v", obj)) } return nil }
Serialize dữ liệu có cấu trúc
Làm việc với dữ liệu có cấu trúc thường là lựa chọn tốt hơn. Go cung cấp hoàn toàn hỗ trợ cho serialize Json cho hoặc từ struct thông qua các thẻ struct của nó. Chúng ta hãy tạo một struct tương ứng với cây Json và hàm Dump()
in ra
type Tree struct { value int left *Tree right *Tree } func (t *Tree) Dump(indent string) { fmt.Println(indent + "value:", t.value) fmt.Print(indent + "left: ") if t.left == nil { fmt.Println(nil) } else { fmt.Println() t.left.Dump(indent + " ") } fmt.Print(indent + "right: ") if t.right == nil { fmt.Println(nil) } else { fmt.Println() t.right.Dump(indent + " ") } }
Đoạn code bên trên hoạt động không? Không hoàn toàn. Không có lỗi xảy ra, nhưng object tree
không được điền bằng Json
jsonTree := ` { "value": 3, "left": { "value": 1, "left": null, "right": { "value": 2, "left": null, "right": null } }, "right": { "value": 4, "left": null, "right": null } }` var tree Tree err = json.Unmarshal([]byte(dd), &tree) if err != nil { fmt.Printf("- Can't deserislize tree, error: %v\n", err) } else { tree.Dump("") }
Kết qủa
value: 0 left: <nil> right: <nil>
Vấn đề ở đây, các trường Tree
là private. Json serialization chỉ hoạt động trên các trường public. Nên chúng ta có thể tạo các trường struct
public. Json package đủ thông minh để chuyển đổi hoàn toàn các key viết thường value
, left
, và right
thành tên các trường viết hoa tương ứng.
type Tree struct { Value int `json:"value"` Left *Tree `json:"left"` Right *Tree `json:"right"` }
Kết quả
value: 3 left: value: 1 left: <nil> right: value: 2 left: <nil> right: <nil> right: value: 4 left: <nil> right: <nil>
Package Json sẽ âm thầm bỏ qua các trường không được map, cũng như các trường private trong struct
. Nhưng thỉnh thoảng bạn muốn map các key cụ thể trong Json vào một trường với một tên khác trong struct
. Bạn có thể sử dụng các thẻ struct. Ví dụ giả sử sử chúng ta thêm một trường khác tên là label
cho Json, chúng ta cần map nó cho một trường gọi là Tag
trong struct.
type Tree struct { Value int Tag string `json:"label"` Left *Tree Right *Tree } func (t *Tree) Dump(indent string) { fmt.Println(indent + "value:", t.Value) if t.Tag != "" { fmt.Println(indent + "tag:", t.Tag) } fmt.Print(indent + "left: ") if t.Left == nil { fmt.Println(nil) } else { fmt.Println() t.Left.Dump(indent + " ") } fmt.Print(indent + "right: ") if t.Right == nil { fmt.Println(nil) } else { fmt.Println() t.Right.Dump(indent + " ") } }
Đây là một Json mới với node gốc (root) của cây (tree) dán nhãn là root
, serialize tên riêng thành trường Tag
và in kết quả
dd := ` { "label": "root", "value": 3, "left": { "value": 1, "left": null, "right": { "value": 2, "left": null, "right": null } }, "right": { "value": 4, "left": null, "right": null } }` var tree Tree err = json.Unmarshal([]byte(dd), &tree) if err != nil { fmt.Printf("- Can't deserislize tree, error: %v\n", err) } else { tree.Dump("") }
Kết quả
value: 3 tag: root left: value: 1 left: <nil> right: value: 2 left: <nil> right: <nil> right: value: 4 left: <nil> right: <nil>
Viết tuỳ chỉnh Marshaller
Bạn sẽ thường xuyên mong muốn serialize các object mà không phải tuân theo các quy tắc nghiêm ngặt của hàm Marshal()
. Ví dụ bạn có lẽ muốn serialize một map với các key int. Trong trường hợp này, bạn có thể viết một marshaller
/unmarshaller
tuỳ chỉnh bằng cách thực thi các interface Marshaler
và Unmarshaler
Chú ý lỗi chính tả: Trong Go, quy ước để đặt tên cho một interface với một phương thức đơn bằng cách thêm hậu tố “er” cho tên phương thức. Nên mặc dù cách đánh vần phổ biến hơn là Marshaller
(với L nhân đôi), tên của interface chỉ là Marshaler
( L đơn).
Đây là các interface Marshaler và Unmarshaler
type Marshaler interface { MarshalJSON() ([]byte, error) } type Unmarshaler interface { UnmarshalJSON([]byte) error }
Bạn phải tạo một kiểu khi thực hiện tuỳ chỉnh serialization, thậm trí nếu bạn muốn tuỳ chỉnh các kiểu được tính hợp sẵn, hay các thành phần được tích hợp bên trong như là map[int]string
. Ở đây chúng ta định nghĩa một kiểu tên là IntStringMap
và thực thi các interface Marshaler
và Unmarshaler
cho kiểu này.
Phương thức MarshalJSON()
tạo một map[string]string
, chuyển đỗi mỗi key int của nó thành một string, và serialize map với các key string sử dụng hàm json.Marshal()
tiêu chuẩn.
type IntStringMap map[int]string func (m *IntStringMap) MarshalJSON() ([]byte, error) { ss := map[string]string{} for k, v := range *m { i := strconv.Itoa(k) ss[i] = v } return json.Marshal(ss) }
Phương thức UnmarshalJSON()
thực hiện ngược lại. Nó deserialize mảng byte dữ liệu thành map[string]string
và sau đó chuyển đổi mỗi key string thành một int
func (m *IntStringMap) UnmarshalJSON(data []byte ) error { ss := map[string]string{} err := json.Unmarshal(data, &ss) if err != nil { return err } for k, v := range ss { i, err := strconv.Atoi(k) if err != nil { return err } (*m)[i] = v } return nil }
Sử dụng hàm cho chuong trình như sau
m := IntStringMap{4: "four", 5: "five"} data, err := m.MarshalJSON() if err != nil { fmt.Println(err) } fmt.Println("IntStringMap to JSON: ", string(data)) m = IntStringMap{} jsonString := []byte("{\"1\": \"one\", \"2\": \"two\"}") m.UnmarshalJSON(jsonString) fmt.Printf("IntStringMap from JSON: %v\n", m) fmt.Println("m[1]:", m[1], "m[2]:", m[2])
Kết quả
IntStringMap to JSON: {"4":"four","5":"five"} IntStringMap from JSON: map[2:two 1:one] m[1]: one m[2]: two
Serialize Enum
Go Enum có thể rất phức tạp để serialize. Tạo các const Zero và One là tương đương với các int 0 và 1.
type EnumType int const ( Zero EnumType = iota One )
Khi bạn nghĩ nó là một int và bạn sẽ không thể serialize nó trực tiếp. Bạn phải viết một tuỳ chỉnh marshaler/unmarshaler.MarshalJSON()
và UnmarshalJSON()
sau đây sẽ serialize hoặc deserialize các hằng số Zero và One đến hoặc từ string Zero và One.
func (e *EnumType) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { return err } value, ok := map[string]EnumType{"Zero": Zero, "One": One}[s] if !ok { return errors.New("Invalid EnumType value") } *e = value return nil } func (e *EnumType) MarshalJSON() ([]byte, error) { value, ok := map[EnumType]string{Zero: "Zero", One:"One"}[*e] if !ok { return nil, errors.New("Invalid EnumType value") } return json.Marshal(value) }
Tiếp theo, hãy thử nhúng EnumType
vào trong một struct
và serialize nó. Hàm main tạo một EnumContainer
và khởi tạo nó với tên Uno
và một giá trị của One
hằng số enum
, bằng 1.
type EnumContainer struct { Name string Value EnumType } func main() { x := One ec := EnumContainer{ "Uno", x, } s, err := json.Marshal(ec) if err != nil { fmt.Printf("fail!") } var ec2 EnumContainer err = json.Unmarshal(s, &ec2) fmt.Println(ec2.Name, ":", ec2.Value) }
Kết quả
Uno : 0
Kết quả mong muốn là Uno : 1
nhưng thay vào đó là Uno : 0
. Không có bug trong marshal/unmarshal code. Hoá ra bạn không thể gắn các enum bằng giá trị nếu bạn muốn serialize chúng. Bạn phải gắn vào một con trỏ cho enum. Dưới đây là đoạn code chỉnh sửa để nó chạy như mong muốn
type EnumContainer struct { Name string Value *EnumType } func main() { x := One ec := EnumContainer{ "Uno", &x, } s, err := json.Marshal(ec) if err != nil { fmt.Printf("fail!") } var ec2 EnumContainer err = json.Unmarshal(s, &ec2) fmt.Println(ec2.Name, ":", *ec2.Value) }
Kết quả
Uno : 1