1. Intro & How to use
There are two main functions in the encoing/json
package: Marshal
, and Unmarshal
:
Marshal(v any) ([]byte, error)
: encode Golang object into JSON.Unmarshal(data []byte, v any) error
: decode JSON to Golang object.
Here, the Golang object refers to both struct
and slice
, map
.
1.1 Marshal
1package main2
3import (4 "encoding/json"5 "fmt"6)7
8type Employee struct {9 Name string `json:"name"`10 Position string `json:"position"`11 Age int `json:"age"`12 Salary uint64 `json:"salary"`13}14
15func main() {17 collapsed lines
16 // 1. Golang object -> JSON17 employee1 := Employee{18 Name: "Alice",19 Position: "Manager",20 Age: 30,21 Salary: 5000,22 }23 jsonData, err := json.Marshal(employee1)24 if err != nil {25 fmt.Println("Error marshaling JSON:", err)26 return27 }28
29 fmt.Println(string(jsonData))30}31// output:32// {"name":"Alice","position":"Manager","age":30,"salary":5000}
1.2 Unmarshal
1func main() {2 // 2. JSON -> Golang object3 jsonStr := `{"name":"Bob","position":"Developer","age":25,"salary":4000000}`4 var employee2 Employee5 err = json.Unmarshal([]byte(jsonStr), &employee2)6 if err != nil {7 fmt.Println("Error unmarshaling JSON:", err)8 return9 }10 fmt.Println(employee2)11}12// output:13// {Bob Developer 25 4000000}
1.3 JSON tag options
json:"key"
: Specify the key in the JSON output.json:"key,omitempty"
: Omit the field if it is empty (i.e., zero value).json:"-"
: Ignore the field (do not marshal or unmarshal it).
2. Attentions!!!
1) pubic fields
When using a struct to handle JSON, the strut’s fields must be public, i.e. the field names must start with an uppercase letter.
-
Example:
1type Employee struct {2Name string3Position string4Age int5**salary uint64 // private field**6}7func main() {8jsonStr := `{"name":"Bob","position":"Developer","age":25,"salary":4000000}`9var employee2 Employee10err = json.Unmarshal([]byte(jsonStr), &employee2)11if err != nil {12fmt.Println("Error unmarshaling JSON:", err)13return14}15fmt.Println(employee2)4 collapsed lines16}17// when unmarshaling, we cannot read correct value of `salary`18// output:19// {Bob Developer 25 **0**}
[!IMPORTANT] In Go, the language design prohibits reflection from accessing private struct fields. This is done intentionally to enforce the principles of encapsulation and data privacy.
2) avoid using map
Avoid using map, because map brings extra cost, extrac code, and extra maintenance cost.
Why consider struct
before map
: struct
defines the fields and their types ahead, while map
has no constraints to data → map
cannot constraint the change of JSON, bring extra work and mantenance cost.
1func main() {2 var m map[string]any3 err = json.Unmarshal([]byte(jsonStr), &m)4 if err != nil {5 fmt.Println("Error unmarshaling JSON:", err)6 return7 }8 // lots of codes for maintaining the map9 name, ok := m["name"].(string)10 if !ok {11 fmt.Println("Error type assertion")12 return13 }14 fmt.Println(name)15 // extra work for other fields...1 collapsed line
16}
3) repeat unmarshaling
Dirty data contamination: the fields that were not explicitly overwritten in the second JSON data retain their values from the previous unmarshalling
Solution: whenever unmarshaling JSON, use a new struct object to load the data
1func main() {2 // First JSON string with the salary field3 jsonStr3 := `{"name":"Bob","position":"Developer","age":25,"salary":4000000}`4 var employee Employee5 _ = json.Unmarshal([]byte(jsonStr3), &employee)6 fmt.Println("employee3:", employee)7 // Second JSON string without the salary field8 jsonStr4 := `{"name":"Kate","position":"Senior Developer","age":35}`9 // Salary remains 400000010 _ = json.Unmarshal([]byte(jsonStr4), &employee)11 fmt.Println("employee4:", employee)12}13// outputs:14// employee3: {Bob Developer 25 4000000}15// employee4: {Kate Senior Developer 35 **4000000**}
4) confusion brought by default value
In Golang, the default value of int
is 0
, string
’s is “”
, pointer
’s is nil
, etc.
Issue: with the approach above, we cannot distinguish the difference between missing object, or the case that value is indeed default value.
Solution: by changing the field to pointer, we can use pointer’s nil
to distinguish the difference.
1func ZeroValueConfusion() {2 str := `{"name":"Bob","position":"Developer","age":25,"salary":100}`3 var p Employee4 _ = json.Unmarshal([]byte(str), &p)5 fmt.Printf("ZeroValueConfusion: %+v\n", p)6
7 str2 := `{"name":"Bob","position":"Developer","age":25}`8 var p2 Employee9 _ = json.Unmarshal([]byte(str2), &p2)10 fmt.Printf("ZeroValueConfusion: %+v\n", p2)11 if p2.Salary == nil {12 fmt.Println("Salary is nil")13 }14}15
4 collapsed lines
16// outputs:17// {Name:Bob Position:Developer Age:25 Salary:0x1400000e2f0}18// {Name:Bob Position:Developer Age:25 Salary:<nil>}19// p2.Salary is nil
5) JSON tags
- Confusion brought by default value: default value or missing value
1type Employee struct {2 Name string `json:"name"`3 Position string `json:"position"`4 Age int `json:"age,omitempty"`5 Salary uint64 `json:"salary,omitempty"`6}7
8func TagMarshal() {9 p := Employee{10 Name: "Bob",11 Salary: 0,12 }13 output, _ := json.MarshalIndent(p, "", " ")14 println(string(output))15}5 collapsed lines
16// outputs:17// {18// "name": "Bob",19// "position": ""20// }
-
Solution: use pointer
1type Employee struct {2Name string `json:"name"`3Position string `json:"position"`4Age int `json:"age,omitempty"`5Salary *uint64 `json:"salary,omitempty"`6}78func TagMarshal() {9p := Employee{10Name: "Bob",11Salary: new(uint64),12}13*p.Salary = 10014output, _ := json.MarshalIndent(p, "", " ")15println(string(output))8 collapsed lines16}1718// outputs:19// {20// "name": "Bob",21// "position": "",22// "salary": 10023// }
You may find the full example of the code here: https://github.com/jidalii/go-playground/tree/main/parse-json