SoFunction
Updated on 2025-03-06

In-depth analysis of JSON encoding and decoding in Golang

With the rapid development of the Internet and the widespread application of data exchange, the processing of various data formats has become a key issue in software development. As a general data exchange format, JSON has been widely used in various application scenarios, including web services, mobile applications and large-scale data processing. Golang is a high-performance, concurrent and secure language that has excellent JSON processing capabilities. This article will introduce the relevant knowledge of JSON encoding and decoding in Golang to help everyone understand its basic principles and efficient application.

1. Introduction to JSON

JSON is a text-based lightweight data exchange format that represents structured data in a way that is easy for humans to read and write. JSON organizes data in the form of key-value pairs and supports a variety of data types, including strings, numbers, booleans, arrays, and objects. Here is a simple JSON example:

 {
   "name": "Alice",
   "age": 25,
   "isStudent": true,
   "hobbies": ["reading", "coding", "music"]
 }

In the above example, name is a string type key, corresponding to "Alice"; age is a numeric type key, corresponding to 25; isStudent is a Boolean type key, corresponding to true; hobbies is an array type key, corresponding to an array containing three string elements.

2. JSON encoding in Golang

The encoding/json package in the Golang standard library provides rich functionality for encoding Go data structures into JSON format. Here are some common JSON encoding usage examples:

2.1 JSON encoding of structures

In Golang, you can specify the field name and other options when JSON encoding by adding a json tag to the structure field. For example, consider the following Person structure:

 type Person struct {
     Name string `json:"name"`
     Age  int    `json:"age"`
 }

To encode the structure as JSON, you can use the () function:

 p := Person{Name: "Alice", Age: 25}
 data, err := (p)
 if err != nil {
     (err)
 }
 (string(data))

Run the above code and the output will be:

{"name":"Alice","age":25}

In this example, the() function encodes the Person structure into JSON format and stores the result in a data variable. Finally, we use the () function to print out the encoded JSON string.

2.2 JSON encoding of slices and maps

In addition to structures, slices and mappings in Golang can also be easily JSON encoding. For example, consider the following examples of slices and mappings:

 names := []string{"Alice", "Bob", "Charlie"}
 data, err := (names)
 if err != nil {
     (err)
 }
 (string(data))
 ​
 scores := map[string]int{
     "Alice":   100,
     "Bob":     85,
     "Charlie": 92,
 }
 data, err = (scores)
 if err != nil {
     (err)
 }
 (string(data))

Run the above code and the output will be:

 ["Alice","Bob","Charlie"]
 {"Alice":100,"Bob":85,"Charlie":92}

In this example, we first JSON encoding of slice names and map scores respectively, and print out the results. Slices and mappings are encoded as corresponding JSON arrays and objects.

3. JSON decoding in Golang

In addition to JSON encoding, the encoding/json package in Golang also provides the function of JSON decoding, which can decode JSON data into Go data structures. Here are some common JSON decoding usage examples:

3.1 JSON decoding into a structure

To decode JSON into a struct, you need to first define the corresponding struct type and decode it using the () function. For example, consider the following JSON data:

 {
   "name": "Alice",
   "age": 25
 }

We can define a Person structure to represent this data:

 type Person struct {
     Name string `json:"name"`
     Age  int    `json:"age"`
 }

Then, you can decode JSON into the structure using the () function:

 jsonStr := `{"name":"Alice","age":25}`
 var p Person
 err := ([]byte(jsonStr), &p)
 if err != nil {
     (err)
 }
 (, )

Run the above code and the output will be:

Alice 25

In this example, we first save the JSON data in the jsonStr variable. Then, use the () function to decode JSON into a Person structure and store the result in the variable p. Finally, we print out the field value of p.

3.2 JSON decoded to slice and map

In addition to decoding as a structure, JSON data can also be decoded as slices and maps. The process of decoding to slices and mapping is similar to decoding to structures. Here is the sample code:

 jsonStr := `["Alice","Bob","Charlie"]`
 var names []string
 err := ([]byte(jsonStr), &names)
 if err != nil {
     (err)
 }
 (names)
 ​
 jsonStr = `{"Alice":100,"Bob":85,"Charlie":92}`
 var scores map[string]int
 err = ([]byte(jsonStr), &scores)
 if err != nil {
     (err)
 }
 (scores)

Run the above code and the output will be:

 [Alice Bob Charlie]
 map[Alice:100 Bob:85 Charlie:92]

In this example, we first save the JSON data in the jsonStr variable. Then, use the () function to decode the JSON into the corresponding slice and map, and store the result in the corresponding variable. Finally, we print out the values ​​of these variables.

4. Custom encoding and decoding

Golang's encoding/json package provides a way to customize encoding and decoding, allowing flexible control of the serialization and deserialization process of JSON data. Through implementation and interface, the encoding and decoding behavior of fields can be customized.

For example, suppose we have a field of time type that we want to encode and decode in a specific date format in JSON. We can define a custom type and implement and interface.

 type CustomTime 
 ​
 func (ct CustomTime) MarshalJSON() ([]byte, error) {
     formatted := (ct).Format("2006-01-02")
     return []byte(`"` + formatted + `"`), nil
 }
 ​
 func (ct *CustomTime) UnmarshalJSON(data []byte) error {
     // Assume that the date format is "2006-01-02"     parsed, err := (`"2006-01-02"`, string(data))
     if err != nil {
         return err
     }
     *ct = CustomTime(parsed)
     return nil
 }

In the above code, we define a CustomTime type and implement the MarshalJSON() and UnmarshalJSON() methods for it. The MarshalJSON() method formats the time to the specified date format and encodes it. The UnmarshalJSON() method decodes the JSON data based on a specific date format and converts it to a time type.

Through custom encoding and decoding logic, we can flexibly process specific types of fields according to actual needs.

5. JSON Tag Options

In addition to specifying field names, the json tag provides additional options to further control the behavior of encoding and decoding. Here are some commonly used JSON tag options:

  • omitempty: If the value of the field is null (such as zero value, empty string, empty slice, etc.), the field is ignored during encoding.
  • string: Encode the field as a JSON string type, not its primitive type.
  • omitempty and string can be used in combination, for example json:"myField,omitempty,string".

Example:

 type Person struct {
     Name      string    `json:"name"`
     Age       int       `json:"age,omitempty"`
     BirthDate CustomTime `json:"birth_date,string"`
 }

In the above example, we define a Person structure where the Name field uses the default options for encoding and decoding, the Age field uses the omitempty option, and the BirthDate field uses the string option.

These options can help us more accurately control the encoding and decoding process of JSON data.

6. Handle nested structures

When dealing with complex data structures, structures may be nested with other structures. Golang's JSON encoding and decoding can automatically handle nested structures without additional configuration.

For example, suppose we have the following example code about dealing with nested structures:

 type Address struct {
     Street  string `json:"street"`
     City    string `json:"city"`
     Country string `json:"country"`
 }
 ​
 type Person struct {
     Name    string  `json:"name"`
     Age     int     `json:"age"`
     Address Address `json:"address"`
 }
 ​
 p := Person{
     Name: "Alice",
     Age:  25,
     Address: Address{
         Street:  "123 Main St",
         City:    "New York",
         Country: "USA",
     },
 }
 ​
 data, err := (p)
 if err != nil {
     (err)
 }
 (string(data))

In the above code, we define two structures: Address and Person. The Address structure is nested as one of the fields in the Person structure.

We created an instance p of Person and encoded it into JSON format. The () function automatically recursively encodes the nested structure into a nested JSON object.

The output will be:

 {"name":"Alice","age":25,"address":{"street":"123 Main St","city":"New York","country":"USA"}}

With Golang's JSON encoding and decoding capabilities, we can easily process complex data with nested structures.

