The gconv
conversion component provides powerful type conversion functionality, allowing developers to customize conversion logic between types. It offers two main custom conversion method registration mechanisms: RegisterTypeConverterFunc
and RegisterAnyConverterFunc
.
Custom type conversion does not support custom conversion of basic types (such as int
, string
, etc.), and only supports complex data structure types (such as slice
, map
, struct
, etc.). This design is to improve conversion efficiency.
RegisterTypeConverterFunc
Function Definition
The RegisterTypeConverterFunc
method is used to register conversion functions between specific types, defined as follows:
func RegisterTypeConverterFunc(fn interface{}) (err error)
The signature of the conversion function must conform to the following format:
func(T1) (*T2, error)
Where T1
needs to be a non-pointer object, and T2
needs to be a pointer type. If the types are incorrect, the conversion method registration will report an error.
The design of requiring the input parameter (T1
) to be a non-pointer object is to ensure the safety of the input parameter and to avoid modifying the input parameter within the conversion method, which could cause problems outside the scope.
Usage Examples
Struct Type Conversion
The most common application scenario for RegisterTypeConverterFunc
is type conversion between structs. Let's look at an example using RegisterTypeConverterFunc
:
package main
import (
"fmt"
"time"
"github.com/gogf/gf/v2/util/gconv"
)
// UserInfo custom type
type UserInfo struct {
ID int
Name string
Birthday time.Time
}
type UserDTO struct {
UserID string
UserName string
Age int
}
func userInfoT2UserDTO(in UserInfo) (*UserDTO, error) {
if in.ID <= 0 {
return nil, fmt.Errorf("invalid user ID: %d", in.ID)
}
// Calculate age
age := time.Now().Year() - in.Birthday.Year()
return &UserDTO{
UserID: fmt.Sprintf("U%d", in.ID),
UserName: in.Name,
Age: age,
}, nil
}
func main() {
converter := gconv.NewConverter()
// Register custom type conversion function - from UserInfo to UserDTO
err := converter.RegisterTypeConverterFunc(userInfoT2UserDTO)
if err != nil {
fmt.Println("Failed to register conversion function:", err)
return
}
// Use custom type conversion
userInfo := UserInfo{
ID: 101,
Name: "Zhang San",
Birthday: time.Date(1990, 5, 15, 0, 0, 0, 0, time.Local),
}
var userDTO UserDTO
err = converter.Scan(userInfo, &userDTO)
if err != nil {
fmt.Println("Conversion failed:", err)
} else {
fmt.Printf("Custom type conversion result: %#v\n", userDTO)
// Output similar to: Custom type conversion result: main.UserDTO{UserID:"U101", UserName:"Zhang San", Age:35}
}
// Test error handling
invalidUser := UserInfo{ID: -1, Name: "Invalid User"}
err = converter.Scan(invalidUser, &userDTO)
if err != nil {
fmt.Println("Expected error:", err) // Output error message: invalid user ID: -1
}
}
In this example code, two type conversion scenarios are demonstrated: custom struct conversion, and automatic conversion of structs as properties. The conversion method uses the general struct conversion method gconv.Scan
, which in its internal implementation will automatically determine if there is a custom type conversion function, will prioritize using the custom type conversion function, otherwise will follow the default conversion logic.
Alias Type Conversion
We can also use RegisterTypeConverterFunc
to implement conversion of alias types. Here is an example:
package main
import (
"fmt"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type MyTime = *gtime.Time
type Src struct {
A MyTime
}
type Dst struct {
B string
}
type SrcWrap struct {
Value Src
}
type DstWrap struct {
Value Dst
}
// MyTimeToDstConverter custom conversion function from MyTime to Dst
func MyTimeToDstConverter(src MyTime) (dst *Dst, err error) {
if src == nil {
return &Dst{B: ""}, nil
}
return &Dst{B: src.String()}, nil
}
func main() {
// Register custom conversion function
err := gconv.RegisterTypeConverterFunc(MyTimeToDstConverter)
if err != nil {
panic(err)
}
// Create test data
now := gtime.Now()
src := Src{A: now}
// Execute conversion
var dst *Dst
err = gconv.Scan(src, &dst)
if err != nil {
panic(err)
}
fmt.Println("Original time:", now)
fmt.Println("Conversion result:", dst.B)
}
RegisterAnyConverterFunc
RegisterAnyConverterFunc
provides a more flexible type conversion mechanism, allowing one conversion function to handle conversions between multiple types. This is especially useful when dealing with complex data structure conversion needs.
Function Definition
The RegisterAnyConverterFunc
method provides a more flexible type conversion mechanism, allowing handling of conversions between multiple types, defined as follows:
func RegisterAnyConverterFunc(f AnyConvertFunc, types ...reflect.Type)
Where the definition of AnyConvertFunc
is:
// AnyConvertFunc is the function type for converting any to specified type.
type AnyConvertFunc func(from any, to reflect.Value) error
This approach allows developers to register a general conversion function that can handle conversions between multiple types.
Usage Examples
Struct Conversion Example
Here is a basic example using RegisterAnyConverterFunc
, demonstrating how to implement custom struct conversion:
package main
import (
"fmt"
"reflect"
"github.com/gogf/gf/v2/util/gconv"
)
// UserInput source struct
type UserInput struct {
Name string
Age int
IsActive bool
}
// UserModel target struct
type UserModel struct {
ID int
FullName string
Age int
Status int
}
func userInput2Model(in any, out reflect.Value) error {
// Check if output type is UserModel
if out.Type() == reflect.TypeOf(&UserModel{}) {
// Handle conversion from UserInput to UserModel
if input, ok := in.(UserInput); ok {
model := UserModel{
ID: 1,
FullName: input.Name,
Age: input.Age,
Status: 0,
}
if input.IsActive {
model.Status = 1
}
out.Elem().Set(reflect.ValueOf(model))
return nil
}
return fmt.Errorf("conversion from %T to UserModel not supported", in)
}
return fmt.Errorf("conversion to %s not supported", out.Type())
}
func main() {
// Create converter
converter := gconv.NewConverter()
// Register custom conversion function
converter.RegisterAnyConverterFunc(userInput2Model, reflect.TypeOf(UserModel{}))
// Test conversion
var (
model UserModel
input = UserInput{Name: "Zhang San", Age: 30, IsActive: true}
)
err := converter.Scan(input, &model)
if err != nil {
fmt.Println("Conversion failed:", err)
} else {
fmt.Printf(
"Conversion result: ID=%d, Name=%s, Age=%d, Status=%d\n",
model.ID, model.FullName, model.Age, model.Status,
)
}
}
Complex Nested Struct Conversion Example
Here is a more complex example with nested data structures, demonstrating how to use RegisterAnyConverterFunc
to handle complex nested struct conversions:
package main
import (
"fmt"
"reflect"
"github.com/gogf/gf/v2/util/gconv"
)
// UserDetail source data structure
type UserDetail struct {
UserID int
Name string
Address Address
}
// UserDTO target data structure
type UserDTO struct {
ID string
DisplayName string
Location string
}
type Address struct {
City string
Country string
}
func userDetail2DTO(in any, out reflect.Value) error {
// Check if output type is UserDTO
if out.Type() == reflect.TypeOf(&UserDTO{}) {
// Handle conversion from UserDetail to UserDTO
if detail, ok := in.(UserDetail); ok {
dto := UserDTO{
ID: fmt.Sprintf("USER-%d", detail.UserID),
DisplayName: detail.Name,
Location: fmt.Sprintf("%s, %s", detail.Address.City, detail.Address.Country),
}
out.Elem().Set(reflect.ValueOf(dto))
return nil
}
return fmt.Errorf("conversion from %T to UserDTO not supported", in)
}
return fmt.Errorf("conversion to %s not supported", out.Type())
}
func main() {
// Create converter
converter := gconv.NewConverter()
// Register custom conversion function
converter.RegisterAnyConverterFunc(userDetail2DTO, reflect.TypeOf(UserDTO{}))
// Test conversion
var (
dto UserDTO
detail = UserDetail{
UserID: 12345,
Name: "Zhang San",
Address: Address{
City: "Beijing",
Country: "China",
},
}
)
if err := converter.Scan(detail, &dto); err != nil {
fmt.Println("Conversion failed:", err)
} else {
fmt.Printf(
"Conversion result: ID=%s, Name=%s, Address=%s\n",
dto.ID, dto.DisplayName, dto.Location,
)
}
}
This example demonstrates how to use RegisterAnyConverterFunc
to implement complex nested struct conversion, including conversion from UserDetail
to UserDTO
, and conversion from nested map
to UserDTO
. This kind of complex data structure conversion is the main application scenario for RegisterAnyConverterFunc
.
Comparison of Two Conversion Functions
The two registration methods each have their advantages, disadvantages, and applicable scenarios:
RegisterTypeConverterFunc
Advantages:
- Type safety: Since the function signature has already defined the input and output types, the compiler can perform type checking at compile time.
- Simple to use: Definition and usage are relatively intuitive, no need to manually handle reflection.
- Suitable for specific type conversion: When you only need to handle conversion between two specific types, this method is clearer.
Disadvantages:
- Limited flexibility: Each function can only handle conversion from one specific type to another specific type.
- Multiple functions needed: If you need to handle conversions of multiple types, you need to define and register multiple functions.
RegisterAnyConverterFunc
Advantages:
- Highly flexible: One function can handle conversions from multiple input types to a specific output type.
- Code reuse: Related conversion logic can be centralized in one function, reducing duplicate code.
- Dynamic judgment: Can dynamically decide how to convert based on input type at runtime.
Disadvantages:
- Reduced type safety: Using reflection and interface types, the compiler cannot check for type errors.
- Complex to use: Need to manually handle reflection and type assertions, increasing complexity.