So, today’s bug and a lesson to be learned is about non/pointer receiver function, it happens when I’m doing custom marshal JSON for my struct. I’m stuck for a few hours when to fix this.
So the scenario is, I have a struct let’s say a ClassRoom struct, and the ClassRoom has many students. Every student ranked ascending by their mark or something whatever it is :D, this is just an example to describe how is our real case struct. It’s similar, but different in name.
type ClassRoom struct {
Name string `json:"name"`
Students map[int]Student `json:"students"`
}
type Students struct {
Name string `json:"name"`
Age int `json:"age"`
}
With this struct, if we marshall it to JSON, the result will looks like this:
{
"name": "D3TI2-Classmate",
"students": {
"1": {
"name": "Iman Tumorang",
"age": 17
},
"2": {
"name": "Christin Tumorang",
"age": 18
},
"3": {
"name": "Idola Manurung",
"age": 18
}
}
}
But for requirement to API, we want the JSON result is without the student’s rank. We just want looks like this:
{
"name": "D3TI2-Classmate",
"students": [
{
"name": "Iman Tumorang",
"age": 17
},
{
"name": "Christin Tumorang",
"age": 18
},
{
"name": "Idola Manurung",
"age": 18
}
]
}
Well, based on my experience, to do this in Golang is very easy. We just create a function with the ClassRoom as the receiver with the same name as the function name in interface of Marshaler
func (s ClassRoom) MarshalJSON() ([]byte, error) {
arrStudent := []Student{}
arrKey := []int{}
for k, _ := range s.Students {
arrKey = append(arrKey, k)
}
sort.Ints(arrKey)
for _, pos := range arrKey {
arrStudent = append(arrStudent, s.Students[pos])
}
return json.Marshal(struct {
Name string `json:"name"`
Students []Student `json:"students"`
}{
Name: s.Name,
Students: arrStudent,
})
}
Just simple as that. But then, when I’m trying to test it. The JSON results is not right as we expected. And I’m stucked for a few hours in this issue :D *so fool I am 😅
Issue
Before the code fixed, the code is more like this below. If we see here, it’s like nothing wrong here. But the result is wrong.
package main
import (
"encoding/json"
"fmt"
"log"
"sort"
)
func main() {
students := map[int]Student{
1: Student{
Name: "Iman Tumorang",
Age: 17,
},
2: Student{
Name: "Christin Tumorang",
Age: 18,
},
3: Student{
Name: "Idola Manurung",
Age: 18,
},
}
sample := ClassRoom{
Name: "D3TI2-Classmate",
Students: students,
}
jbyt, err := json.Marshal(sample)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jbyt))
}
type ClassRoom struct {
Name string `json:"name"`
Students map[int]Student `json:"students"`
}
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (s *ClassRoom) MarshalJSON() ([]byte, error) {
arrStudent := []Student{}
arrKey := []int{}
for k, _ := range s.Students {
arrKey = append(arrKey, k)
}
sort.Ints(arrKey)
for _, pos := range arrKey {
arrStudent = append(arrStudent, s.Students[pos])
}
return json.Marshal(struct {
Name string `json:"name"`
Students []Student `json:"students"`
}{
Students: arrStudent,
Name: s.Name,
})
}
After tired of searching solutions, then I figured it later when I accidentally call the JSON marshall with a pointer object.
sample := &ClassRoom{ // See the ampersand symbol "&"
Name: "D3TI2-Classmate",
Students: students,
}
_,_= json.Marshal(sample)
That’s the problem. In my custom MarshalJSON I make the receiver is a pointer. So my custom MarshalJSON won’t called if I try to marshal a non-pointer object. But, if I marshal a non-pointer object, it will succeed to marshal.
So to summarize:
If I made the custom MarshalJSON with a pointer receiver
func (s *ClassRoom)MarshalJSON() ([]byte, error){
// Other codes
}
Then when I want to marshal the Object, I must pass a pointer object to
json.Marhsal
sample := &ClassRoom{ // See the ampersand symbol "&"
Name: "D3TI2-Classmate",
Students: students,
}
_,_= json.Marshal(sample)
And it also vise versa for non-pointer (value) receiver.
func (s ClassRoom)MarshalJSON() ([]byte, error){
// Other codes
}
If I made the custom MarshalJSON with a non-pointer (value) receiver
func (s ClassRoom)MarshalJSON() ([]byte, error){
// Other codes
}
Then when I want to marshal the Object, I must pass a non-pointer (value) object to
json.Marhsal
sample := ClassRoom{ // Without the ampersand symbol "&"
Name: "D3TI2-Classmate",
Students: students,
}
_,_= json.Marshal(sample)
Well, this is a very silly issue for me, because it’s really made me furious for a few hours. And it’s made me more cautious when deciding using a pointer or non pointer receiver in function.