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

7,692 reads

@ imantumorang Iman Tumorang Software Engineer

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.

reactions

type ClassRoom struct { Name string `json:"name"` Students map[int]Student `json:"students"` } type Students struct { Name string `json:"name"` Age int `json:"age"` }

reactions

With this struct, if we marshall it to JSON, the result will looks like this:

reactions

{ "name" : "D3TI2-Classmate" , "students" : { "1" : { "name" : "Iman Tumorang" , "age" : 17 }, "2" : { "name" : "Christin Tumorang" , "age" : 18 }, "3" : { "name" : "Idola Manurung" , "age" : 18 } } }

reactions

But for requirement to API, we want the JSON result is without the student’s rank. We just want looks like this:

reactions

{ "name" : "D3TI2-Classmate" , "students" : [ { "name" : "Iman Tumorang" , "age" : 17 }, { "name" : "Christin Tumorang" , "age" : 18 }, { "name" : "Idola Manurung" , "age" : 18 } ] }

reactions

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

reactions

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, }) }

reactions

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 😅

reactions

Issue

reactions

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.

reactions

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, }) }

reactions

Fixing

After tired of searching solutions, then I figured it later when I accidentally call the JSON marshall with a pointer object.

reactions

sample := &ClassRoom{ // See the ampersand symbol "&" Name: "D3TI2-Classmate" , Students : students, } _,_= json.Marshal(sample)

reactions

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.

reactions

So to summarize:

If I made the custom MarshalJSON with a pointer receiver

reactions

func (s *ClassRoom)MarshalJSON() ([]byte, error){ // Other codes }

reactions

Then when I want to marshal the Object, I must pass a pointer object to

json.Marhsal

reactions

sample := &ClassRoom{ // See the ampersand symbol "&" Name: "D3TI2-Classmate" , Students : students, } _,_= json.Marshal(sample)

reactions

And it also vise versa for non-pointer (value) receiver.

reactions

func (s ClassRoom)MarshalJSON() ([]byte, error){ // Other codes }

reactions

If I made the custom MarshalJSON with a non-pointer (value) receiver

reactions

func (s ClassRoom)MarshalJSON() ([]byte, error){ // Other codes }

reactions

Then when I want to marshal the Object, I must pass a non-pointer (value) object to

json.Marhsal

reactions

sample := ClassRoom{ // Without the ampersand symbol "&" Name: "D3TI2-Classmate" , Students : students, } _,_= json.Marshal(sample)

reactions

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.

reactions

Tags