Indonesian (Bahasa Indonesia) translation by Dzu Al-Faqqar (you can also view the original English article)
Ikhtisar
JSON adalah salah satu format serialisasi yang paling populer. Ini dapat dibaca manusia, ringkas, dan dapat diurai dengan mudah oleh aplikasi web apa pun yang menggunakan JavaScript. Go sebagai bahasa pemrograman modern memiliki dukungan kelas satu untuk serialisasi JSON di perpustakaan standarnya.
Tetapi ada beberapa sudut dan celah. Dalam tutorial ini Anda akan belajar bagaimana membuat serialisasi dan deserialize secara efektif serta data terstruktur ke / dari JSON. Anda juga akan belajar bagaimana menghadapi skenario tingkat lanjut seperti serialisasi enum.
Paket json
Go mendukung beberapa format serialisasi dalam paket enkode dari perpustakaan standarnya. Salah satunya adalah format JSON yang populer. Anda membuat serialisasi nilai Golang menggunakan fungsi Marshal () menjadi irisan byte. Anda deserialize sepotong byte ke nilai Golang yang menggunakan fungsi Unmarshal(). Ini yang sederhana. Persyaratan berikut setara dalam konteks artikel ini:
- Serialisasi/Encoding/luarannya
- Deserialization/Decoding/Unmarshalling
Saya lebih suka serialisasi karena itu mencerminkan fakta bahwa Anda mengubah struktur hirarkis berpotensi data ke dan dari aliran byte.
Marsekal
Fungsi Marshal() yang dapat mengambil apa pun, yang berarti antarmuka kosong dalam pergi dan kembali sepotong byte dan kesalahan. Berikut adalah tanda tangan:
func Marsekal (v antarmuka {}) (byte [], kesalahan)
Jika Marshal() gagal untuk cerita bersambung nilai input, itu akan kembali kesalahan bebas-nol. Marshal() memiliki beberapa keterbatasan ketat (kita akan lihat nanti bagaimana mengatasinya dengan kustom marshallers):
- Peta kunci harus string.
- Nilai-nilai peta harus jenis Defender oleh paket json.
- Jenis berikut tidak didukung: Channel, kompleks, dan fungsi.
- Struktur data siklik yang tidak didukung.
- Petunjuk akan dikodekan (dan kemudian diterjemahkan) sebagai nilai-nilai yang mereka menunjukkan (atau 'null' jika pointer nol).
Unmarshal
Fungsi Unmarshal() mengambil sepotong byte yang mudah-mudahan mewakili berlaku JSON dan antarmuka tujuan, yang biasanya pointer ke struct atau jenis dasar. Itu deserializes JSON ke antarmuka secara generik. Jika serialisasi gagal, itu akan kembali kesalahan. Berikut adalah tanda tangan:
func Unmarshal (byte [] data, {} antarmuka v) kesalahan
Jenis sederhana serializing
Anda dapat dengan mudah serialize jenis sederhana seperti menggunakan paket json. Hasilnya tidak akan sebuah objek JSON yang penuh, tetapi sebuah string sederhana. Di sini int 5 serial ke byte array [53], yang berkaitan dengan 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) Output: - 5 => [53], '5' - [53] => 5
Jika Anda mencoba untuk tidak didukung jenis seperti pertemuan serialize, Anda akan mendapatkan kesalahan:
// Trying to serialize a function foo := func() { fmt.Println("foo() here") } bytes, err = json.Marshal(foo) if err != nil { fmt.Println(err) } Output: json: unsupported type: func()
Serializing Data yang sewenang-wenang dengan peta
Kekuatan JSON adalah bahwa hal ini dapat mewakili sewenang-wenang hirarkis data sangat baik. Paket JSON mendukung dan memanfaatkan kosong generik antarmuka (interface {}) untuk mewakili setiap hirarki JSON. Berikut adalah contoh dari deserializing dan kemudian serializing pohon biner mana setiap node memiliki nilai int dan dua cabang, kanan dan kiri, yang mungkin berisi node lain atau null.
Null JSON setara dengan Go nil. Seperti yang Anda lihat di output, fungsi json.Unmarshal () berhasil mengubah gumpalan JSON menjadi struktur data Go yang terdiri dari peta antarmuka yang bersarang dan mempertahankan tipe nilai sebagai int. Fungsi json.Marshal()
berhasil membuat cerita bersambung objek yang dihasilkan ke representasi JSON yang sama.
// Arbitrary nested JSON 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)) } } Output: -------- 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}
Untuk melintasi peta generik antarmuka, Anda harus menggunakan pernyataan jenis. Sebagai contoh:
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 }
Serialisasi Data Terstruktur
Bekerja dengan data terstruktur seringkali merupakan pilihan yang lebih baik. Go memberikan dukungan luar biasa untuk membuat serial JSON ke / dari struct
melalui tag struct
nya. Mari kita buat struct
yang sesuai dengan pohon JSON kita dan fungsi Dump()
yang lebih cerdas yang mencetaknya:
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 + " ") } }
Ini bagus dan jauh lebih bersih daripada pendekatan JSON yang sewenang-wenang. Tetapi apakah itu berhasil? Tidak juga. Tidak ada kesalahan, tetapi objek pohon kami tidak diisi oleh 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("") } Output: value: 0 left: <nil> right: <nil>
Masalahnya adalah bidang Pohon bersifat pribadi. Serialisasi JSON hanya berfungsi di bidang publik. Jadi kita dapat membuat bidang struct
publik. Paket json cukup pintar untuk secara transparan mengkonversi huruf kecil "nilai", "kiri", dan "kanan" ke nama bidang huruf besar yang sesuai.
type Tree struct { Value int `json:"value"` Left *Tree `json:"left"` Right *Tree `json:"right"` } Output: value: 3 left: value: 1 left: <nil> right: value: 2 left: <nil> right: <nil> right: value: 4 left: <nil> right: <nil>
Paket json akan secara diam-diam mengabaikan bidang yang belum dipetakan di JSON serta bidang pribadi di struct
Anda. Tetapi kadang-kadang Anda mungkin ingin memetakan kunci tertentu di JSON ke bidang dengan nama yang berbeda di struct
Anda. Anda dapat menggunakan tag struct
untuk itu. Misalnya, misalkan kita menambahkan bidang lain yang disebut "label" ke JSON, tetapi kita perlu memetakannya ke bidang yang disebut "Tag" di struct kami.
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 + " ") } }
Berikut ini adalah JSON baru dengan simpul akar dari pohon berlabel "root", diserialkan dengan benar ke dalam bidang Tag dan dicetak dalam output:
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("") } Output: value: 3 tag: root left: value: 1 left: <nil> right: value: 2 left: <nil> right: <nil> right: value: 4 left: <nil> right: <nil>
Menulis Marshaller Kustom
Anda akan sering ingin membuat serial objek yang tidak sesuai dengan persyaratan ketat fungsi Marshal(). Misalnya, Anda mungkin ingin membuat serial peta dengan kunci int. Dalam kasus ini, Anda bisa menulis custom marshaller / unmarshaller dengan mengimplementasikan antarmuka Marshaler
dan Unmarshaler
.
Catatan tentang ejaan: di pergi, Konvensi adalah nama sebuah antarmuka dengan metode tunggal dengan menambahkan "eh" akhiran untuk nama metode. Jadi, meskipun ejaan yang lebih umum adalah "Marshaller" (dengan double L), nama antarmuka adalah hanya "Marshaler" (single L).
Berikut adalah antarmuka Marshaler dan Unmarshaler:
type Marshaler interface { MarshalJSON() ([]byte, error) } type Unmarshaler interface { UnmarshalJSON([]byte) error }
Anda harus membuat sejenis ketika melakukan kustom serialisasi, bahkan jika Anda ingin cerita bersambung built-in tipe atau komposisi jenis-jenis built-in seperti map[int]string
. Di sini saya mendefinisikan tipe yang disebut IntStringMap
dan melaksanakan Marshaler
dan Unmarshaler
antarmuka untuk jenis ini.
Metode MarshalJSON()
menciptakan sebuah map[string]string
, mengkonversi masing-masing kunci int sendiri untuk string, dan serializes peta dengan tombol string yang menggunakan json standar. Fungsi json.Marshal()
.
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) }
Metode UnmarshalJSON()
melakukan sebaliknya. Ini deserializes array byte data menjadi string [string] peta dan kemudian mengkonversi setiap kunci string ke int dan mengisi sendiri.
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 }
Berikut adalah cara untuk menggunakannya dalam program:
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]) Output: IntStringMap to JSON: {"4":"four","5":"five"} IntStringMap from JSON: map[2:two 1:one] m[1]: one m[2]: two
Serializing Enums
Pergi enums dapat cukup menjengkelkan untuk cerita bersambung. Ide untuk menulis artikel tentang pergi json serialisasi keluar dari pertanyaan seorang kolega bertanya padaku tentang bagaimana cerita bersambung enums. Ini adalah Go enum
. Konstanta Zero dan One sama dengan ints 0 dan 1.
type EnumType int const ( Zero EnumType = iota One )
Meskipun Anda mungkin berpikir itu int, dan dalam banyak hal itu adalah, Anda tidak dapat membuat cerita bersambung langsung. Anda harus menulis marshaler / unmarshaler khusus. Itu tidak masalah setelah bagian terakhir. MarshalJSON()
dan UnmarshalJSON()
berikut akan cerita bersambung/deserialize konstanta nol dan satu dari string terkait "Nol" dan "Satu".
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) }
Mari kita coba menyematkan EnumType
ini dalam sebuah struct
dan membuat serial. Fungsi utama membuat EnumContainer
dan menginisialisasi dengan nama "Uno" dan nilai konstanta enum
ONE
kami, yang sama dengan int 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) } Output: Uno : 0
Output yang diharapkan adalah "Uno: 1", tetapi sebaliknya "Uno: 0". Apa yang terjadi? Tidak ada bug dalam kode marshal / unmarshal. Ternyata Anda tidak dapat menanamkan enum dengan nilai jika Anda ingin membuat cerita bersambung. Anda harus menanamkan pointer ke enum. Berikut ini adalah versi yang dimodifikasi yang berfungsi seperti yang diharapkan:
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) } Output: Uno : 1
Kesimpulan
Go menyediakan banyak opsi untuk membuat serial dan deserialisasi JSON. Sangat penting untuk memahami seluk beluk paket enkode / json untuk mengambil keuntungan dari kekuatan.
Tutorial ini menempatkan semua kekuatan di tangan Anda, termasuk bagaimana cerita bersambung enums Go sukar dipahami.
Pergi cerita bersambung beberapa objek!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly