Iman Tumorang

Software Engineer

Today I Learned: Beware with pointer in Golang!!!

August 9th 2018

Today’s Bug When Doing Custom JSON Marshal in Golang

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.

Fixing

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.

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.

More by Iman Tumorang

More Related Stories