Iman Tumorang

Software Engineer

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

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

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.
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.

Tags

More by Iman Tumorang

Golang
Golang
Docker
Kubernetes
Golang
Golang
Kubernetes
Docker
Golang
Golang
Golang
Golang
Golang
Golang
Nosql
Golang
Golang
Unit Testing
Push Notification
Golang
Golang
Topics of interest