Skip to content

Commit 8294a15

Browse files
committed
Document mixed type conversions
1 parent dd24a15 commit 8294a15

3 files changed

Lines changed: 104 additions & 29 deletions

File tree

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ go-model library provides [handy methods](#supported-methods) to process `struct
1919
* Get all `reflect.StructField` for given struct instance
2020
* Add global no traverse type to the list or use `notraverse` option in the struct field
2121
* Options to name map key, omit empty fields, and instruct not to traverse with struct/map/slice
22-
* Conversions between mixed types
22+
* Conversions between mixed non-pointer types
2323

2424
## Installation
2525

@@ -58,8 +58,8 @@ import (
5858
* Tags - [usage](#tags-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Tags)
5959
* AddNoTraverseType - [usage](#addnotraversetype--removenotraversetype-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#AddNoTraverseType)
6060
* RemoveNoTraverseType - [usage](#addnotraversetype--removenotraversetype-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#RemoveNoTraverseType)
61-
* AddConversion - TODO
62-
* RemoveConversion - TODO
61+
* AddConversion - [usage](#addconversion--removeconversion-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#AddConversion)
62+
* RemoveConversion - [usage](#addconversion--removeconversion-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#RemoveConversion)
6363

6464
#### Copy Method
6565
How do I copy my struct object into another? Not to worry, go-model does deep copy.
@@ -216,16 +216,21 @@ model.RemoveNoTraverseType(time.Location{}, &time.Location{})
216216

217217
#### AddConversion & RemoveConversion Method
218218

219-
TODO
220-
219+
This example registers a custom conversion from the `int` to the `string` type.
221220
```go
222221
AddConversion((*int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) {
223-
return reflect.ValueOf(strconv.Itoa(int(in.Int())) + "lala"), nil
222+
return reflect.ValueOf(strconv.FormatInt(in.Int(), 10)), nil
224223
})
225224
```
226225

227226
If a an integer field on the source struct matches the name of a string field on the target struct, the provided Converter method is invoked.
228227

228+
Note that if you want to register a converter from `int` to `*string` you will
229+
have to provide a pointer to a pointer as destination type ( `(**string)(nil)`
230+
).
231+
232+
More examples can be found in the [AddConversion godoc](https://godoc.org/github.com/jeevatkm/go-model#AddConversion).
233+
229234
## Versioning
230235
go-model releases versions according to [Semantic Versioning](http://semver.org)
231236

model.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616
)
1717

18+
// Converter is used to provide custom mappers for a datatype pair.
1819
type Converter func(in reflect.Value) (reflect.Value, error)
1920

2021
const (
@@ -45,7 +46,7 @@ var (
4546
NoTraverseTypeList map[reflect.Type]bool
4647

4748
// Type conversion functions at library level
48-
MapTypList map[reflect.Type]map[reflect.Type]Converter
49+
converterMap map[reflect.Type]map[reflect.Type]Converter
4950

5051
typeOfBytes = reflect.TypeOf([]byte(nil))
5152
typeOfInterface = reflect.TypeOf((*interface{})(nil)).Elem()
@@ -77,39 +78,34 @@ func extractType(x interface{}) reflect.Type {
7778
return reflect.TypeOf(x).Elem()
7879
}
7980

80-
// TODO
81-
// Register a converter form UUID to string:
82-
// model.AddConversion((*UUID)(nil), (*string)(nil), converter)
83-
// Register a converter from *UUID to string:
84-
// model.AddConversion((**UUID)(nil), (*string)(nil), converter)
85-
// TODO
81+
// AddConversion mothod allows registering a custom `Converter` into the global `converterMap`.
8682
func AddConversion(in interface{}, out interface{}, converter Converter) {
8783
srcType := extractType(in)
8884
targetType := extractType(out)
89-
if _, ok := MapTypList[srcType]; !ok {
90-
MapTypList[srcType] = map[reflect.Type]Converter{}
85+
if _, ok := converterMap[srcType]; !ok {
86+
converterMap[srcType] = map[reflect.Type]Converter{}
9187
}
92-
MapTypList[srcType][targetType] = converter
88+
converterMap[srcType][targetType] = converter
9389
}
9490

9591
// Remove registered conversions
9692
func RemoveConversion(in interface{}, out interface{}) {
9793
srcType := extractType(in)
9894
targetType := extractType(out)
99-
if _, ok := MapTypList[srcType]; !ok {
95+
if _, ok := converterMap[srcType]; !ok {
10096
return
10197
}
102-
if _, ok := MapTypList[srcType][targetType]; !ok {
98+
if _, ok := converterMap[srcType][targetType]; !ok {
10399
return
104100
}
105-
delete(MapTypList[srcType], targetType)
101+
delete(converterMap[srcType], targetType)
106102
}
107103

108104
func conversionExists(srcType reflect.Type, destType reflect.Type) bool {
109-
if _, ok := MapTypList[srcType]; !ok {
105+
if _, ok := converterMap[srcType]; !ok {
110106
return false
111107
}
112-
if _, ok := MapTypList[srcType][destType]; !ok {
108+
if _, ok := converterMap[srcType][destType]; !ok {
113109
return false
114110
}
115111
return true
@@ -597,7 +593,7 @@ func Kind(s interface{}, name string) (reflect.Kind, error) {
597593

598594
func init() {
599595
NoTraverseTypeList = map[reflect.Type]bool{}
600-
MapTypList = map[reflect.Type]map[reflect.Type]Converter{}
596+
converterMap = map[reflect.Type]map[reflect.Type]Converter{}
601597

602598
// Default NoTraverseTypeList
603599
// --------------------------
@@ -672,15 +668,16 @@ func doCopy(dv, sv reflect.Value) []error {
672668
// check dst field settable or not
673669
if dfv.CanSet() {
674670

675-
// handle embedded or nested struct
676671
if conversionExists(sfv.Type(), dfv.Type()) {
677-
res, err := MapTypList[sfv.Type()][dfv.Type()](sfv)
672+
// handle custom converters
673+
res, err := converterMap[sfv.Type()][dfv.Type()](sfv)
678674
if err != nil {
679675
errs = append(errs, err)
680676
} else {
681677
dfv.Set(res)
682678
}
683679
} else if isStruct(sfv) {
680+
// handle embedded or nested struct
684681

685682
if noTraverse {
686683
// This is struct kind and it's present in NoTraverseTypeList or

model_test.go

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
"time"
1414
)
1515

16+
//
17+
// Copy test cases
18+
//
19+
1620
func TestConverter(t *testing.T) {
1721
type SampleStructA struct {
1822
Int int
@@ -27,7 +31,7 @@ func TestConverter(t *testing.T) {
2731
}
2832

2933
AddConversion((*int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) {
30-
return reflect.ValueOf(strconv.Itoa(int(in.Int())) + "lala"), nil
34+
return reflect.ValueOf(strconv.FormatInt(in.Int(), 10) + "lala"), nil
3135
})
3236

3337
src := SampleStructB{Mixed: 123, Int: 5, String: "string"}
@@ -69,10 +73,6 @@ func TestMissingConverter(t *testing.T) {
6973
assertEqual(t, "string", dst.String)
7074
}
7175

72-
//
73-
// Copy test cases
74-
//
75-
7676
func TestCopyIntegerAndIntegerPtr(t *testing.T) {
7777
type SampleStruct struct {
7878
Int int
@@ -2023,3 +2023,76 @@ func logSrcDst(t *testing.T, src, dst interface{}) {
20232023
func logIt(t *testing.T, str string, v interface{}) {
20242024
t.Logf("%v: %#v", str, v)
20252025
}
2026+
2027+
// Examples
2028+
2029+
// Register a custom `Converter` to allow conversions from `int` to `string`.
2030+
func ExampleAddConversion() {
2031+
AddConversion((*int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) {
2032+
return reflect.ValueOf(strconv.FormatInt(in.Int(), 10)), nil
2033+
})
2034+
type StructA struct {
2035+
Mixed string
2036+
}
2037+
2038+
type StructB struct {
2039+
Mixed int
2040+
}
2041+
src := StructB{Mixed: 123}
2042+
dst := StructA{}
2043+
2044+
errs := Copy(&dst, &src)
2045+
if errs != nil {
2046+
panic(errs)
2047+
}
2048+
fmt.Printf("%v", dst)
2049+
// Output: {123}
2050+
}
2051+
2052+
// Register a custom `Converter` to allow conversions from `*int` to `string`.
2053+
func ExampleAddConversion_SourcePointer() {
2054+
AddConversion((**int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) {
2055+
return reflect.ValueOf(strconv.FormatInt(in.Elem().Int(), 10)), nil
2056+
})
2057+
type StructA struct {
2058+
Mixed string
2059+
}
2060+
2061+
type StructB struct {
2062+
Mixed *int
2063+
}
2064+
val := 123
2065+
src := StructB{Mixed: &val}
2066+
dst := StructA{}
2067+
2068+
errs := Copy(&dst, &src)
2069+
if errs != nil {
2070+
panic(errs[0])
2071+
}
2072+
fmt.Printf("%v", dst)
2073+
// Output: {123}
2074+
}
2075+
2076+
// Register a custom `Converter` to allow conversions from `int` to `*string`.
2077+
func ExampleAddConversion_DestinationPointer() {
2078+
AddConversion((*int)(nil), (**string)(nil), func(in reflect.Value) (reflect.Value, error) {
2079+
str := strconv.FormatInt(in.Int(), 10)
2080+
return reflect.ValueOf(&str), nil
2081+
})
2082+
type StructA struct {
2083+
Mixed *string
2084+
}
2085+
2086+
type StructB struct {
2087+
Mixed int
2088+
}
2089+
src := StructB{Mixed: 123}
2090+
dst := StructA{}
2091+
2092+
errs := Copy(&dst, &src)
2093+
if errs != nil {
2094+
panic(errs[0])
2095+
}
2096+
fmt.Printf("%v", *dst.Mixed)
2097+
// Output: 123
2098+
}

0 commit comments

Comments
 (0)