Nested Struct vs Embedded Struct di Go: Marshaling dan Unmarshaling - Part 2
Pada artikel sebelumnya, kita telah membahas perbedaan utama antara nested struct dengan embedded struct. Sekarang kita akan coba lihat perbedaan hasil dari proses marshal dan unmarshal struct
yang di dalamnya memiliki nested struct dan embedded struct. Kita juga akan melihat beberapa keunikan yang mungkin terjadi jika struct
tersebut di-marshal atau di-unmarshal.
Marshaling
Marshaling adalah proses mengubah struktur data Go menjadi serangkaian byte, seperti JSON. Kita bisa mengubah tipe struct
menjadi JSON. Berikut ini perbedaan hasil dari proses marshal pada struct
yang memiliki nested struct dan struct
yang memiliki embedded struct.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
Country string
}
type Person struct {
Name string
Age int
Address Address // Nested struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
Country: "Indonesia",
},
}
jsonBytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonBytes)) // Output: {"Name":"John","Age":46,"Address":{"Street":"Durian Street","City":"Bandung","Country":"Indonesia"}}
}
Pada contoh di atas, kita bisa lihat ada tipe Address
dan tipe Person
. Tipe Address
dijadikan sebagai nested struct dalam tipe Person
. Jika objek dengan tipe Person
di-marshal, maka hasilnya jadi seperti di bawah ini.
{
"Name": "John",
"Age": 46,
"Address": {
"Street": "Durian Street",
"City": "Bandung",
"Country": "Indonesia"
}
}
Sekarang kita akan coba marshal struct
yang memiliki embedded struct di dalamnya.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
Country string
}
type Person struct {
Name string
Age int
Address // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
Country: "Indonesia",
},
}
jsonBytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonBytes)) // Output: {"Name":"John","Age":46,"Street":"Durian Street","City":"Bandung","Country":"Indonesia"}
}
Pada contoh di atas, kita bisa lihat ada tipe Address
dan tipe Person
, sama seperti sebelumnya. Tapi kali ini tipe Address
dijadikan sebagai embedded struct dalam tipe Person
. Jika objek dengan tipe Person
di-marshal, maka hasilnya jadi seperti di bawah ini.
{
"Name": "John",
"Age": 46,
"Street": "Durian Street",
"City": "Bandung",
"Country": "Indonesia"
}
Perhatikan bahwa jika tipe Address
di-embed, maka semua field dari tipe Address
berada langsung di dalam objek bertipe Person
.
Gimana Kalau Ada Nama Field yang Sama di Tingkatan yang Beda?
Hasil marshal suatu struct
bisa jadi tidak sesuai dengan apa yang kita kira kalau di dalam struct
tersebut terdapat field dengan nama yang sama. Pada contoh di bawah, ada tipe Address
dan tipe Person
. Tipe Address
di-embed ke dalam tipe Person
. Perhatikan bahwa tipe Address
dan tipe Person
sama-sama memiliki field Country
.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
Country string
}
type Person struct {
Name string
Age int
Country string
Address // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Country: "Australia",
Address: Address{
Street: "Durian Street",
City: "Bandung",
Country: "Indonesia",
},
}
jsonBytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonBytes)) // Output: {"Name":"John","Age":46,"Country":"Australia","Street":"Durian Street","City":"Bandung"}
}
Jika objek bertipe Person
di-marshal, maka bisa dilihat bahwa value Country
diambil dari field Country
yang ada di tingkatan/ lapisan lebih tinggi. Field Country
terlihat memiliki value "Australia"
, bukan "Indonesia"
. Seperti yang sudah kita ketahui pada artikel sebelumnya, hal itu terjadi karena menambahkan field Country
di tingkatan/ lapisan yang lebih tinggi, akan menyembunyikan field Country
yang ada di tingkatan/ lapisan struct
yang lebih dalam.
{
"Name": "John",
"Age": 46,
"Country": "Australia",
"Street": "Durian Street",
"City": "Bandung"
}
Gimana Kalau Ada Nama Field yang Sama di Tingkatan yang Sama?
Seperti pada contoh sebelumnya, kali ini kita juga akan melakukan marshal suatu struct
yang di dalamnya terdapat embedded struct. Tapi kali ini nama field yang sama berada pada tingkatan yang sama.
Pada contoh di bawah ada tipe Address
, tipe Birthplace
, dan tipe Person
. Tipe Address
dan tipe Birthplace
di-embed ke dalam tipe Person
. Perhatikan bahwa tipe Address
dan tipe Birthplace
sama-sama memiliki field City
. Lalu jika objek bertipe Person
di-marshal, field City
mana yang akan diambil?
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
}
type Birthplace struct {
City string
Country string
}
type Person struct {
Name string
Age int
Address // Embedded struct
Birthplace // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
Birthplace: Birthplace{
City: "Jakarta",
Country: "Indonesia",
},
}
jsonBytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonBytes)) // Output: {"Name":"John","Age":46,"Street":"Durian Street","Country":"Indonesia"}
}
Jika objek bertipe Person
di-marshal, kita bisa lihat field City
tidak muncul. Berdasarkan yang tertulis pada dokumentasi, kalau ada field yang namanya sama di tingkatan/ lapisan yang sama, maka field tersebut diabaikan dan tidak memunculkan error.
{
"Name": "John",
"Age": 46,
"Street": "Durian Street",
"Country": "Indonesia"
}
Unmarshaling
Unmarshaling adalah proses mengubah serangkaian byte (contoh format: JSON), menjadi struktur data Go. Dua contoh di bawah menunjukkan cara unmarshal struct
yang di dalamnya ada nested struct dan struct
yang di dalamnya ada embedded struct.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
Country string
}
type Person struct {
Name string
Age int
Address Address // Nested struct
}
func main() {
jsonBytes := []byte(`{"Name":"John","Age":46,"Address":{"Street":"Durian Street","City":"Bandung","Country":"Indonesia"}}`)
var person Person
err := json.Unmarshal(jsonBytes, &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", person) // Output: {Name:John Age:46 Address:{Street:Durian Street City:Bandung Country:Indonesia}}
}
Untuk bisa memetakan JSON ke struct
yang di dalamnya ada nested struct, maka JSON harus memiliki field yang sesuai dengan field dari struct
tempat menyimpan hasil unmarshal. String JSON pada contoh di atas memiliki field Name
, Age
, dan Address
. Lalu value dari field Address
adalah objek yang memiliki field Street
, City
, dan Country
. Itu sesuai dengan tempat untuk menyimpan hasil unmarshal, yaitu di variabel person
yang bertipe Person
.
Sekarang, perhatikan contoh selanjutnya.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
Country string
}
type Person struct {
Name string
Age int
Address // Embedded struct
}
func main() {
jsonBytes := []byte(`{"Name":"John","Age":46,"Street":"Durian Street","City":"Bandung","Country":"Indonesia"}`)
var person Person
err := json.Unmarshal(jsonBytes, &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", person) // Output: {Name:John Age:46 Address:{Street:Durian Street City:Bandung Country:Indonesia}}
}
Berbeda dari sebelumnya, string JSON kali ini tidak memiliki field Address
. Field Street
, City
, dan Country
bukan berada pada objek yang menjadi value dari field Address
.
Hasil unmarshal string JSON tersebut dapat disimpan pada variabel person
bertipe Person
. Tipe Person
adalah struct
yang memiliki Address
sebagai embedded struct di dalamnya. Karena tipe Address
di-embed langsung ke dalam tipe Person
, maka semua field dalam tipe Address
diberikan kepada tipe Person
. Ini membuat tipe Person
dianggap memiliki field Street
, City
, dan Country
langsung di dalamnya.
Gimana Kalau Ada Nama Field yang Sama di Tingkatan yang Beda?
Pada contoh di bawah, ada tipe Address
dan tipe Person
. Tipe Address
di-embed ke dalam tipe Person
. Perhatikan bahwa tipe Address
dan tipe Person
sama-sama memiliki field Country
. Kita akan melakukan unmarshal terhadap JSON yang memiliki field Name
, Age
, Street
, City
, dan Country
.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
Country string
}
type Person struct {
Name string
Age int
Country string
Address // Embedded struct
}
func main() {
jsonBytes := []byte(`{"Name":"John","Age":46,"Country":"Australia","Street":"Durian Street","City":"Bandung"}`)
var person Person
err := json.Unmarshal(jsonBytes, &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", person) // Output: {Name:John Age:46 Country:Australia Address:{Street:Durian Street City:Bandung Country:}}
}
Perhatikan hasil print dari variabel person
. Field Country
dari struct
terluar terisi dengan value "Australia"
, tapi field Country
dari struct
yang di-embed value-nya kosong.
Sama seperti pada contoh marshaling, hal itu terjadi karena field Country
di tingkatan/ lapisan yang lebih tinggi akan menyembunyikan field Country
yang ada di tingkatan/ lapisan struct
yang lebih dalam.
Gimana Kalau Ada Nama Field yang Sama di Tingkatan yang Sama?
Kita akan melakukan unmarshal suatu struct
yang di dalamnya terdapat embedded struct. Tapi kali ini nama field yang sama berada pada tingkatan yang sama.
Pada contoh di bawah ada tipe Address
, tipe Birthplace
, dan tipe Person
. Tipe Address
dan tipe Birthplace
di-embed ke dalam tipe Person
. Perhatikan bahwa tipe Address
dan tipe Birthplace
sama-sama memiliki field City
.
Kita akan melakukan unmarshal terhadap JSON yang memiliki field Name
, Age
, Street
, City
, dan Country
. Kira-kira value field City
di JSON akan disimpan di field City
tipe Address
atau tipe Birthplace
, ya?
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
}
type Birthplace struct {
City string
Country string
}
type Person struct {
Name string
Age int
Address // Embedded struct
Birthplace // Embedded struct
}
func main() {
jsonBytes := []byte(`{"Name":"John","Age":46,"Street":"Durian Street","City":"Bandung","Country":"Indonesia"}`)
var person Person
err := json.Unmarshal(jsonBytes, &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", person) // Output: {Name:John Age:46 Address:{Street:Durian Street City:} Birthplace:{City: Country:Indonesia}}
}
Jika kita lihat hasil print variabel person
, ternyata field City
dari tipe Address
dan field City
dari tipe Birthplace
sama-sama kosong, tidak ada yang terisi value "Bandung"
.
Ini sama seperti pada contoh marshaling yang sebelumnya. Berdasarkan yang tertulis pada dokumentasi, kalau ada field yang namanya sama di tingkatan/ lapisan yang sama, maka field tersebut diabaikan dan tidak memunculkan error.
Kita bisa mengubah kodenya menjadi seperti ini agar value "Bandung"
dapat tersimpan pada field City
dari embedded struct.
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Street string
City string
}
type Birthplace struct {
City string
Country string
}
type PersonBase struct {
Name string
Age int
}
type Person struct {
PersonBase // Embedded struct
Address // Embedded struct
Birthplace // Embedded struct
}
func (p *Person) UnmarshalJSON(jsonBytes []byte) error {
var personBase PersonBase
err := json.Unmarshal(jsonBytes, &personBase)
if err != nil {
return err
}
p.PersonBase = personBase
var address Address
err = json.Unmarshal(jsonBytes, &address)
if err != nil {
return err
}
p.Address = address
var birthplace Birthplace
err = json.Unmarshal(jsonBytes, &birthplace)
if err != nil {
return err
}
p.Birthplace = birthplace
return nil
}
func main() {
jsonBytes := []byte(`{"Name":"John","Age":46,"Street":"Durian Street","City":"Bandung","Country":"Indonesia"}`)
var person Person
err := json.Unmarshal(jsonBytes, &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", person) // Output: {PersonBase:{Name:John Age:46} Address:{Street:Durian Street City:Bandung} Birthplace:{City:Bandung Country:Indonesia}}
}
Pada kode di atas, kita bisa lihat field Name
dan field Age
dipindahkan ke tipe PersonBase
. Lalu tipe PersonBase
di-embed ke dalam tipe Person
. Sekarang tipe Person
memiliki 3 embedded struct, yaitu PersonBase
, Address
, dan Birthplace
.
Selanjutnya, kita buat tipe Person
mengimplementasikan interface Unmarshaler
. Suatu tipe dianggap mengimplementasikan interface Unmarshaler
kalau tipe itu memiliki method UnmarshalJSON()
.
Karena tipe Person
sekarang memiliki method UnmarshalJSON()
, method tersebut akan dieksekusi ketika melakukan unmarshal suatu JSON. Cara yang dilakukan untuk dapat mengambil value field City
pada JSON adalah dengan melakukan unmarshal satu per satu untuk setiap embedded struct di dalam tipe Person
. Pada hasil print variabel person
terlihat field City
pada tipe Address
dan field City
pada tipe Birthplace
memiliki value "Bandung"
.