Go’s JSON package (encoding/json) provides straightforward capabilities for marshaling and unmarshaling data structures. However, certain scenarios require customization, such as handling non-standard JSON formats or manipulating data during these transformations. Here, we'll explore how to implement custom marshaling and unmarshaling logic.
Marshaling and Unmarshaling Basics
Let's start with the basics. Marshaling is the process of converting Go data structures to JSON. Unmarshaling is the reverse, converting JSON data into Go data structures.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
Age int
}
func main() {
// Marshaling: Go struct to JSON
user := User{Name: "Alice", Age: 30}
jsonData, _ := json.Marshal(user)
fmt.Println(string(jsonData))
// Unmarshaling: JSON to Go struct
jsonString := `{"Name":"Bob","Age":25}`
var newUser User
json.Unmarshal([]byte(jsonString), &newUser)
fmt.Println(newUser)
}
Custom Marshaling
To implement custom marshaling, a data type must implement the json.Marshaler interface, which requires the MarshalJSON() method.
package main
import (
"encoding/json"
"fmt"
)
type Employee struct {
Name string
Salary int
Position string
}
func (e Employee) MarshalJSON() ([]byte, error) {
type Alias Employee // create an alias to avoid recursive calls
return json.Marshal(&struct {
Position string `json:"role"`
Alias
}{
Position: e.Position,
Alias: (Alias)(e),
})
}
func main() {
emp := Employee{Name: "Charlie", Salary: 50000, Position: "Engineer"}
jsonData, _ := json.Marshal(emp)
fmt.Println(string(jsonData)) // Output: {"Name":"Charlie","Salary":50000,"role":"Engineer"}
}
In this example, we changed the field name Position to role in the JSON output, showcasing a field renaming customization.
Custom Unmarshaling
To enact custom unmarshaling behavior, implement the json.Unmarshaler interface, which involves the UnmarshalJSON() method.
package main
import (
"encoding/json"
"fmt"
)
type Account struct {
ID int `json:"id"`
Name string `json:"name"`
Active bool `json:"active"`
}
func (a *Account) UnmarshalJSON(data []byte) error {
var temp struct {
ID int `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
}
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
a.ID = temp.ID
a.Name = temp.Name
a.Active = (temp.Status == "active") // Converts status string to boolean
return nil
}
func main() {
jsonString := `{"id":1,"name":"Roy","status":"active"}`
var acc Account
json.Unmarshal([]byte(jsonString), &acc)
fmt.Println(acc) // Output: {1 Roy true}
}
This example demonstrates custom behavior during unmarshaling, where a string field status from the JSON is converted into a boolean field Active in Go.
Combining Both Techniques
You can combine both custom marshaling and unmarshaling techniques in the same struct to handle complex data transformation requirements. This allows for full control over the JSON representation and parsing logic.
Customize these methods according to your application's needs to neatly control how data is transformed in and out of your Go programs.