Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
name: Go
on: [push]
jobs:
on: [push, pull_request]

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go 1.12
uses: actions/setup-go@v1
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.12
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v1
go-version: '1.21'
cache: true

- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
run: go mod download

- name: Build
run: go build -v .
run: go build -v ./...

- name: Test
run: go test -v -race ./...
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/devfeel/mapper

go 1.21
117 changes: 117 additions & 0 deletions mapper_generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package mapper

import (
"errors"
"reflect"
)

// ============ 泛型全局函数 (推荐使用) ============

// Map 同构类型映射 (泛型)
// 使用示例: mapper.Map(&User{}, &User{})
func Map[From any, To any](from *From, to *To) error {
if from == nil || to == nil {
return errors.New("from or to is nil")
}
return standardMapper.Mapper(from, to)
}

// MapTo 异构类型映射 (泛型)
// 使用示例: mapper.MapTo[Target](from, &target)
func MapTo[To any](from any, to *To) error {
if from == nil || to == nil {
return errors.New("from or to is nil")
}
return standardMapper.Mapper(from, to)
}

// MapSliceGeneric 泛型 Slice 映射
// 使用示例: mapper.MapSliceGeneric(users, &targets)
func MapSliceGeneric[From any, To any](fromSlice []From, toSlice *[]To) error {
if fromSlice == nil || toSlice == nil {
return errors.New("fromSlice or toSlice is nil")
}

result := make([]To, len(fromSlice))
for i, v := range fromSlice {
// 创建 From 指针
fromPtr := reflect.New(reflect.TypeOf(v)).Interface()
reflect.ValueOf(fromPtr).Elem().Set(reflect.ValueOf(v))

var target To
err := standardMapper.Mapper(fromPtr, &target)
if err != nil {
return err
}
result[i] = target
}
*toSlice = result
return nil
}

// MapToSliceGeneric 泛型 Map 转 Slice
// 使用示例: mapper.MapToSliceGeneric[Target](mapData, &targets)
func MapToSliceGeneric[T any](fromMap map[string]any, toSlice *[]T) error {
if fromMap == nil || toSlice == nil {
return errors.New("fromMap or toSlice is nil")
}

// 创建 T 类型的空切片
result := make([]T, 0)

for _, v := range fromMap {
if data, ok := v.(map[string]any); ok {
var target T
err := standardMapper.MapperMap(data, &target)
if err != nil {
return err
}
result = append(result, target)
}
}
*toSlice = result
return nil
}

// ============ 泛型 Mapper 实例 (可选) ============

// MapperGeneric 泛型 Mapper 简化实例
// 内部组合标准 mapper,复用反射逻辑
type MapperGeneric struct {
mapper IMapper
}

// NewMapperGeneric 创建泛型 Mapper 实例
func NewMapperGeneric() *MapperGeneric {
return &MapperGeneric{
mapper: standardMapper,
}
}

// Map 同构类型映射
func (m *MapperGeneric) Map(from, to any) error {
if from == nil || to == nil {
return errors.New("from or to is nil")
}
return m.mapper.Mapper(from, to)
}

// MapTo 异构类型映射 - 泛型方法
// 注意: Go 接口方法不支持泛型,因此这里使用 any 再内部转换
func (m *MapperGeneric) MapTo(to any, from any) error {
if from == nil || to == nil {
return errors.New("from or to is nil")
}
return m.mapper.Mapper(from, to)
}

// MapSlice 泛型 Slice 映射
func (m *MapperGeneric) MapSlice(fromSlice, toSlice any) error {
if fromSlice == nil || toSlice == nil {
return errors.New("fromSlice or toSlice is nil")
}
return m.mapper.MapperSlice(fromSlice, toSlice)
}

// MapperGenericInstance 全局泛型 Mapper 实例
var MapperGenericInstance = NewMapperGeneric()
149 changes: 149 additions & 0 deletions mapper_generic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package mapper

import (
"testing"
)

// 测试类型定义
type (
SourceUser struct {
Name string
Age int
}

TargetUser struct {
Name string
Age int
}

SourceWithTag struct {
UserName string `mapper:"name"`
UserAge int `mapper:"age"`
}

TargetWithTag struct {
Name string `mapper:"name"`
Age int `mapper:"age"`
}
)

func Test_Generic_Map(t *testing.T) {
from := &SourceUser{Name: "test", Age: 25}
to := &TargetUser{}

err := Map(from, to)
if err != nil {
t.Error("Map failed:", err)
}

if to.Name != from.Name || to.Age != from.Age {
t.Error("Map result not match", to, from)
} else {
t.Log("Map success:", to)
}
}

func Test_Generic_MapTo(t *testing.T) {
from := &SourceWithTag{UserName: "test", UserAge: 25}
to := &TargetWithTag{}

err := MapTo(from, to)
if err != nil {
t.Error("MapTo failed:", err)
}

if to.Name != from.UserName || to.Age != from.UserAge {
t.Error("MapTo result not match", to, from)
} else {
t.Log("MapTo success:", to)
}
}

func Test_Generic_MapSlice(t *testing.T) {
fromSlice := []SourceUser{
{Name: "user1", Age: 10},
{Name: "user2", Age: 20},
{Name: "user3", Age: 30},
}

var toSlice []TargetUser

err := MapSliceGeneric(fromSlice, &toSlice)
if err != nil {
t.Error("MapSliceGeneric failed:", err)
}

if len(toSlice) != 3 {
t.Error("MapSliceGeneric length not match")
} else {
t.Log("MapSliceGeneric success:", toSlice)
}
}

func Test_Generic_MapToSlice(t *testing.T) {
fromMap := map[string]any{
"user1": map[string]any{"Name": "user1", "Age": 10},
"user2": map[string]any{"Name": "user2", "Age": 20},
}

var toSlice []TargetUser

err := MapToSliceGeneric(fromMap, &toSlice)
if err != nil {
t.Error("MapToSliceGeneric failed:", err)
}

if len(toSlice) != 2 {
t.Error("MapToSliceGeneric length not match")
} else {
t.Log("MapToSliceGeneric success:", toSlice)
}
}

func Benchmark_Generic_Map(b *testing.B) {
from := &SourceUser{Name: "test", Age: 25}
to := &TargetUser{}

b.ResetTimer()
for i := 0; i < b.N; i++ {
Map(from, to)
}
}

func Benchmark_Traditional_Map(b *testing.B) {
from := &SourceUser{Name: "test", Age: 25}
to := &TargetUser{}

b.ResetTimer()
for i := 0; i < b.N; i++ {
Mapper(from, to)
}
}

func Benchmark_Generic_MapSlice(b *testing.B) {
fromSlice := make([]SourceUser, 100)
for i := 0; i < 100; i++ {
fromSlice[i] = SourceUser{Name: "user", Age: i}
}

var toSlice []TargetUser

b.ResetTimer()
for i := 0; i < b.N; i++ {
MapSliceGeneric(fromSlice, &toSlice)
}
}

func Benchmark_Traditional_MapperSlice(b *testing.B) {
fromSlice := make([]SourceUser, 100)
for i := 0; i < 100; i++ {
fromSlice[i] = SourceUser{Name: "user", Age: i}
}

toSlice := make([]TargetUser, 100)

b.ResetTimer()
for i := 0; i < b.N; i++ {
MapperSlice(fromSlice, toSlice)
}
}
Loading