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

Written by imantumorang | Published 2018/08/09
Tech Story Tags: golang | today-i-learned | programming | software-development | pointer-in-golang

TLDR 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. In my custom MarshalJSON I make the receiver is a pointer. So my custom marshall 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 the. Object. It also vise versa for non. pointer (value) receiver in function.via the TL;DR App

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.

Written by imantumorang | Software Engineer
Published by HackerNoon on 2018/08/09