7. Handle non-export fields

In Golang, non-exported (not starting with capital letters) structure fields are ignored by default during JSON encoding and decoding. This means that these fields are not encoded into and decoded from JSON.

If you need to deal with non-exported fields, you can use the json:"-" tag in the field's definition to indicate that the field is ignored. Alternatively, the encoding and decoding logic of non-exported fields can be handled by defining custom MarshalJSON and UnmarshalJSON methods.

 type Person struct {
     name string `json:"-"`
     Age  int    `json:"age"`
 }

In the above example, the name field is marked as ignored and will not participate in JSON encoding and decoding. The Age field will be encoded and decoded normally.

8. Handle empty values

In the JSON encoding and decoding process, the processing of null values ​​is an important consideration. Null values ​​include nil pointers, empty slices, empty maps, etc. Golang's encoding/json package provides options for handling null values.

When encoding, if the value of the field is null, the omitempty option can be used to indicate that the field is ignored during encoding. This is useful for reducing redundant information in JSON data.

When decoding, if the value of the field in the JSON data is null, you can use the pointer type or interface{} type to receive the decoded value. This distinguishes between null and non-null values.

Example:

 type Person struct {
     Name  string  `json:"name,omitempty"`
     Age   int     `json:"age,omitempty"`
     Extra *string `json:"extra,omitempty"`
 }
 ​
 jsonStr := `{"name":"Alice","age":null,"extra":"additional info"}`
 var p Person
 err := ([]byte(jsonStr), &p)
 if err != nil {
     (err)
 }
 ​
 ()   // Output: "Alice" ()    // Output: 0 ()  // Output: nil

In the above example, the Name field in the Person structure uses the omitempty option, so if the value of the field is an empty string when encoded, it will be ignored. The value of the Age field in JSON data is null, and after decoding, it will be set to the zero value of the type. The value of the Extra field in JSON data is "additional info", and is set to nil after decoding.

9. Handle circular references

Circular reference refers to the reference of objects in a data structure to each other, forming a closed loop. When coding and decoding JSON, handling circular references is a challenge.

Golang's encoding/json package does not support circular reference encoding and decoding by default, because it will lead to infinite recursion. If there is a data structure for circular references, additional processing is required to avoid circular references.

One way to deal with loop references is to use pointer types to break the loop. By defining the structure field as a pointer type, circular references can be avoided during JSON encoding and decoding.

Example:

 type Person struct {
     Name    string   `json:"name"`
     Friends []*Person `json:"friends"`
 }
 ​
 alice := &Person{Name: "Alice"}
 bob := &Person{Name: "Bob"}
 charlie := &Person{Name: "Charlie"}
 ​
  = []*Person{bob, charlie}
  = []*Person{alice}
  = []*Person{alice, bob}
 ​
 data, err := (alice)
 if err != nil {
     (err)
 }
 ​
 (string(data))

In the above example, the Friends field in the Person structure is defined as []*Person When performing JSON encoding, Golang's encoding/json package handles the loop reference and replaces the object in the loop reference with null.

Golang's encoding/json package cannot handle circular references by default when decoding JSON data. If there is a circular reference in the JSON data, the decoding process will go into infinite recursion and eventually lead to a stack overflow. To solve this problem, we can use type or custom decoding functions to handle circular references.

Using type, the original JSON data can be stored in the structure and then parsed in subsequent processing.

Example:

 type Person struct {
     Name    string          `json:"name"`
     Friends [] `json:"friends"`
 }
 ​
 jsonStr := `{"name":"Alice","friends":[
     {"name":"Bob","friends":null},
     {"name":"Charlie","friends":null}
 ]}`
 var p Person
 err := ([]byte(jsonStr), &p)
 if err != nil {
     (err)
 }
 ​
 ()     // Output: "Alice" ()  // Output: [{"name":"Bob","friends":null},{"name":"Charlie","friends":null}]

