Before you go, check out these stories!

0
Hackernoon logoToday I Learned: Beware with pointer in Golang!!! by@imantumorang

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

Author profile picture

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

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

The Noonification banner

Subscribe to get your daily round-up of top tech stories!