In the above example, the Friends field in the Person structure uses the type, which stores the original JSON data as a byte slice. In this way, we can parse these raw data in subsequent processing.

Custom decoding functions are another way to handle circular references. Through custom decoding functions, we can control the decoding process, process loop references and build the correct object relationship.

Example:

 type Person struct {
     Name    string   `json:"name"`
     Friends []*Person `json:"friends"`
 }
 ​
 func (p *Person) UnmarshalJSON(data []byte) error {
     type Alias Person
     aux := &struct {
         *Alias
         Friends []*Person `json:"friends"`
     }{
         Alias: (*Alias)(p),
     }
     if err := (data, &aux); err != nil {
         return err
     }
      = 
     return nil
 }
 ​
 jsonStr := `{"name":"Alice","friends":[
     {"name":"Bob","friends":null},
     {"name":"Charlie","friends":null}
 ]}`
 var p Person
 err := ([]byte(jsonStr), &p)
 if err != nil {
     (err)
 }
 ​
 ()            // Output: "Alice" ([0].Name) // Output: "Bob" ([1].Name) // Output: "Charlie"

In the above example, we define a custom decoding function UnmarshalJSON for the Person structure. During the decoding process, we use an auxiliary structure aux to receive the decoded JSON data and convert it into a Person structure. Then, assign the Friends field in the auxiliary structure to the Friends field of the original structure.

By using type or custom decoding functions, we can process JSON data containing loop references and successfully decode into the correct object structure.

10. Processing JSON data with uncertain structures

Sometimes, we may need to deal with JSON data with uncertain structures. In this case, Golang's encoding/json package provides the type and interface{} type to handle this uncertainty.

Types can be used to store raw JSON data and parse in subsequent processing. It can receive any legitimate JSON data and retain its original form.

Example:

 type Data struct {
     Name    string          `json:"name"`
     Payload  `json:"payload"`
 }
 ​
 jsonStr := `{"name":"Event","payload":{"type":"message","content":"Hello, world!"}}`
 var d Data
 err := ([]byte(jsonStr), &d)
 if err != nil {
     (err)
 }
 ​
 ()                   // Output: "Event" (string())        // Output: {"type":"message","content":"Hello, world!"}

In the above example, the Payload field in the Data structure uses a type, which stores the original JSON data as a byte slice. We can use the string() function to convert it to a string for printing or further parsing.

Another way to deal with uncertain structures is to use the interface{} type. The interface{} type can receive any type of value, including primitive types, structures, slices, etc. By using the interface{} type, we can process JSON data with an uncertain structure, but type assertions are required in subsequent processing.

Example:

 type Data struct {
     Name    string      `json:"name"`
     Payload interface{} `json:"payload"`
 }
 ​
 jsonStr := `{"name":"Event","payload":{"type":"message","content":"Hello, world!"}}`
 var d Data
 err := ([]byte(jsonStr), &d)
 if err != nil {
     (err)
 }
 ​
 ()                                // Output: "Event" payload, ok := .(map[string]interface{})
 if ok {
     (payload["type"].(string))          // Output: "message"     (payload["content"].(string))       // Output: "Hello, world!" }

In the above example, the Payload field in the Data structure uses the interface{} type, which can receive any type of value. In subsequent processing, we use type assertions to convert it to a specific type and perform further operations.

By using type and interface{} types, we can flexibly process JSON data with uncertain structures and parse and operate according to actual situations.

11. Summary

This article introduces in-depth JSON encoding and decoding technology in Golang. We understand the basic principles of JSON and the methods of handling JSON in Golang. Through sample code, we show how to use the encoding/json package for encoding and decoding operations, and by rationally applying these technologies, we can efficiently process large-scale structured data, improving the performance and efficiency of the software.

The above is the detailed content of in-depth analysis of JSON encoding and decoding in Golang. For more information about Golang JSON encoding and decoding, please pay attention to my other related articles!