├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── go.mod ├── model.go ├── model_example_test.go ├── model_test.go ├── tag.go ├── tag_test.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | coverage.out 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | branches: 6 | only: 7 | - master 8 | - develop 9 | 10 | go: 11 | # for saving travis resource commented out few versions test 12 | #- 1.2 13 | #- 1.3 14 | #- 1.4 15 | #- 1.5 16 | #- 1.6 17 | #- 1.7 18 | #- 1.8 19 | - 1.9 20 | - "1.10" 21 | - 1.11 22 | - tip 23 | 24 | install: 25 | - go get -v ./... 26 | 27 | script: 28 | - go test -v ./... -coverprofile=coverage.txt -covermode=atomic 29 | 30 | after_success: 31 | - bash <(curl -s https://codecov.io/bash) 32 | 33 | matrix: 34 | allow_failures: 35 | - go: tip 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Jeevanandam M., https://myjeeva.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-model [![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html) [![Build Status](https://travis-ci.org/jeevatkm/go-model.svg?branch=master)](https://travis-ci.org/jeevatkm/go-model) [![codecov](https://codecov.io/gh/jeevatkm/go-model/branch/master/graph/badge.svg)](https://codecov.io/gh/jeevatkm/go-model/branch/master) [![GoReport](https://goreportcard.com/badge/jeevatkm/go-model)](https://goreportcard.com/report/jeevatkm/go-model) [![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/jeevatkm/go-model/releases/latest) [![GoDoc](https://godoc.org/github.com/jeevatkm/go-model?status.svg)](https://godoc.org/github.com/jeevatkm/go-model) [![License](https://img.shields.io/github/license/jeevatkm/go-model.svg)](LICENSE) 2 | 3 | Robust & Easy to use model mapper and utility methods for Go `struct`. Typical methods increase productivity and make Go development more fun :smile: 4 | 5 | ***v1.1.0 [released](https://github.com/jeevatkm/go-model/releases/latest) and tagged on Aug 27, 2018*** 6 | 7 | go-model tested with Go `v1.2` and above. 8 | 9 | ## Features 10 | go-model library provides [handy methods](#supported-methods) to process `struct` with below highlighted features. It's born from typical need while developing Go application or utility. I hope it's helpful to Go community! 11 | * Embedded/Anonymous struct 12 | * Multi-level nested struct/map/slice 13 | * Pointer and non-pointer within struct/map/slice 14 | * Struct within map and slice 15 | * Embedded/Anonymous struct fields appear in map at same level as represented by Go 16 | * Interface within struct/map/slice 17 | * Get struct field `reflect.Kind` by field name 18 | * Get all the struct field tags (`reflect.StructTag`) or selectively by field name 19 | * Get all `reflect.StructField` for given struct instance 20 | * Get or Set by individual field name on struct 21 | * Add global no traverse type to the list or use `notraverse` option in the struct field 22 | * Options to name map key, omit empty fields, and instruct not to traverse with struct/map/slice 23 | * Conversions between mixed non-pointer types - add custom conversation method, refer to usage 24 | 25 | ## Installation 26 | 27 | #### Stable Version - Production Ready 28 | Please refer section [Versioning](#versioning) for detailed info. 29 | 30 | **go.mod** 31 | ```sh 32 | require gopkg.in/jeevatkm/go-model.v1 v1.1.0 33 | ``` 34 | 35 | **go get** 36 | ```sh 37 | go get -u gopkg.in/jeevatkm/go-model.v1 38 | ``` 39 | 40 | ## It might be beneficial for your project :smile: 41 | 42 | go-model author also published following projects to Go Community. 43 | 44 | * [aah framework](https://aahframework.org) - A secure, flexible, rapid Go web framework. 45 | * [THUMBAI](https://thumbai.app), [Source Code](https://github.com/thumbai/thumbai) - Go Mod Repository, Go Vanity Service and Simple Proxy Server. 46 | * [go-resty](https://github.com/go-resty/resty) - Simple HTTP and REST client for Go. 47 | 48 | 49 | ## Usage 50 | Import go-model into your code and refer it as `model`. Have a look on [model test cases](model_test.go) to know more possibilities. 51 | ```go 52 | import ( 53 | "gopkg.in/jeevatkm/go-model.v1" 54 | ) 55 | ``` 56 | 57 | ### Supported Methods 58 | * Copy - [usage](#copy-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Copy) 59 | * Map - [usage](#map-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Map) 60 | * Clone - [usage](#clone-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Clone) 61 | * IsZero - [usage](#iszero-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#IsZero) 62 | * HasZero - [usage](#haszero-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#HasZero) 63 | * IsZeroInFields - [usage](#iszeroinfields-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#IsZeroInFields) 64 | * Fields - [usage](#fields-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Fields) 65 | * Kind - [usage](#kind-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Kind) 66 | * Tag - [usage](#tag-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Tag) 67 | * Tags - [usage](#tags-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Tags) 68 | * Get - [usage](#get-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Get) 69 | * Set - [usage](#set-method), [godoc](https://godoc.org/github.com/jeevatkm/go-model#Set) 70 | * AddNoTraverseType - [usage](#addnotraversetype--removenotraversetype-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#AddNoTraverseType) 71 | * RemoveNoTraverseType - [usage](#addnotraversetype--removenotraversetype-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#RemoveNoTraverseType) 72 | * AddConversion - [usage](#addconversion--removeconversion-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#AddConversion) 73 | * RemoveConversion - [usage](#addconversion--removeconversion-methods), [godoc](https://godoc.org/github.com/jeevatkm/go-model#RemoveConversion) 74 | 75 | #### Copy Method 76 | How do I copy my struct object into another? Not to worry, go-model does deep copy. 77 | ```go 78 | // let's say you have just decoded/unmarshalled your request body to struct object. 79 | tempProduct, _ := myapp.ParseJSON(request.Body) 80 | 81 | product := Product{} 82 | 83 | // tag your Product fields with appropriate options like 84 | // -, omitempty, notraverse to get desired result. 85 | // Not to worry, go-model does deep copy :) 86 | errs := model.Copy(&product, tempProduct) 87 | fmt.Println("Errors:", errs) 88 | 89 | fmt.Printf("\nSource: %#v\n", tempProduct) 90 | fmt.Printf("\nDestination: %#v\n", product) 91 | ``` 92 | 93 | #### Map Method 94 | I want to convert my struct into Map (`map[string]interface{}`). Sure, go-model does deep convert. 95 | ```go 96 | // tag your SearchResult fields with appropriate options like 97 | // -, name, omitempty, notraverse to get desired result. 98 | sr, _ := myapp.GetSearchResult( /* params here */ ) 99 | 100 | // Embedded/Anonymous struct fields appear in map at same level as represented by Go 101 | srchResMap, err := model.Map(sr) 102 | fmt.Println("Error:", err) 103 | 104 | fmt.Printf("\nSearch Result Map: %#v\n", srchResMap) 105 | ``` 106 | 107 | #### Clone Method 108 | I would like to clone my struct object. That's nice, you know go-model does deep processing. 109 | ```go 110 | input := Product { /* Product struct field values go here */ } 111 | 112 | // have your struct fields tagged appropriately. Options like 113 | // -, name, omitempty, notraverse to get desired result. 114 | clonedObj := model.Clone(input) 115 | 116 | // let's see the result 117 | fmt.Printf("\nCloned Object: %#v\n", clonedObj) 118 | ``` 119 | 120 | #### IsZero Method 121 | I want to check my struct object is empty or not. Of course, go-model does deep zero check. 122 | ```go 123 | // let's say you have just decoded/unmarshalled your request body to struct object. 124 | productInfo, _ := myapp.ParseJSON(request.Body) 125 | 126 | // wanna check productInfo is empty or not 127 | isEmpty := model.IsZero(productInfo) 128 | 129 | // tag your ProductInfo fields with appropriate options like 130 | // -, omitempty, notraverse to get desired result. 131 | fmt.Println("Hey, I have all fields zero value:", isEmpty) 132 | ``` 133 | 134 | #### HasZero Method 135 | I want to check my struct object has any zero/empty value. Of course, go-model does deep zero check. 136 | ```go 137 | // let's say you have just decoded/unmarshalled your request body to struct object. 138 | productInfo, _ := myapp.ParseJSON(request.Body) 139 | 140 | // wanna check productInfo is empty or not 141 | isEmpty := model.HasZero(productInfo) 142 | 143 | // tag your ProductInfo fields with appropriate options like 144 | // -, omitempty, notraverse to get desired result. 145 | fmt.Println("Hey, I have zero values:", isEmpty) 146 | ``` 147 | 148 | #### IsZeroInFields Method 149 | Is it possible to check to particular fields has zero/empty values. Of-course you can. 150 | ```go 151 | // let's say you have just decoded/unmarshalled your request body to struct object. 152 | product, _ := myapp.ParseJSON(request.Body) 153 | 154 | // check particular fields has zero value or not 155 | fieldName, isEmpty := model.IsZeroInFields(product, "SKU", "Title", "InternalIdentifier") 156 | 157 | fmt.Println("Empty Field Name:", fieldName) 158 | fmt.Println("Yes, I have zero value:", isEmpty) 159 | ``` 160 | 161 | #### Fields Method 162 | You wanna all the fields from `struct`, Yes you can have it :) 163 | ```go 164 | src := SampleStruct { 165 | /* struct fields go here */ 166 | } 167 | 168 | fields, _ := model.Fields(src) 169 | fmt.Println("Fields:", fields) 170 | ``` 171 | 172 | #### Kind Method 173 | go-model library provides an ability to know the `reflect.Kind` in as easy way. 174 | ```go 175 | src := SampleStruct { 176 | /* struct fields go here */ 177 | } 178 | 179 | fieldKind, _ := model.Kind(src, "BookingInfoPtr") 180 | fmt.Println("Field kind:", fieldKind) 181 | ``` 182 | 183 | #### Tag Method 184 | I want to get Go lang supported Tag value from my `struct`. Yes, it is easy to get it. 185 | ```go 186 | src := SampleStruct { 187 | BookCount int `json:"-"` 188 | BookCode string `json:"-"` 189 | ArchiveInfo BookArchive `json:"archive_info,omitempty"` 190 | Region BookLocale `json:"region,omitempty"` 191 | } 192 | 193 | tag, _ := model.Tag(src, "ArchiveInfo") 194 | fmt.Println("Tag Value:", tag.Get("json")) 195 | 196 | // Output: 197 | Tag Value: archive_info,omitempty 198 | ``` 199 | 200 | #### Tags Method 201 | I would like to get all the fields Tag values from my `struct`. It's easy. 202 | ```go 203 | src := SampleStruct { 204 | BookCount int `json:"-"` 205 | BookCode string `json:"-"` 206 | ArchiveInfo BookArchive `json:"archive_info,omitempty"` 207 | Region BookLocale `json:"region,omitempty"` 208 | } 209 | 210 | tags, _ := model.Tags(src) 211 | fmt.Println("Tags:", tags) 212 | ``` 213 | 214 | #### Get Method 215 | I want to get value by field name on my `struct`. Yes, it is easy to get it. 216 | ```go 217 | src := SampleStruct { 218 | BookCount: 100, 219 | BookCode: "GHT67HH00", 220 | } 221 | 222 | value, _ := model.Get(src, "BookCode") 223 | fmt.Println("Value:", value) 224 | 225 | // Output: 226 | Value: GHT67HH00 227 | ``` 228 | 229 | #### Set Method 230 | I want to set value by field name on my `struct`. Yes, it is easy to get it. 231 | ```go 232 | src := SampleStruct { 233 | BookCount: 100, 234 | BookCode: "GHT67HH00", 235 | } 236 | 237 | err := model.Set(&src, "BookCount", 200) 238 | fmt.Println("Error:", err) 239 | ``` 240 | 241 | #### AddNoTraverseType & RemoveNoTraverseType Methods 242 | There are scenarios, where you want the object values but not to traverse/look inside the struct object. Use `notraverse` option in the model tag for those fields or Add it `NoTraverseTypeList`. Customize it as per your need. 243 | 244 | Default `NoTraverseTypeList` has these types `time.Time{}`, `&time.Time{}`, `os.File{}`, `&os.File{}`, `http.Request{}`, `&http.Request{}`, `http.Response{}`, `&http.Response{}`. 245 | ```go 246 | // If you have added your type into list then you need not mention `notraverse` option for those types. 247 | 248 | // Adding type into NoTraverseTypeList 249 | model.AddNoTraverseType(time.Location{}, &time.Location{}) 250 | 251 | // Removing type from NoTraverseTypeList 252 | model.RemoveNoTraverseType(time.Location{}, &time.Location{}) 253 | ``` 254 | 255 | #### AddConversion & RemoveConversion Methods 256 | 257 | This example registers a custom conversion from the `int` to the `string` type. 258 | ```go 259 | AddConversion((*int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) { 260 | return reflect.ValueOf(strconv.FormatInt(in.Int(), 10)), nil 261 | }) 262 | ``` 263 | 264 | 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. 265 | 266 | Note that if you want to register a converter from `int` to `*string` you will 267 | have to provide a pointer to a pointer as destination type ( `(**string)(nil)` 268 | ). 269 | 270 | More examples can be found in the [AddConversion godoc](https://godoc.org/github.com/jeevatkm/go-model#AddConversion). 271 | 272 | ## Versioning 273 | go-model releases versions according to [Semantic Versioning](http://semver.org) 274 | 275 | `gopkg.in/jeevatkm/go-model.vX` points to appropriate tag versions; `X` denotes version number and it's a stable release. It's recommended to use version, for eg. `gopkg.in/jeevatkm/go-model.v0`. Development takes place at the master branch. Although the code in master should always compile and test successfully, it might break API's. We aim to maintain backwards compatibility, but API's and behaviour might be changed to fix a bug. 276 | 277 | ## Contributing 278 | Welcome! If you find any improvement or issue you want to fix, feel free to send a pull request. I like pull requests that include test cases for fix/enhancement. I have done my best to bring pretty good code coverage. Feel free to write tests. 279 | 280 | BTW, I'd like to know what you think about go-model. Kindly open an issue or send me an email; it'd mean a lot to me. 281 | 282 | ## Author 283 | Jeevanandam M. - jeeva@myjeeva.com 284 | 285 | ## Contributors 286 | Have a look on [Contributors](https://github.com/jeevatkm/go-model/graphs/contributors) page. 287 | 288 | ## License 289 | go-model released under MIT license, refer [LICENSE](LICENSE) file. 290 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gopkg.in/jeevatkm/go-model.v1 2 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jeevanandam M. (https://github.com/jeevatkm). 2 | // go-model source code and usage is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package model provides robust and easy-to-use model mapper and utility methods for Go. 6 | // These typical methods increase productivity and make Go development more fun :) 7 | package model 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "net/http" 13 | "os" 14 | "reflect" 15 | "time" 16 | ) 17 | 18 | // Converter is used to provide custom mappers for a datatype pair. 19 | type Converter func(in reflect.Value) (reflect.Value, error) 20 | 21 | const ( 22 | // TagName is used to mention field options for go-model library. 23 | // 24 | // Example: 25 | // -------- 26 | // BookCount int `model:"bookCount"` 27 | // ArchiveInfo StoreInfo `model:"archiveInfo,notraverse"` 28 | TagName = "model" 29 | 30 | // OmitField value is used to omit field(s) from processing 31 | OmitField = "-" 32 | 33 | // OmitEmpty option is used skip field(s) from output if it's zero value 34 | OmitEmpty = "omitempty" 35 | 36 | // NoTraverse option makes sure the go-model library to not to traverse inside the struct object. 37 | // However, the field value will be evaluated or processed by library. 38 | NoTraverse = "notraverse" 39 | ) 40 | 41 | var ( 42 | // Version # of go-model library 43 | Version = "1.1.0" 44 | 45 | // NoTraverseTypeList keeps track of no-traverse type list at library level 46 | noTraverseTypeList map[reflect.Type]bool 47 | 48 | // Type conversion functions at library level 49 | converterMap map[reflect.Type]map[reflect.Type]Converter 50 | 51 | typeOfBytes = reflect.TypeOf([]byte(nil)) 52 | typeOfInterface = reflect.TypeOf((*interface{})(nil)).Elem() 53 | ) 54 | 55 | // AddNoTraverseType method adds the Go Lang type into global `NoTraverseTypeList`. 56 | // The type(s) from list is considered as "No Traverse" type by go-model library 57 | // for model mapping process. See also `RemoveNoTraverseType()` method. 58 | // model.AddNoTraverseType(time.Time{}, &time.Time{}, os.File{}, &os.File{}) 59 | // 60 | // Default NoTraverseTypeList: time.Time{}, &time.Time{}, os.File{}, &os.File{}, 61 | // http.Request{}, &http.Request{}, http.Response{}, &http.Response{} 62 | // 63 | func AddNoTraverseType(i ...interface{}) { 64 | for _, v := range i { 65 | t := reflect.TypeOf(v) 66 | if _, ok := noTraverseTypeList[t]; ok { 67 | 68 | // already registered for no traverse, move on 69 | continue 70 | } 71 | 72 | // not found, add it 73 | noTraverseTypeList[t] = true 74 | } 75 | } 76 | 77 | // RemoveNoTraverseType method is used to remove Go Lang type from the `NoTraverseTypeList`. 78 | // See also `AddNoTraverseType()` method. 79 | // model.RemoveNoTraverseType(http.Request{}, &http.Request{}) 80 | // 81 | func RemoveNoTraverseType(i ...interface{}) { 82 | for _, v := range i { 83 | t := reflect.TypeOf(v) 84 | if _, ok := noTraverseTypeList[t]; ok { 85 | 86 | // found, delete it 87 | delete(noTraverseTypeList, t) 88 | } 89 | } 90 | } 91 | 92 | // AddConversion mothod allows registering a custom `Converter` into the global `converterMap` 93 | // by supplying pointers of the target types. 94 | func AddConversion(in interface{}, out interface{}, converter Converter) { 95 | srcType := extractType(in) 96 | targetType := extractType(out) 97 | AddConversionByType(srcType, targetType, converter) 98 | } 99 | 100 | // AddConversionByType allows registering a custom `Converter` into golbal `converterMap` by types. 101 | func AddConversionByType(srcType reflect.Type, targetType reflect.Type, converter Converter) { 102 | if _, ok := converterMap[srcType]; !ok { 103 | converterMap[srcType] = map[reflect.Type]Converter{} 104 | } 105 | converterMap[srcType][targetType] = converter 106 | } 107 | 108 | // RemoveConversion registered conversions 109 | func RemoveConversion(in interface{}, out interface{}) { 110 | srcType := extractType(in) 111 | targetType := extractType(out) 112 | if _, ok := converterMap[srcType]; !ok { 113 | return 114 | } 115 | if _, ok := converterMap[srcType][targetType]; !ok { 116 | return 117 | } 118 | delete(converterMap[srcType], targetType) 119 | } 120 | 121 | // IsZero method returns `true` if all the exported fields in a given `struct` 122 | // are zero value otherwise `false`. If input is not a struct, method returns `false`. 123 | // 124 | // A "model" tag with the value of "-" is ignored by library for processing. 125 | // Example: 126 | // 127 | // // Field is ignored by go-model processing 128 | // BookCount int `model:"-"` 129 | // BookCode string `model:"-"` 130 | // 131 | // A "model" tag value with the option of "notraverse"; library will not traverse 132 | // inside the struct object. However, the field value will be evaluated whether 133 | // it's zero value or not. 134 | // Example: 135 | // 136 | // // Field is not traversed but value is evaluated/processed 137 | // ArchiveInfo BookArchive `model:"archiveInfo,notraverse"` 138 | // Region BookLocale `model:",notraverse"` 139 | // 140 | func IsZero(s interface{}) bool { 141 | if s == nil { 142 | return true 143 | } 144 | 145 | sv, err := structValue(s) 146 | if err != nil { 147 | return false 148 | } 149 | 150 | fields := modelFields(sv) 151 | 152 | for _, f := range fields { 153 | fv := sv.FieldByName(f.Name) 154 | tag := newTag(f.Tag.Get(TagName)) 155 | 156 | if tag.isOmitField() { 157 | continue 158 | } 159 | 160 | // embedded or nested struct 161 | if isStruct(fv) { 162 | // check type is in NoTraverseTypeList or has 'notraverse' tag option 163 | if isNoTraverseType(fv) || tag.isNoTraverse() { 164 | 165 | // not traversing inside, but evaluating a value 166 | if !isFieldZero(fv) { 167 | return false 168 | } 169 | 170 | continue 171 | } 172 | 173 | if !IsZero(fv.Interface()) { 174 | return false 175 | } 176 | 177 | continue 178 | } 179 | 180 | if !isFieldZero(fv) { 181 | return false 182 | } 183 | } 184 | 185 | return true 186 | } 187 | 188 | // IsZeroInFields method verifies the value for the given list of field names against 189 | // given struct. Method returns `Field Name` and `true` for the zero value field. 190 | // Otherwise method returns empty `string` and `false`. 191 | // 192 | // Note: 193 | // [1] This method doesn't traverse nested and embedded `struct`, instead it just evaluates that `struct`. 194 | // [2] If given field is not exists in the struct, method moves on to next field 195 | // 196 | // A "model" tag with the value of "-" is ignored by library for processing. 197 | // Example: 198 | // 199 | // // Field is ignored by go-model processing 200 | // BookCount int `model:"-"` 201 | // BookCode string `model:"-"` 202 | // 203 | func IsZeroInFields(s interface{}, names ...string) (string, bool) { 204 | if s == nil || len(names) == 0 { 205 | return "", true 206 | } 207 | 208 | sv, err := structValue(s) 209 | if err != nil { 210 | return "", false 211 | } 212 | 213 | for _, name := range names { 214 | fv := sv.FieldByName(name) 215 | 216 | // if given field is not exists then continue 217 | if !fv.IsValid() { 218 | continue 219 | } 220 | 221 | if isFieldZero(fv) { 222 | return name, true 223 | } 224 | } 225 | 226 | return "", false 227 | } 228 | 229 | // HasZero method returns `true` if any one of the exported fields in a given 230 | // `struct` is zero value otherwise `false`. If input is not a struct, method 231 | // returns `false`. 232 | // 233 | // A "model" tag with the value of "-" is ignored by library for processing. 234 | // Example: 235 | // 236 | // // Field is ignored by go-model processing 237 | // BookCount int `model:"-"` 238 | // BookCode string `model:"-"` 239 | // 240 | // A "model" tag value with the option of "notraverse"; library will not traverse 241 | // inside the struct object. However, the field value will be evaluated whether 242 | // it's zero value or not. 243 | // Example: 244 | // 245 | // // Field is not traversed but value is evaluated/processed 246 | // ArchiveInfo BookArchive `model:"archiveInfo,notraverse"` 247 | // Region BookLocale `model:",notraverse"` 248 | // 249 | func HasZero(s interface{}) bool { 250 | if s == nil { 251 | return true 252 | } 253 | 254 | sv, err := structValue(s) 255 | if err != nil { 256 | return false 257 | } 258 | 259 | fields := modelFields(sv) 260 | 261 | for _, f := range fields { 262 | fv := sv.FieldByName(f.Name) 263 | tag := newTag(f.Tag.Get(TagName)) 264 | 265 | if tag.isOmitField() { 266 | continue 267 | } 268 | 269 | // embedded or nested struct 270 | if isStruct(fv) { 271 | // check type is in NoTraverseTypeList or has 'notraverse' tag option 272 | if isNoTraverseType(fv) || tag.isNoTraverse() { 273 | 274 | // not traversing inside, but evaluating a value 275 | if isFieldZero(fv) { 276 | return true 277 | } 278 | 279 | continue 280 | } 281 | 282 | if HasZero(fv.Interface()) { 283 | return true 284 | } 285 | 286 | continue 287 | } 288 | 289 | if isFieldZero(fv) { 290 | return true 291 | } 292 | } 293 | 294 | return false 295 | } 296 | 297 | // Copy method copies all the exported field values from source `struct` into destination `struct`. 298 | // The "Name", "Type" and "Kind" is should match to qualify a copy. One exception though; 299 | // if the destination field type is "interface{}" then "Type" and "Kind" doesn't matter, 300 | // source value gets copied to that destination field. 301 | // 302 | // Example: 303 | // 304 | // src := SampleStruct { /* source struct field values go here */ } 305 | // dst := SampleStruct {} 306 | // 307 | // errs := model.Copy(&dst, src) 308 | // if errs != nil { 309 | // fmt.Println("Errors:", errs) 310 | // } 311 | // 312 | // Note: 313 | // [1] Copy process continues regardless of the case it qualifies or not. The non-qualified field(s) 314 | // gets added to '[]error' that you will get at the end. 315 | // [2] Two dimensional slice type is not supported yet. 316 | // 317 | // A "model" tag with the value of "-" is ignored by library for processing. 318 | // Example: 319 | // 320 | // // Field is ignored while processing 321 | // BookCount int `model:"-"` 322 | // BookCode string `model:"-"` 323 | // 324 | // A "model" tag value with the option of "omitempty"; library will not copy those values 325 | // into destination struct object. It may be handy for partial put or patch update 326 | // request scenarios; if you don't want to copy empty/zero value into destination object. 327 | // Example: 328 | // 329 | // // Field is not copy into 'dst' if it's empty/zero value 330 | // ArchiveInfo BookArchive `model:"archiveInfo,omitempty"` 331 | // Region BookLocale `model:",omitempty,notraverse"` 332 | // 333 | // A "model" tag value with the option of "notraverse"; library will not traverse 334 | // inside the struct object. However, the field value will be evaluated whether 335 | // it's zero value or not, and then copied to the destination object accordingly. 336 | // Example: 337 | // 338 | // // Field is not traversed but value is evaluated/processed 339 | // ArchiveInfo BookArchive `model:"archiveInfo,notraverse"` 340 | // Region BookLocale `model:",notraverse"` 341 | // 342 | func Copy(dst, src interface{}) []error { 343 | var errs []error 344 | 345 | if src == nil || dst == nil { 346 | return append(errs, errors.New("Source or Destination is nil")) 347 | } 348 | 349 | sv := valueOf(src) 350 | dv := valueOf(dst) 351 | 352 | if !isStruct(sv) || !isStruct(dv) { 353 | return append(errs, errors.New("Source or Destination is not a struct")) 354 | } 355 | 356 | if !isPtr(dv) { 357 | return append(errs, errors.New("Destination struct is not a pointer")) 358 | } 359 | 360 | if IsZero(src) { 361 | return append(errs, errors.New("Source struct is empty")) 362 | } 363 | 364 | // processing, copy field value(s) 365 | errs = doCopy(dv, sv) 366 | if len(errs) > 0 { 367 | return errs 368 | } 369 | 370 | return nil 371 | } 372 | 373 | // Clone method creates a clone of given `struct` object. As you know go-model does, deep processing. 374 | // So all field values you get in the result. 375 | // 376 | // Example: 377 | // input := SampleStruct { /* input struct field values go here */ } 378 | // 379 | // clonedObj := model.Clone(input) 380 | // 381 | // fmt.Printf("\nCloned Object: %#v\n", clonedObj) 382 | // 383 | // Note: 384 | // [1] Two dimensional slice type is not supported yet. 385 | // 386 | // A "model" tag with the value of "-" is ignored by library for processing. 387 | // Example: 388 | // 389 | // // Field is ignored while processing 390 | // BookCount int `model:"-"` 391 | // BookCode string `model:"-"` 392 | // 393 | // A "model" tag value with the option of "omitempty"; library will not clone those values 394 | // into result struct object. 395 | // Example: 396 | // 397 | // // Field is not cloned into 'result' if it's empty/zero value 398 | // ArchiveInfo BookArchive `model:"archiveInfo,omitempty"` 399 | // Region BookLocale `model:",omitempty,notraverse"` 400 | // 401 | // A "model" tag value with the option of "notraverse"; library will not traverse 402 | // inside the struct object. However, the field value will be evaluated whether 403 | // it's zero value or not, and then cloned to the result accordingly. 404 | // Example: 405 | // 406 | // // Field is not traversed but value is evaluated/processed 407 | // ArchiveInfo BookArchive `model:"archiveInfo,notraverse"` 408 | // Region BookLocale `model:",notraverse"` 409 | // 410 | func Clone(s interface{}) (interface{}, error) { 411 | sv, err := structValue(s) 412 | if err != nil { 413 | return nil, err 414 | } 415 | 416 | // figure out target type 417 | st := deepTypeOf(sv) 418 | 419 | // create a target type 420 | dv := reflect.New(st) 421 | 422 | // apply copy to target 423 | doCopy(dv, sv) 424 | 425 | return dv.Interface(), nil 426 | } 427 | 428 | // Map method converts all the exported field values from the given `struct` 429 | // into `map[string]interface{}`. In which the keys of the map are the field names 430 | // and the values of the map are the associated values of the field. 431 | // 432 | // Example: 433 | // 434 | // src := SampleStruct { /* source struct field values go here */ } 435 | // 436 | // err := model.Map(src) 437 | // if err != nil { 438 | // fmt.Println("Error:", err) 439 | // } 440 | // 441 | // Note: 442 | // [1] Two dimensional slice type is not supported yet. 443 | // 444 | // The default 'Key Name' string is the struct field name. However, it can be 445 | // changed in the struct field's tag value via "model" tag. 446 | // Example: 447 | // 448 | // // Now field 'Key Name' is customized 449 | // BookTitle string `model:"bookTitle"` 450 | // 451 | // A "model" tag with the value of "-" is ignored by library for processing. 452 | // Example: 453 | // 454 | // // Field is ignored while processing 455 | // BookCount int `model:"-"` 456 | // BookCode string `model:"-"` 457 | // 458 | // A "model" tag value with the option of "omitempty"; library will not include those values 459 | // while converting to map[string]interface{}. If it's empty/zero value. 460 | // Example: 461 | // 462 | // // Field is not included in result map if it's empty/zero value 463 | // ArchivedDate time.Time `model:"archivedDate,omitempty"` 464 | // Region BookLocale `model:",omitempty,notraverse"` 465 | // 466 | // A "model" tag value with the option of "notraverse"; library will not traverse 467 | // inside the struct object. However, the field value will be evaluated whether 468 | // it's zero value or not, and then added to the result map accordingly. 469 | // Example: 470 | // 471 | // // Field is not traversed but value is evaluated/processed 472 | // ArchivedDate time.Time `model:"archivedDate,notraverse"` 473 | // Region BookLocale `model:",notraverse"` 474 | // 475 | func Map(s interface{}) (map[string]interface{}, error) { 476 | sv, err := structValue(s) 477 | if err != nil { 478 | return nil, err 479 | } 480 | 481 | // processing, field value(s) into map 482 | return doMap(sv), nil 483 | } 484 | 485 | // Fields method returns the exported struct fields from the given `struct`. 486 | // Example: 487 | // 488 | // src := SampleStruct { /* source struct field values go here */ } 489 | // 490 | // fields, _ := model.Fields(src) 491 | // for _, f := range fields { 492 | // tag := newTag(f.Tag.Get("model")) 493 | // fmt.Println("Field Name:", f.Name, "Tag Name:", tag.Name, "Tag Options:", tag.Options) 494 | // } 495 | // 496 | func Fields(s interface{}) ([]reflect.StructField, error) { 497 | sv, err := structValue(s) 498 | if err != nil { 499 | return nil, err 500 | } 501 | 502 | return modelFields(sv), nil 503 | } 504 | 505 | // Kind method returns `reflect.Kind` for the given field name from the `struct`. 506 | // Example: 507 | // 508 | // src := SampleStruct { 509 | // BookCount int `json:"-"` 510 | // BookCode string `json:"-"` 511 | // ArchiveInfo BookArchive `json:"archive_info,omitempty"` 512 | // Region BookLocale `json:"region,omitempty"` 513 | // } 514 | // 515 | // fieldKind, _ := model.Kind(src, "ArchiveInfo") 516 | // fmt.Println("Field kind:", fieldKind) 517 | // 518 | func Kind(s interface{}, name string) (reflect.Kind, error) { 519 | sv, err := structValue(s) 520 | if err != nil { 521 | return reflect.Invalid, err 522 | } 523 | 524 | fv, err := getField(sv, name) 525 | if err != nil { 526 | return reflect.Invalid, err 527 | } 528 | 529 | return fv.Type().Kind(), nil 530 | } 531 | 532 | // Get method returns a field value from `struct` by field name. 533 | // Example: 534 | // 535 | // src := SampleStruct { 536 | // BookCount int `json:"-"` 537 | // BookCode string `json:"-"` 538 | // ArchiveInfo BookArchive `json:"archive_info,omitempty"` 539 | // Region BookLocale `json:"region,omitempty"` 540 | // } 541 | // 542 | // value, err := model.Get(src, "ArchiveInfo") 543 | // fmt.Println("Field Value:", value) 544 | // fmt.Println("Error:", err) 545 | // 546 | // Note: Get method does not honor model tag annotations. Get simply access 547 | // value on exported fields. 548 | // 549 | func Get(s interface{}, name string) (interface{}, error) { 550 | sv, err := structValue(s) 551 | if err != nil { 552 | return nil, err 553 | } 554 | 555 | fv, err := getField(sv, name) 556 | if err != nil { 557 | return nil, err 558 | } 559 | 560 | return fv.Interface(), nil 561 | } 562 | 563 | // Set method sets a value into field on struct by field name. 564 | // Example: 565 | // 566 | // src := SampleStruct { 567 | // BookCount int `json:"-"` 568 | // BookCode string `json:"-"` 569 | // ArchiveInfo BookArchive `json:"archive_info,omitempty"` 570 | // Region BookLocale `json:"region,omitempty"` 571 | // } 572 | // 573 | // bookLocale := BookLocale { 574 | // Locale: "en-US", 575 | // Language: "en", 576 | // Region: "US", 577 | // } 578 | // 579 | // err := model.Set(&src, "Region", bookLocale) 580 | // fmt.Println("Error:", err) 581 | // 582 | // Note: Set method does not honor model tag annotations. Set simply given 583 | // value by field name on exported fields. 584 | // 585 | func Set(s interface{}, name string, value interface{}) error { 586 | if s == nil { 587 | return errors.New("Invalid input ") 588 | } 589 | 590 | sv := valueOf(s) 591 | if isPtr(sv) { 592 | sv = sv.Elem() 593 | } else { 594 | return errors.New("Destination struct is not a pointer") 595 | } 596 | 597 | fv, err := getField(sv, name) 598 | if err != nil { 599 | return err 600 | } 601 | 602 | if !fv.CanSet() { 603 | return fmt.Errorf("Field: %v, cannot be settable", name) 604 | } 605 | 606 | tv := valueOf(value) 607 | if isPtr(tv) { 608 | tv = tv.Elem() 609 | } 610 | 611 | if (fv.Kind() != tv.Kind()) || fv.Type() != tv.Type() { 612 | return fmt.Errorf("Field: %v, type/kind did not match", name) 613 | } 614 | 615 | // assign the given value 616 | fv.Set(tv) 617 | 618 | return nil 619 | } 620 | 621 | // 622 | // go-model init 623 | // 624 | 625 | func init() { 626 | noTraverseTypeList = map[reflect.Type]bool{} 627 | converterMap = map[reflect.Type]map[reflect.Type]Converter{} 628 | 629 | // Default NoTraverseTypeList 630 | // -------------------------- 631 | // Auto No Traverse struct list for not traversing Deep Level 632 | // However, field value will be evaluated/processed by go-model library 633 | AddNoTraverseType( 634 | time.Time{}, 635 | &time.Time{}, 636 | os.File{}, 637 | &os.File{}, 638 | http.Request{}, 639 | &http.Request{}, 640 | http.Response{}, 641 | &http.Response{}, 642 | 643 | // it's better to add it to the list for appropriate type(s) 644 | ) 645 | } 646 | 647 | // 648 | // Non-exported methods of model library 649 | // 650 | 651 | func doCopy(dv, sv reflect.Value) []error { 652 | dv = indirect(dv) 653 | sv = indirect(sv) 654 | fields := modelFields(sv) 655 | 656 | var errs []error 657 | 658 | for _, f := range fields { 659 | sfv := sv.FieldByName(f.Name) 660 | tag := newTag(f.Tag.Get(TagName)) 661 | 662 | if tag.isOmitField() { 663 | continue 664 | } 665 | 666 | // check type is in NoTraverseTypeList or has 'notraverse' tag option 667 | noTraverse := (isNoTraverseType(sfv) || tag.isNoTraverse()) 668 | 669 | // check whether field is zero or not 670 | var isVal bool 671 | if isStruct(sfv) && !noTraverse { 672 | isVal = !IsZero(sfv.Interface()) 673 | } else { 674 | isVal = !isFieldZero(sfv) 675 | } 676 | 677 | // get dst field by name 678 | dfv := dv.FieldByName(f.Name) 679 | 680 | // validate field - exists in dst, kind and type 681 | err := validateCopyField(f, sfv, dfv) 682 | if err != nil { 683 | if err != errFieldNotExists { 684 | errs = append(errs, err) 685 | } 686 | 687 | continue 688 | } 689 | 690 | // if value is not exists 691 | if !isVal { 692 | // field value is zero and check 'omitempty' option present 693 | // then don't copy into destination struct 694 | // otherwise copy to dst 695 | if !tag.isOmitEmpty() { 696 | dfv.Set(zeroOf(dfv)) 697 | } 698 | continue 699 | } 700 | 701 | // check dst field settable or not 702 | if dfv.CanSet() { 703 | if isStruct(sfv) { 704 | // handle embedded or nested struct 705 | v, innerErrs := copyVal(dfv.Type(), sfv, noTraverse) 706 | 707 | // add errors to main stream 708 | errs = append(errs, innerErrs...) 709 | 710 | // handle based on ptr/non-ptr value 711 | dfv.Set(v) 712 | } else { 713 | v, err := copyVal(dfv.Type(), sfv, false) 714 | errs = append(errs, err...) 715 | dfv.Set(v) 716 | } 717 | } 718 | } 719 | 720 | return errs 721 | } 722 | 723 | func doMap(sv reflect.Value) map[string]interface{} { 724 | sv = indirect(sv) 725 | fields := modelFields(sv) 726 | m := map[string]interface{}{} 727 | 728 | for _, f := range fields { 729 | fv := sv.FieldByName(f.Name) 730 | tag := newTag(f.Tag.Get(TagName)) 731 | 732 | if tag.isOmitField() { 733 | continue 734 | } 735 | 736 | // map key name 737 | keyName := f.Name 738 | if !isStringEmpty(tag.Name) { 739 | keyName = tag.Name 740 | } 741 | 742 | // check type is in NoTraverseTypeList or has 'notraverse' tag option 743 | noTraverse := (isNoTraverseType(fv) || tag.isNoTraverse()) 744 | 745 | // check whether field is zero or not 746 | var isVal bool 747 | if isStruct(fv) && !noTraverse { 748 | isVal = !IsZero(fv.Interface()) 749 | } else { 750 | isVal = !isFieldZero(fv) 751 | } 752 | 753 | if !isVal { 754 | // field value is zero and has 'omitempty' option present 755 | // then not include in the Map 756 | if !tag.isOmitEmpty() { 757 | m[keyName] = zeroOf(fv).Interface() 758 | } 759 | 760 | continue 761 | } 762 | 763 | // handle embedded or nested struct 764 | if isStruct(fv) { 765 | 766 | if noTraverse { 767 | // This is struct kind and it's present in NoTraverseTypeList or 768 | // has 'notraverse' tag option. So go-model is not gonna traverse inside. 769 | // however will take care of field value 770 | m[keyName] = mapVal(fv, true).Interface() 771 | } else { 772 | 773 | // embedded struct values gets mapped at embedded level 774 | // as represented by Go instead of object 775 | fmv := doMap(fv) 776 | if f.Anonymous { 777 | for k, v := range fmv { 778 | m[k] = v 779 | } 780 | } else { 781 | m[keyName] = fmv 782 | } 783 | } 784 | 785 | continue 786 | } 787 | 788 | m[keyName] = mapVal(fv, false).Interface() 789 | } 790 | 791 | return m 792 | } 793 | 794 | func copyVal(dt reflect.Type, f reflect.Value, notraverse bool) (reflect.Value, []error) { 795 | var ( 796 | ptr bool 797 | nf reflect.Value 798 | errs []error 799 | ) 800 | 801 | if conversionExists(f.Type(), dt) && !notraverse { 802 | // handle custom converters 803 | res, err := converterMap[f.Type()][dt](f) 804 | if err != nil { 805 | errs = append(errs, err) 806 | } 807 | return res, errs 808 | } 809 | 810 | // take care interface{} and its actual value 811 | if isInterface(f) { 812 | f = valueOf(f.Interface()) 813 | } 814 | 815 | // if ptr, let's take a note 816 | if isPtr(f) { 817 | ptr = true 818 | f = f.Elem() 819 | } 820 | 821 | // two dimensional slice is not yet supported by this library 822 | switch f.Kind() { 823 | case reflect.Struct: 824 | if notraverse { 825 | nf = f 826 | } else { 827 | nf = reflect.New(f.Type()) 828 | 829 | // currently, struct within map/slice errors doesn't get propagated 830 | doCopy(nf, f) 831 | 832 | // unwrap 833 | nf = nf.Elem() 834 | } 835 | case reflect.Map: 836 | if dt.Kind() == reflect.Ptr { 837 | dt = dt.Elem() 838 | } 839 | nf = reflect.MakeMap(dt) 840 | 841 | for _, key := range f.MapKeys() { 842 | ov := f.MapIndex(key) 843 | 844 | cv := reflect.New(dt.Elem()).Elem() 845 | v, err := copyVal(dt.Elem(), ov, isNoTraverseType(ov)) 846 | if len(err) > 0 { 847 | errs = append(errs, err...) 848 | } else { 849 | cv.Set(v) 850 | nf.SetMapIndex(key, cv) 851 | } 852 | } 853 | case reflect.Slice: 854 | if f.Type() == typeOfBytes { 855 | nf = f 856 | } else { 857 | if dt.Kind() == reflect.Ptr { 858 | dt = dt.Elem() 859 | } 860 | nf = reflect.MakeSlice(dt, f.Len(), f.Cap()) 861 | 862 | for i := 0; i < f.Len(); i++ { 863 | ov := f.Index(i) 864 | 865 | cv := reflect.New(dt.Elem()).Elem() 866 | v, err := copyVal(dt.Elem(), ov, isNoTraverseType(ov)) 867 | if len(err) > 0 { 868 | errs = append(errs, err...) 869 | } else { 870 | cv.Set(v) 871 | nf.Index(i).Set(cv) 872 | } 873 | } 874 | } 875 | default: 876 | nf = f 877 | } 878 | 879 | if ptr { 880 | // wrap 881 | o := reflect.New(nf.Type()) 882 | o.Elem().Set(nf) 883 | 884 | return o, errs 885 | } 886 | 887 | return nf, errs 888 | } 889 | 890 | func mapVal(f reflect.Value, notraverse bool) reflect.Value { 891 | var ( 892 | ptr bool 893 | nf reflect.Value 894 | ) 895 | 896 | // take care interface{} and its actual value 897 | if isInterface(f) { 898 | f = valueOf(f.Interface()) 899 | } 900 | 901 | // if ptr, let's take a note 902 | if isPtr(f) { 903 | ptr = true 904 | f = f.Elem() 905 | } 906 | 907 | // two dimensional slice is not yet supported by this library 908 | switch f.Kind() { 909 | case reflect.Struct: 910 | if notraverse { 911 | nf = f 912 | } else { 913 | nf = valueOf(doMap(f)) 914 | } 915 | case reflect.Map: 916 | nmv := map[string]interface{}{} 917 | 918 | for _, key := range f.MapKeys() { 919 | skey := fmt.Sprintf("%v", key.Interface()) 920 | mv := f.MapIndex(key) 921 | nv := mapVal(mv, isNoTraverseType(mv)) 922 | nmv[skey] = nv.Interface() 923 | } 924 | 925 | nf = valueOf(nmv) 926 | case reflect.Slice: 927 | if f.Type() == typeOfBytes { 928 | nf = f 929 | } else { 930 | if f.Len() > 0 { 931 | fsv := f.Index(0) 932 | 933 | // figure out target slice type 934 | if isStruct(fsv) { 935 | nf = reflect.MakeSlice(reflect.SliceOf(typeOfInterface), f.Len(), f.Cap()) 936 | } else { 937 | nf = reflect.MakeSlice(f.Type(), f.Len(), f.Cap()) 938 | } 939 | 940 | for i := 0; i < f.Len(); i++ { 941 | sv := f.Index(i) 942 | 943 | var dv reflect.Value 944 | if isStruct(sv) { 945 | dv = reflect.New(typeOfInterface).Elem() 946 | } else { 947 | dv = reflect.New(sv.Type()).Elem() 948 | } 949 | 950 | dv.Set(mapVal(sv, isNoTraverseType(sv))) 951 | nf.Index(i).Set(dv) 952 | } 953 | } 954 | } 955 | default: 956 | nf = f 957 | } 958 | 959 | if ptr { 960 | // wrap 961 | o := reflect.New(nf.Type()) 962 | o.Elem().Set(nf) 963 | 964 | return o 965 | } 966 | 967 | return nf 968 | } 969 | -------------------------------------------------------------------------------- /model_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jeevanandam M. (https://github.com/jeevatkm). 2 | // go-model source code and usage is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | package model 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "strconv" 11 | ) 12 | 13 | // 14 | // Examples 15 | // 16 | 17 | // Register a custom `Converter` to allow conversions from `int` to `string`. 18 | func ExampleAddConversion() { 19 | AddConversion((*int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) { 20 | return reflect.ValueOf(strconv.FormatInt(in.Int(), 10)), nil 21 | }) 22 | type StructA struct { 23 | Mixed string 24 | } 25 | 26 | type StructB struct { 27 | Mixed int 28 | } 29 | src := StructB{Mixed: 123} 30 | dst := StructA{} 31 | 32 | errs := Copy(&dst, &src) 33 | if errs != nil { 34 | panic(errs) 35 | } 36 | fmt.Printf("%v", dst) 37 | // Output: {123} 38 | } 39 | 40 | // Register a custom `Converter` to allow conversions from `*int` to `string`. 41 | func ExampleAddConversion_sourcePointer() { 42 | AddConversion((**int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) { 43 | return reflect.ValueOf(strconv.FormatInt(in.Elem().Int(), 10)), nil 44 | }) 45 | type StructA struct { 46 | Mixed string 47 | } 48 | 49 | type StructB struct { 50 | Mixed *int 51 | } 52 | val := 123 53 | src := StructB{Mixed: &val} 54 | dst := StructA{} 55 | 56 | errs := Copy(&dst, &src) 57 | if errs != nil { 58 | panic(errs[0]) 59 | } 60 | fmt.Printf("%v", dst) 61 | // Output: {123} 62 | } 63 | 64 | // Register a custom `Converter` to allow conversions from `int` to `*string`. 65 | func ExampleAddConversion_destinationPointer() { 66 | AddConversion((*int)(nil), (**string)(nil), func(in reflect.Value) (reflect.Value, error) { 67 | str := strconv.FormatInt(in.Int(), 10) 68 | return reflect.ValueOf(&str), nil 69 | }) 70 | type StructA struct { 71 | Mixed *string 72 | } 73 | 74 | type StructB struct { 75 | Mixed int 76 | } 77 | src := StructB{Mixed: 123} 78 | dst := StructA{} 79 | 80 | errs := Copy(&dst, &src) 81 | if errs != nil { 82 | panic(errs[0]) 83 | } 84 | fmt.Printf("%v", *dst.Mixed) 85 | // Output: 123 86 | } 87 | 88 | // Register a custom `Converter` to allow conversions from `int` to `*string` by passing types. 89 | func ExampleAddConversion_destinationPointerByType() { 90 | srcType := reflect.TypeOf((*int)(nil)).Elem() 91 | targetType := reflect.TypeOf((**string)(nil)).Elem() 92 | AddConversionByType(srcType, targetType, func(in reflect.Value) (reflect.Value, error) { 93 | str := strconv.FormatInt(in.Int(), 10) 94 | return reflect.ValueOf(&str), nil 95 | }) 96 | type StructA struct { 97 | Mixed *string 98 | } 99 | 100 | type StructB struct { 101 | Mixed int 102 | } 103 | src := StructB{Mixed: 123} 104 | dst := StructA{} 105 | 106 | errs := Copy(&dst, &src) 107 | if errs != nil { 108 | panic(errs[0]) 109 | } 110 | fmt.Printf("%v", *dst.Mixed) 111 | // Output: 123 112 | } 113 | -------------------------------------------------------------------------------- /model_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jeevanandam M. (https://github.com/jeevatkm). 2 | // go-model source code and usage is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | package model 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "os" 11 | "reflect" 12 | "strconv" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | // 18 | // Copy test cases 19 | // 20 | 21 | func TestConverter(t *testing.T) { 22 | type SampleStructA struct { 23 | Int int 24 | String string 25 | Mixed string 26 | } 27 | 28 | type SampleStructB struct { 29 | Int int 30 | String string 31 | Mixed int 32 | } 33 | 34 | AddConversion((*int)(nil), (*string)(nil), func(in reflect.Value) (reflect.Value, error) { 35 | return reflect.ValueOf(strconv.FormatInt(in.Int(), 10) + "lala"), nil 36 | }) 37 | 38 | src := SampleStructB{Mixed: 123, Int: 5, String: "string"} 39 | dst := SampleStructA{} 40 | 41 | errs := Copy(&dst, src) 42 | if errs != nil { 43 | t.Error("Error occurred while copying.") 44 | } 45 | assertEqual(t, "123lala", dst.Mixed) 46 | assertEqual(t, 5, dst.Int) 47 | assertEqual(t, "string", dst.String) 48 | } 49 | 50 | func TestMissingConverter(t *testing.T) { 51 | type SampleStructA struct { 52 | Int int 53 | String string 54 | Mixed string 55 | } 56 | 57 | type SampleStructB struct { 58 | Int int 59 | String string 60 | Mixed int 61 | } 62 | 63 | RemoveConversion((*int)(nil), (*string)(nil)) 64 | 65 | src := SampleStructB{Mixed: 123, Int: 5, String: "string"} 66 | dst := SampleStructA{} 67 | 68 | errs := Copy(&dst, src) 69 | if errs == nil { 70 | t.Error("The conversion between int and string should have failed.") 71 | } 72 | assertEqual(t, "", dst.Mixed) 73 | assertEqual(t, 5, dst.Int) 74 | assertEqual(t, "string", dst.String) 75 | } 76 | 77 | func TestCopyIntegerAndIntegerPtr(t *testing.T) { 78 | type SampleStruct struct { 79 | Int int 80 | IntPtr *int 81 | Int64 int64 82 | Int64Ptr *int64 83 | } 84 | 85 | intPtr := int(1001) 86 | int64Ptr := int64(1002) 87 | 88 | src := SampleStruct{ 89 | Int: 2001, 90 | IntPtr: &intPtr, 91 | Int64: 2002, 92 | Int64Ptr: &int64Ptr, 93 | } 94 | 95 | dst := SampleStruct{} 96 | 97 | errs := Copy(&dst, src) 98 | if errs != nil { 99 | t.Error("Error occurred while copying.") 100 | } 101 | 102 | logSrcDst(t, src, dst) 103 | 104 | assertEqual(t, src.Int, dst.Int) 105 | assertEqual(t, src.Int64, dst.Int64) 106 | 107 | assertEqual(t, true, src.IntPtr != dst.IntPtr) 108 | assertEqual(t, *src.IntPtr, *dst.IntPtr) 109 | assertEqual(t, *src.Int64Ptr, *dst.Int64Ptr) 110 | } 111 | 112 | func TestCopyStringAndStringPtr(t *testing.T) { 113 | type SampleStruct struct { 114 | String string 115 | StringPtr *string 116 | } 117 | 118 | strPtr := "This is string for pointer test" 119 | src := SampleStruct{ 120 | String: "This is string for test", 121 | StringPtr: &strPtr, 122 | } 123 | 124 | dst := SampleStruct{} 125 | 126 | errs := Copy(&dst, &src) 127 | if errs != nil { 128 | t.Error("Error occurred while copying.") 129 | } 130 | 131 | logSrcDst(t, src, dst) 132 | 133 | assertEqual(t, src.String, dst.String) 134 | assertEqual(t, *src.StringPtr, *dst.StringPtr) 135 | assertEqual(t, true, src.StringPtr != dst.StringPtr) 136 | } 137 | 138 | func TestCopyBooleanAndBooleanPtr(t *testing.T) { 139 | type SampleStruct struct { 140 | Boolean bool 141 | BooleanPtr *bool 142 | BooleanOmitEmpty bool `model:",omitempty"` 143 | BooleanOmitEmptyPtr *bool `model:",omitempty"` 144 | } 145 | 146 | boolPtr := true 147 | src := SampleStruct{ 148 | Boolean: true, 149 | BooleanPtr: &boolPtr, 150 | } 151 | 152 | dst := SampleStruct{} 153 | 154 | errs := Copy(&dst, &src) 155 | if errs != nil { 156 | t.Error("Error occurred while copying.") 157 | } 158 | 159 | logSrcDst(t, src, dst) 160 | 161 | assertEqual(t, src.Boolean, dst.Boolean) 162 | assertEqual(t, *src.BooleanPtr, *dst.BooleanPtr) 163 | assertEqual(t, true, src.BooleanPtr != dst.BooleanPtr) 164 | assertEqual(t, false, dst.BooleanOmitEmpty) 165 | assertEqual(t, true, dst.BooleanOmitEmptyPtr == nil) 166 | } 167 | 168 | func TestCopyFloatAndFloatPtr(t *testing.T) { 169 | type SampleStruct struct { 170 | Float32 float32 171 | Float64 float64 172 | Float32PtrOmit *float32 `model:"-"` 173 | Float32Ptr *float32 174 | Float64Ptr *float64 175 | } 176 | 177 | f32 := float32(0.1) 178 | f64 := float64(0.2) 179 | 180 | src := SampleStruct{ 181 | Float32: float32(0.11), 182 | Float32Ptr: &f32, 183 | Float64: float64(0.22), 184 | Float64Ptr: &f64, 185 | } 186 | 187 | dst := SampleStruct{} 188 | 189 | errs := Copy(&dst, &src) 190 | if errs != nil { 191 | t.Error("Error occurred while copying.") 192 | } 193 | 194 | logSrcDst(t, src, dst) 195 | 196 | assertEqual(t, src.Float32, dst.Float32) 197 | assertEqual(t, *src.Float32Ptr, *dst.Float32Ptr) 198 | 199 | assertEqual(t, src.Float64, dst.Float64) 200 | assertEqual(t, *src.Float64Ptr, *dst.Float64Ptr) 201 | 202 | assertEqual(t, true, src.Float32Ptr != dst.Float32Ptr) 203 | assertEqual(t, true, src.Float64Ptr != dst.Float64Ptr) 204 | } 205 | 206 | func TestCopySliceStringAndSliceStringPtr(t *testing.T) { 207 | type SampleStruct struct { 208 | SliceString []string 209 | SliceStringPtr *[]string 210 | } 211 | 212 | sliceStrPtr := []string{"This is slice string test pointer."} 213 | src := SampleStruct{ 214 | SliceString: []string{"This is slice string test."}, 215 | SliceStringPtr: &sliceStrPtr, 216 | } 217 | 218 | dst := SampleStruct{} 219 | 220 | errs := Copy(&dst, &src) 221 | if errs != nil { 222 | t.Error("Error occurred while copying.") 223 | } 224 | 225 | logSrcDst(t, src, dst) 226 | 227 | assertEqual(t, src.SliceString, dst.SliceString) 228 | assertEqual(t, *src.SliceStringPtr, *dst.SliceStringPtr) 229 | assertEqual(t, true, src.SliceStringPtr != dst.SliceStringPtr) 230 | } 231 | 232 | func TestCopyByteAndByteSlice(t *testing.T) { 233 | type SampleStruct struct { 234 | Byte byte 235 | SliceBytes []byte 236 | SliceBytesPtr *[]byte 237 | } 238 | 239 | bytesPtr := []byte("This is byte pointer value") 240 | 241 | src := SampleStruct{ 242 | Byte: byte('A'), 243 | SliceBytes: []byte("This is byte value"), 244 | SliceBytesPtr: &bytesPtr, 245 | } 246 | 247 | dst := SampleStruct{} 248 | 249 | errs := Copy(&dst, &src) 250 | if errs != nil { 251 | t.Error("Error occurred while copying.") 252 | } 253 | 254 | logSrcDst(t, src, dst) 255 | 256 | assertEqual(t, src.Byte, dst.Byte) 257 | assertEqual(t, true, &src.Byte != &dst.Byte) 258 | 259 | assertEqual(t, src.SliceBytes, dst.SliceBytes) 260 | assertEqual(t, true, &src.SliceBytes != &dst.SliceBytes) 261 | 262 | assertEqual(t, *src.SliceBytesPtr, *dst.SliceBytesPtr) 263 | assertEqual(t, true, src.SliceBytesPtr != dst.SliceBytesPtr) 264 | } 265 | 266 | func TestCopySliceElementsPtr(t *testing.T) { 267 | type SampleSubInfo2 struct { 268 | SliceIntPtr []*int 269 | SliceInt64Ptr []*int64 270 | SliceStringPtr []*string 271 | SliceFloat32 []*float32 272 | SliceFloat64 []*float64 273 | SliceInterface []interface{} 274 | } 275 | 276 | type SampleSubInfo1 struct { 277 | SliceIntPtr []*int 278 | SliceInt64Ptr []*int64 279 | SliceStringPtr []*string 280 | SliceFloat32 []*float32 281 | SliceFloat64 []*float64 282 | SliceInterface []interface{} 283 | Level2 SampleSubInfo2 284 | } 285 | 286 | type SampleStruct struct { 287 | SliceIntPtr []*int 288 | SliceInt64Ptr []*int64 289 | SliceStringPtr []*string 290 | SliceFloat32 []*float32 291 | SliceFloat64 []*float64 292 | SliceInterface []interface{} 293 | Level1 SampleSubInfo1 294 | } 295 | 296 | i1 := int(1) 297 | i2 := int(2) 298 | i3 := int(3) 299 | 300 | i11 := int64(11) 301 | i12 := int64(12) 302 | i13 := int64(13) 303 | 304 | str1 := "This is string pointer 1" 305 | str2 := "This is string pointer 2" 306 | str3 := "This is string pointer 3" 307 | 308 | f1 := float32(0.1) 309 | f2 := float32(0.2) 310 | f3 := float32(0.3) 311 | 312 | f11 := float64(0.11) 313 | f12 := float64(0.12) 314 | f13 := float64(0.13) 315 | 316 | src := SampleStruct{ 317 | SliceIntPtr: []*int{&i1, &i2, &i3}, 318 | SliceInt64Ptr: []*int64{&i11, &i12, &i13}, 319 | SliceStringPtr: []*string{&str1, &str2, &str3}, 320 | SliceFloat32: []*float32{&f1, &f2, &f3}, 321 | SliceFloat64: []*float64{&f11, &f12, &f13}, 322 | SliceInterface: []interface{}{&i1, i11, &str1, &f1, f11}, 323 | Level1: SampleSubInfo1{ 324 | SliceIntPtr: []*int{&i1, &i2, &i3}, 325 | SliceInt64Ptr: []*int64{&i11, &i12, &i13}, 326 | SliceStringPtr: []*string{&str1, &str2, &str3}, 327 | SliceFloat32: []*float32{&f1, &f2, &f3}, 328 | SliceFloat64: []*float64{&f11, &f12, &f13}, 329 | SliceInterface: []interface{}{&i1, i11, &str1, &f1, f11}, 330 | Level2: SampleSubInfo2{ 331 | SliceIntPtr: []*int{&i1, &i2, &i3}, 332 | SliceInt64Ptr: []*int64{&i11, &i12, &i13}, 333 | SliceStringPtr: []*string{&str1, &str2, &str3}, 334 | SliceFloat32: []*float32{&f1, &f2, &f3}, 335 | SliceFloat64: []*float64{&f11, &f12, &f13}, 336 | SliceInterface: []interface{}{&i1, i11, &str1, &f1, f11}, 337 | }, 338 | }, 339 | } 340 | 341 | dst := SampleStruct{} 342 | 343 | errs := Copy(&dst, src) 344 | if errs != nil { 345 | t.Error("Error occurred while copying.") 346 | } 347 | 348 | logSrcDst(t, src, dst) 349 | 350 | // Level 0 assertion 351 | assertEqual(t, true, src.SliceIntPtr[0] != dst.SliceIntPtr[0]) 352 | assertEqual(t, src.SliceIntPtr, dst.SliceIntPtr) 353 | 354 | assertEqual(t, true, src.SliceInt64Ptr[0] != dst.SliceInt64Ptr[0]) 355 | assertEqual(t, src.SliceInt64Ptr, dst.SliceInt64Ptr) 356 | 357 | assertEqual(t, true, src.SliceStringPtr[0] != dst.SliceStringPtr[0]) 358 | assertEqual(t, src.SliceStringPtr, dst.SliceStringPtr) 359 | 360 | assertEqual(t, true, src.SliceFloat32[0] != dst.SliceFloat32[0]) 361 | assertEqual(t, src.SliceFloat32, dst.SliceFloat32) 362 | 363 | assertEqual(t, true, src.SliceFloat64[0] != dst.SliceFloat64[0]) 364 | assertEqual(t, src.SliceFloat64, dst.SliceFloat64) 365 | 366 | assertEqual(t, true, src.SliceInterface[0] != dst.SliceInterface[0]) 367 | assertEqual(t, src.SliceInterface, dst.SliceInterface) 368 | 369 | // Level 1 assertion 370 | assertEqual(t, true, src.Level1.SliceIntPtr[0] != dst.Level1.SliceIntPtr[0]) 371 | assertEqual(t, src.Level1.SliceIntPtr, dst.Level1.SliceIntPtr) 372 | 373 | assertEqual(t, true, src.Level1.SliceInt64Ptr[0] != dst.Level1.SliceInt64Ptr[0]) 374 | assertEqual(t, src.Level1.SliceInt64Ptr, dst.Level1.SliceInt64Ptr) 375 | 376 | assertEqual(t, true, src.Level1.SliceStringPtr[0] != dst.Level1.SliceStringPtr[0]) 377 | assertEqual(t, src.Level1.SliceStringPtr, dst.Level1.SliceStringPtr) 378 | 379 | assertEqual(t, true, src.Level1.SliceFloat32[0] != dst.Level1.SliceFloat32[0]) 380 | assertEqual(t, src.Level1.SliceFloat32, dst.Level1.SliceFloat32) 381 | 382 | assertEqual(t, true, src.Level1.SliceFloat64[0] != dst.Level1.SliceFloat64[0]) 383 | assertEqual(t, src.Level1.SliceFloat64, dst.Level1.SliceFloat64) 384 | 385 | assertEqual(t, true, src.Level1.SliceInterface[0] != dst.Level1.SliceInterface[0]) 386 | assertEqual(t, src.Level1.SliceInterface, dst.Level1.SliceInterface) 387 | 388 | // Level 2 assertion 389 | assertEqual(t, true, src.Level1.Level2.SliceIntPtr[0] != dst.Level1.Level2.SliceIntPtr[0]) 390 | assertEqual(t, src.Level1.SliceIntPtr, dst.Level1.SliceIntPtr) 391 | 392 | assertEqual(t, true, src.Level1.Level2.SliceInt64Ptr[0] != dst.Level1.Level2.SliceInt64Ptr[0]) 393 | assertEqual(t, src.Level1.Level2.SliceInt64Ptr, dst.Level1.Level2.SliceInt64Ptr) 394 | 395 | assertEqual(t, true, src.Level1.Level2.SliceStringPtr[0] != dst.Level1.Level2.SliceStringPtr[0]) 396 | assertEqual(t, src.Level1.Level2.SliceStringPtr, dst.Level1.Level2.SliceStringPtr) 397 | 398 | assertEqual(t, true, src.Level1.Level2.SliceFloat32[0] != dst.Level1.Level2.SliceFloat32[0]) 399 | assertEqual(t, src.Level1.Level2.SliceFloat32, dst.Level1.Level2.SliceFloat32) 400 | 401 | assertEqual(t, true, src.Level1.Level2.SliceFloat64[0] != dst.Level1.Level2.SliceFloat64[0]) 402 | assertEqual(t, src.Level1.Level2.SliceFloat64, dst.Level1.Level2.SliceFloat64) 403 | 404 | assertEqual(t, true, src.Level1.Level2.SliceInterface[0] != dst.Level1.Level2.SliceInterface[0]) 405 | assertEqual(t, src.Level1.Level2.SliceInterface, dst.Level1.Level2.SliceInterface) 406 | } 407 | 408 | func TestCopyMapElements(t *testing.T) { 409 | type SampleSubInfo struct { 410 | Name string 411 | Year int 412 | } 413 | 414 | type SampleStruct struct { 415 | MapIntInt map[int]int 416 | MapStringInt map[string]int 417 | MapStringString map[string]string 418 | MapStruct map[string]SampleSubInfo 419 | MapInterfaces map[string]interface{} 420 | } 421 | 422 | src := SampleStruct{ 423 | MapIntInt: map[int]int{1: 1001, 2: 1002, 3: 1003, 4: 1004}, 424 | MapStringInt: map[string]int{"first": 1001, "second": 1002, "third": 1003, "forth": 1004}, 425 | MapStringString: map[string]string{"first": "1001", "second": "1002", "third": "1003"}, 426 | MapStruct: map[string]SampleSubInfo{ 427 | "struct1": {Name: "struct 1 value", Year: 2001}, 428 | "struct2": {Name: "struct 2 value", Year: 2002}, 429 | "struct3": {Name: "struct 3 value", Year: 2003}, 430 | }, 431 | MapInterfaces: map[string]interface{}{ 432 | "inter1": 100001, 433 | "inter2": "This is my interface string", 434 | "inter3": SampleSubInfo{Name: "inter3: struct 1 value", Year: 2003}, 435 | "inter4": float32(1.6546565), 436 | "inter5": float64(1.6546565), 437 | "inter6": &SampleSubInfo{Name: "inter6: struct 2 value", Year: 2006}, 438 | }, 439 | } 440 | 441 | dst := SampleStruct{} 442 | 443 | errs := Copy(&dst, &src) 444 | if errs != nil { 445 | t.Error("Error occurred while copying.") 446 | } 447 | 448 | logSrcDst(t, src, dst) 449 | 450 | assertEqual(t, src.MapIntInt, dst.MapIntInt) 451 | assertEqual(t, src.MapStringInt, dst.MapStringInt) 452 | assertEqual(t, src.MapStringString, dst.MapStringString) 453 | assertEqual(t, src.MapStruct, dst.MapStruct) 454 | assertEqual(t, src.MapInterfaces, dst.MapInterfaces) 455 | } 456 | 457 | func TestCopyStructEmbededAndAttribute(t *testing.T) { 458 | type SampleSubInfo struct { 459 | Name string 460 | Year int 461 | } 462 | 463 | type SampleStruct struct { 464 | Level1Struct SampleSubInfo `model:",notraverse"` 465 | Level1StructPtr *SampleSubInfo 466 | Level1StructNoTraverse *SampleSubInfo `model:",notraverse"` 467 | CreatedTime time.Time 468 | SampleSubInfo 469 | } 470 | 471 | src := SampleStruct{ 472 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 473 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 474 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 475 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 476 | CreatedTime: time.Now(), 477 | } 478 | 479 | dst := SampleStruct{} 480 | 481 | errs := Copy(&dst, &src) 482 | if errs != nil { 483 | t.Error("Error occurred while copying.") 484 | } 485 | 486 | logSrcDst(t, src, dst) 487 | 488 | assertEqual(t, src.Name, dst.Name) 489 | assertEqual(t, src.Year, dst.Year) 490 | 491 | assertEqual(t, src.Level1Struct.Name, dst.Level1Struct.Name) 492 | assertEqual(t, src.Level1Struct.Year, dst.Level1Struct.Year) 493 | 494 | assertEqual(t, src.Level1StructPtr.Name, dst.Level1StructPtr.Name) 495 | assertEqual(t, src.Level1StructPtr.Year, dst.Level1StructPtr.Year) 496 | 497 | assertEqual(t, true, src.CreatedTime == dst.CreatedTime) 498 | assertEqual(t, src.Level1StructNoTraverse.Year, dst.Level1StructNoTraverse.Year) 499 | } 500 | 501 | func TestCopyStructEmbededAndAttributeDstPtr(t *testing.T) { 502 | type SampleSubInfo struct { 503 | Name string 504 | Year int 505 | } 506 | 507 | type SampleStruct struct { 508 | Level1Struct SampleSubInfo `model:",notraverse"` 509 | Level1StructPtr *SampleSubInfo 510 | Level1StructPtrZero *SampleSubInfo 511 | Level1StructNoTraverse *SampleSubInfo `model:",notraverse"` 512 | CreatedTime time.Time 513 | SampleSubInfo 514 | } 515 | 516 | src := SampleStruct{ 517 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 518 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 519 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 520 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 521 | CreatedTime: time.Now(), 522 | } 523 | 524 | dst := SampleStruct{ 525 | Level1StructPtrZero: &SampleSubInfo{Name: "This level 1 struct ptr zero", Year: 2015}, 526 | } 527 | 528 | errs := Copy(&dst, &src) 529 | if errs != nil { 530 | t.Error("Error occurred while copying.") 531 | } 532 | 533 | logSrcDst(t, src, dst) 534 | 535 | assertEqual(t, src.Name, dst.Name) 536 | assertEqual(t, src.Year, dst.Year) 537 | 538 | assertEqual(t, src.Level1Struct.Name, dst.Level1Struct.Name) 539 | assertEqual(t, src.Level1Struct.Year, dst.Level1Struct.Year) 540 | 541 | assertEqual(t, src.Level1StructPtr.Name, dst.Level1StructPtr.Name) 542 | assertEqual(t, src.Level1StructPtr.Year, dst.Level1StructPtr.Year) 543 | 544 | assertEqual(t, true, src.CreatedTime == dst.CreatedTime) 545 | assertEqual(t, src.Level1StructNoTraverse.Year, dst.Level1StructNoTraverse.Year) 546 | 547 | assertEqual(t, true, dst.Level1StructPtrZero == nil) 548 | } 549 | 550 | func TestCopyStructEmbededAndAttributeMakeZeroInDst(t *testing.T) { 551 | type SampleSubInfo struct { 552 | Name string 553 | Year int 554 | } 555 | 556 | type SampleStruct struct { 557 | Level1Struct SampleSubInfo `model:",notraverse"` 558 | Level1StructPtr *SampleSubInfo 559 | Level1StructNoTraverse *SampleSubInfo `model:",notraverse"` 560 | CreatedTime time.Time 561 | SampleSubInfo 562 | } 563 | 564 | src := SampleStruct{CreatedTime: time.Now()} 565 | 566 | dst := SampleStruct{ 567 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 568 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 569 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 570 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 571 | } 572 | 573 | errs := Copy(&dst, &src) 574 | if errs != nil { 575 | fmt.Println(errs) 576 | t.Error("Error occurred while copying.") 577 | } 578 | 579 | logSrcDst(t, src, dst) 580 | 581 | assertEqual(t, true, src.CreatedTime == dst.CreatedTime) 582 | 583 | assertEqual(t, true, IsZero(dst.Level1Struct)) 584 | assertEqual(t, true, IsZero(dst.SampleSubInfo)) 585 | 586 | assertEqual(t, true, dst.Level1StructPtr == nil) 587 | assertEqual(t, true, dst.Level1StructNoTraverse == nil) 588 | } 589 | 590 | func TestCopyStructEmbededAndAttributeOmitEmpty(t *testing.T) { 591 | type SampleSubInfo struct { 592 | Name string 593 | Year int 594 | } 595 | 596 | type SampleStruct struct { 597 | Level1Struct SampleSubInfo `model:",omitempty,notraverse"` 598 | Level1StructPtr *SampleSubInfo `model:",omitempty"` 599 | Level1StructNoTraverse *SampleSubInfo `model:",omitempty,notraverse"` 600 | CreatedTime time.Time 601 | SampleSubInfo `model:",omitempty"` 602 | } 603 | 604 | src := SampleStruct{CreatedTime: time.Now()} 605 | 606 | dst := SampleStruct{ 607 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 608 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 609 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 610 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 611 | } 612 | 613 | errs := Copy(&dst, src) 614 | if errs != nil { 615 | fmt.Println(errs) 616 | t.Error("Error occurred while copying.") 617 | } 618 | 619 | logSrcDst(t, src, dst) 620 | 621 | assertEqual(t, true, src.CreatedTime == dst.CreatedTime) 622 | 623 | assertEqual(t, 2016, dst.Year) 624 | assertEqual(t, "This embedded struct", dst.Name) 625 | 626 | assertEqual(t, 2013, dst.Level1StructNoTraverse.Year) 627 | assertEqual(t, "This nested no traverse struct", dst.Level1StructNoTraverse.Name) 628 | 629 | assertEqual(t, 2015, dst.Level1Struct.Year) 630 | assertEqual(t, "This level 1 struct", dst.Level1Struct.Name) 631 | 632 | assertEqual(t, 2014, dst.Level1StructPtr.Year) 633 | assertEqual(t, "This level 1 ptr struct", dst.Level1StructPtr.Name) 634 | } 635 | 636 | func TestCopyDestinationIsNotPointer(t *testing.T) { 637 | type SampleStruct struct { 638 | Name string 639 | } 640 | errs := Copy(SampleStruct{}, SampleStruct{Name: "Not a pointer"}) 641 | 642 | assertEqual(t, "Destination struct is not a pointer", errs[0].Error()) 643 | } 644 | 645 | func TestCopyInputIsNotStruct(t *testing.T) { 646 | type SampleStruct struct { 647 | Name string 648 | } 649 | errs := Copy(&SampleStruct{}, map[string]string{"1": "2001"}) 650 | 651 | assertEqual(t, "Source or Destination is not a struct", errs[0].Error()) 652 | } 653 | 654 | func TestCopyStructElementKindDiff(t *testing.T) { 655 | type Source struct { 656 | Name string 657 | } 658 | 659 | type Destination struct { 660 | Name int 661 | } 662 | 663 | errs := Copy(&Destination{}, Source{Name: "This struct element kind is different"}) 664 | 665 | assertEqual(t, "Field: 'Name', src [string] & dst [int] kind didn't match", errs[0].Error()) 666 | } 667 | 668 | func TestCopyStructElementTypeDiffOnLevel1(t *testing.T) { 669 | type SampleLevelSrc struct { 670 | Name string 671 | } 672 | 673 | type SampleLevelDst struct { 674 | Name int 675 | } 676 | 677 | type Source struct { 678 | Name string 679 | Level1 SampleLevelSrc 680 | } 681 | 682 | type Destination struct { 683 | Name int 684 | Level1 SampleLevelDst 685 | } 686 | 687 | src := Source{ 688 | Name: "This struct element kind is different", 689 | Level1: SampleLevelSrc{ 690 | Name: "Level1: This struct element kind is different", 691 | }, 692 | } 693 | 694 | dst := Destination{} 695 | 696 | errs := Copy(&dst, src) 697 | 698 | logSrcDst(t, src, dst) 699 | 700 | assertEqual(t, "Field: 'Name', src [string] & dst [int] kind didn't match", errs[0].Error()) 701 | assertEqual(t, 702 | "Field: 'Level1', src [model.SampleLevelSrc] & dst [model.SampleLevelDst] type didn't match", 703 | errs[1].Error(), 704 | ) 705 | } 706 | 707 | func TestCopyStructTypeDiffOnLevel1Interface(t *testing.T) { 708 | type SampleLevelSrc struct { 709 | Name string 710 | } 711 | 712 | type Source struct { 713 | Name string 714 | Level1 SampleLevelSrc 715 | } 716 | 717 | type Destination struct { 718 | Name int 719 | Level1 interface{} 720 | } 721 | 722 | src := Source{ 723 | Name: "This struct element kind is different", 724 | Level1: SampleLevelSrc{ 725 | Name: "Level1: This struct element kind is different", 726 | }, 727 | } 728 | 729 | dst := Destination{} 730 | 731 | errs := Copy(&dst, src) 732 | 733 | logSrcDst(t, src, dst) 734 | 735 | assertEqual(t, "Field: 'Name', src [string] & dst [int] kind didn't match", errs[0].Error()) 736 | assertEqual(t, 0, dst.Name) 737 | assertEqual(t, src.Level1.Name, dst.Level1.(SampleLevelSrc).Name) 738 | } 739 | 740 | func TestCopyStructZeroValToDst(t *testing.T) { 741 | type Source struct { 742 | Name string 743 | Year int 744 | } 745 | 746 | type Destination struct { 747 | Name string 748 | Year int 749 | } 750 | 751 | src := Source{Year: 2016} 752 | dst := Destination{Name: "Value is gonna disappear"} 753 | 754 | errs := Copy(&dst, src) 755 | if errs != nil { 756 | t.Error("Error occurred while copying.") 757 | } 758 | 759 | logSrcDst(t, src, dst) 760 | 761 | assertEqual(t, "", dst.Name) 762 | assertEqual(t, 2016, dst.Year) 763 | } 764 | 765 | // 766 | // NoTraverseTypeLis test cases 767 | // 768 | 769 | func TestAddNoTraverseType(t *testing.T) { 770 | if !isNoTraverseType(valueOf(os.File{})) { 771 | t.Errorf("Given type not found in omit list") 772 | } 773 | 774 | // Already registered 775 | AddNoTraverseType(os.File{}) 776 | } 777 | 778 | func TestRemoveNoTraverseType(t *testing.T) { 779 | RemoveNoTraverseType(os.File{}) 780 | 781 | if isNoTraverseType(valueOf(os.File{})) { 782 | t.Errorf("Type should not exists in the NoTraverseTypeList") 783 | } 784 | 785 | AddNoTraverseType(os.File{}) 786 | 787 | // test again 788 | if !isNoTraverseType(valueOf(os.File{})) { 789 | t.Errorf("Type should exists in the NoTraverseTypeList") 790 | } 791 | } 792 | 793 | // 794 | // Zero test cases 795 | // 796 | 797 | type SampleStruct struct { 798 | Integer int 799 | IntegerPtr *int 800 | String string 801 | StringPtr *string 802 | Boolean bool 803 | BooleanPtr *bool 804 | BooleanOmit bool `model:"-"` 805 | SliceString []string 806 | SliceStringOmit []string `model:"-"` 807 | SliceStringPtr *[]string 808 | SliceStringPtrOmit *[]string `model:"-"` 809 | SliceStringPtrStr []*string 810 | Float32 float32 811 | Float32Ptr *float32 812 | Float32Omit float32 `model:"-"` 813 | Float32PtrOmit *float32 `model:"-"` 814 | Float64 float64 815 | Float64Ptr *float64 816 | Float64Omit float64 `model:"-"` 817 | Float64PtrOmit *float64 `model:"-"` 818 | SliceStruct []SampleSubInfo 819 | SliceStructPtr []*SampleSubInfo 820 | SliceInt []int 821 | SliceIntPtr []*int 822 | Time time.Time 823 | TimePtr *time.Time 824 | Struct SampleSubInfo 825 | StructPtr *SampleSubInfo 826 | StructNoTraverse SampleSubInfo `model:",notraverse"` 827 | StructPtrNoTraverse *SampleSubInfo `model:",notraverse"` 828 | StructDeep SampleSubInfoDeep 829 | StructDeepPtr *SampleSubInfoDeep 830 | SampleSubInfo 831 | } 832 | 833 | type SampleSubInfo struct { 834 | Name string 835 | Year int 836 | } 837 | 838 | type SampleSubInfoDeep struct { 839 | Name string 840 | NamePtr *string `model:"-"` 841 | Year int `model:"-"` 842 | YearPtr *int 843 | Struct SampleSubInfo 844 | StructPtr *SampleSubInfo 845 | StructNoTraverse SampleSubInfo `model:",notraverse"` 846 | StructPtrNoTraverse *SampleSubInfo `model:",notraverse"` 847 | SampleSubInfo 848 | } 849 | 850 | func TestCopyZeroInput(t *testing.T) { 851 | errs := Copy(&SampleStruct{}, SampleStruct{}) 852 | assertEqual(t, "Source struct is empty", errs[0].Error()) 853 | 854 | errs = Copy(nil, nil) 855 | assertEqual(t, "Source or Destination is nil", errs[0].Error()) 856 | } 857 | 858 | func TestIsZero(t *testing.T) { 859 | IsZero(nil) // nil check 860 | 861 | if !IsZero(SampleStruct{}) { 862 | t.Error("SampleStruct - supposed to be zero") 863 | } 864 | 865 | if !IsZero(&SampleStruct{}) { 866 | t.Error("SampleStruct Ptr - supposed to be zero") 867 | } 868 | 869 | if !IsZero(&SampleStruct{Struct: SampleSubInfo{}, StructPtr: &SampleSubInfo{}}) { 870 | t.Error("SampleStruct with sub struct 1 - supposed to be zero") 871 | } 872 | 873 | if !IsZero(&SampleStruct{Struct: SampleSubInfo{Name: "go-model"}, StructPtr: &SampleSubInfo{}}) { 874 | t.Log("SampleStruct with sub struct 2 - supposed to be zero") 875 | } else { 876 | t.Error("SampleStruct with sub struct 2 - supposed to be zero") 877 | } 878 | 879 | deepStruct := SampleStruct{ 880 | StructDeepPtr: &SampleSubInfoDeep{ 881 | StructPtr: &SampleSubInfo{ 882 | Name: "I'm here", 883 | }, 884 | StructNoTraverse: SampleSubInfo{ 885 | Year: 2005, 886 | }, 887 | }, 888 | } 889 | if IsZero(deepStruct) { 890 | t.Error("SampleStruct deep level - supposed to be non-zero") 891 | } 892 | } 893 | 894 | func TestNonZeroCheck(t *testing.T) { 895 | if IsZero(&SampleStruct{Time: time.Now()}) { 896 | t.Error("SampleStruct notraverse - supposed to be zero") 897 | } 898 | 899 | if IsZero(SampleStruct{SampleSubInfo: SampleSubInfo{Year: 2010}}) { 900 | t.Error("SampleStruct embedded struct - supposed to be non-zero") 901 | } 902 | } 903 | 904 | func TestNonHasZeroCheck(t *testing.T) { 905 | type SampleSubInfo struct { 906 | Name string 907 | Year int 908 | } 909 | 910 | type SampleStruct struct { 911 | Level1Struct SampleSubInfo 912 | Level1StructPtr *SampleSubInfo 913 | Level1StructNoTraverse *SampleSubInfo `model:",notraverse"` 914 | CreatedTime time.Time 915 | SampleSubInfo 916 | } 917 | 918 | src1 := SampleStruct{ 919 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 920 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 921 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 922 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 923 | CreatedTime: time.Now(), 924 | } 925 | 926 | if HasZero(src1) { 927 | t.Error("SampleStruct supposed to be non-zero") 928 | } 929 | 930 | src2 := SampleStruct{ 931 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 932 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 933 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 934 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 935 | } 936 | 937 | if !HasZero(src2) { 938 | t.Error("SampleStruct supposed to have one-zero i.e. CreatedTime field") 939 | } 940 | 941 | src3 := SampleStruct{ 942 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 943 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 ptr struct", Year: 2014}, 944 | Level1StructNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2013}, 945 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct"}, 946 | } 947 | 948 | if !HasZero(src3) { 949 | t.Error("SampleStruct supposed to have one-zero i.e. SampleSubInfo -> Year field") 950 | } 951 | } 952 | 953 | func TestHasZeroForField(t *testing.T) { 954 | type SampleSubInfo struct { 955 | OmitThisField string `model:"-"` 956 | Name string 957 | Year int 958 | } 959 | 960 | if !HasZero(&SampleSubInfo{Name: "only I have populated"}) { 961 | t.Error("Supposed to have one-zero filed i.e. Year") 962 | } 963 | 964 | type SampleSrcStruct1 struct { 965 | Level1StructOmit SampleSubInfo `model:"-"` 966 | Level1StructPtr *SampleSubInfo 967 | Level1Struct SampleSubInfo 968 | Level1StructNoTraverse *SampleSubInfo `model:",notraverse"` 969 | CreatedTime time.Time 970 | SampleSubInfo 971 | } 972 | 973 | src1 := SampleSrcStruct1{} 974 | if !HasZero(src1) { 975 | t.Error("Suppose to be empty") 976 | } 977 | 978 | type SampleSrcStruct2 struct { 979 | Level1StructOmit SampleSubInfo 980 | Level1StructPtr *SampleSubInfo 981 | Level1Struct SampleSubInfo 982 | Level1StructNoTraverse *SampleSubInfo `model:",notraverse"` 983 | CreatedTime time.Time 984 | SampleSubInfo 985 | } 986 | 987 | src2 := SampleSrcStruct2{} 988 | if !HasZero(src2) { 989 | t.Error("Suppose to be empty") 990 | } 991 | 992 | } 993 | 994 | func TestIsStructMethod(t *testing.T) { 995 | src := map[string]interface{}{ 996 | "struct": &SampleStruct{Time: time.Now()}, 997 | } 998 | 999 | mv := valueOf(src) 1000 | keys := mv.MapKeys() 1001 | 1002 | assertEqual(t, true, isStruct(mv.MapIndex(keys[0]))) 1003 | } 1004 | 1005 | func TestIsZeroNotAStructInput(t *testing.T) { 1006 | result1 := IsZero(10001) 1007 | assertEqual(t, false, result1) 1008 | 1009 | result2 := IsZero(map[string]int{"1": 101, "2": 102, "3": 103}) 1010 | assertEqual(t, false, result2) 1011 | 1012 | floatVar := float64(1.7367643) 1013 | result3 := IsZero(&floatVar) 1014 | assertEqual(t, false, result3) 1015 | 1016 | str := "This is not a struct" 1017 | result4 := IsZero(&str) 1018 | assertEqual(t, false, result4) 1019 | } 1020 | 1021 | func TestHasZeroNotAStructInput(t *testing.T) { 1022 | result1 := HasZero(10001) 1023 | assertEqual(t, false, result1) 1024 | 1025 | result2 := HasZero(map[string]int{"1": 101, "2": 102, "3": 103}) 1026 | assertEqual(t, false, result2) 1027 | 1028 | floatVar := float64(1.7367643) 1029 | result3 := HasZero(&floatVar) 1030 | assertEqual(t, false, result3) 1031 | 1032 | str := "This is not a struct" 1033 | result4 := HasZero(&str) 1034 | assertEqual(t, false, result4) 1035 | 1036 | assertEqual(t, true, HasZero(nil)) 1037 | } 1038 | 1039 | // 1040 | // Map test cases 1041 | // 1042 | 1043 | func TestMapMethodValidation(t *testing.T) { 1044 | _, err1 := Map(nil) 1045 | assertEqual(t, "Invalid input ", err1.Error()) 1046 | 1047 | _, err2 := Map("not struct") 1048 | assertEqual(t, "Input is not a struct", err2.Error()) 1049 | } 1050 | 1051 | func TestMapIntegerAndIntegerPtrWithDefaultKeyName(t *testing.T) { 1052 | type SampleStruct struct { 1053 | Int int 1054 | IntPtr *int 1055 | Int64 int64 1056 | Int64Ptr *int64 1057 | } 1058 | 1059 | intPtr := int(1001) 1060 | int64Ptr := int64(1002) 1061 | 1062 | src := SampleStruct{ 1063 | Int: 2001, 1064 | IntPtr: &intPtr, 1065 | Int64: 2002, 1066 | Int64Ptr: &int64Ptr, 1067 | } 1068 | 1069 | result, err := Map(src) 1070 | if err != nil { 1071 | t.Error("Error occurred while Map export.") 1072 | } 1073 | 1074 | logSrcDst(t, src, result) 1075 | 1076 | // Assertion 1077 | 1078 | value1, found1 := result["Int"] 1079 | assertEqual(t, true, found1) 1080 | assertEqual(t, src.Int, value1) 1081 | 1082 | value2, found2 := result["Int64Ptr"] 1083 | assertEqual(t, true, found2) 1084 | assertEqual(t, *src.Int64Ptr, *value2.(*int64)) 1085 | } 1086 | 1087 | func TestMapIntegerAndIntegerPtrWithCustomKeyName(t *testing.T) { 1088 | type SampleStruct struct { 1089 | Int int `model:"int"` 1090 | IntPtr *int `model:"32Pointer"` 1091 | Int64 int64 `model:"int64"` 1092 | Int64Ptr *int64 `model:"64Pointer"` 1093 | } 1094 | 1095 | intPtr := int(1001) 1096 | int64Ptr := int64(1002) 1097 | 1098 | src := SampleStruct{ 1099 | Int: 2001, 1100 | IntPtr: &intPtr, 1101 | Int64: 2002, 1102 | Int64Ptr: &int64Ptr, 1103 | } 1104 | 1105 | result, err := Map(src) 1106 | if err != nil { 1107 | t.Error("Error occurred while Map export.") 1108 | } 1109 | 1110 | logSrcDst(t, src, result) 1111 | 1112 | // Assertion 1113 | 1114 | value1, found1 := result["int"] 1115 | assertEqual(t, true, found1) 1116 | assertEqual(t, src.Int, value1) 1117 | 1118 | value2, found2 := result["64Pointer"] 1119 | assertEqual(t, true, found2) 1120 | assertEqual(t, *src.Int64Ptr, *value2.(*int64)) 1121 | } 1122 | 1123 | func TestMapStringAndStringPtr(t *testing.T) { 1124 | type SampleStruct struct { 1125 | String string `model:"myStringKey"` 1126 | StringPtr *string 1127 | StringZero string `model:",omitempty"` 1128 | StringPtrZero string `model:",omitempty"` 1129 | } 1130 | 1131 | strPtr := "Map: This is string for pointer test" 1132 | src := SampleStruct{ 1133 | String: "Map: This is string for test", 1134 | StringPtr: &strPtr, 1135 | } 1136 | 1137 | result, err := Map(src) 1138 | if err != nil { 1139 | t.Error("Error occurred while Map export.") 1140 | } 1141 | 1142 | logSrcDst(t, src, result) 1143 | 1144 | // Assertion 1145 | 1146 | value1, found1 := result["myStringKey"] 1147 | assertEqual(t, src.String, value1) 1148 | assertEqual(t, true, found1) 1149 | 1150 | vaule2, found2 := result["StringPtr"] 1151 | assertEqual(t, *src.StringPtr, *vaule2.(*string)) 1152 | assertEqual(t, true, found2) 1153 | 1154 | _, notFound1 := result["StringZero"] 1155 | assertEqual(t, false, notFound1) 1156 | 1157 | _, notFound2 := result["StringPtrZero"] 1158 | assertEqual(t, false, notFound2) 1159 | } 1160 | 1161 | func TestMapBooleanAndBooleanPtr(t *testing.T) { 1162 | type SampleStruct struct { 1163 | Boolean bool 1164 | BooleanPtr *bool 1165 | } 1166 | 1167 | boolPtr := true 1168 | src := SampleStruct{ 1169 | Boolean: true, 1170 | BooleanPtr: &boolPtr, 1171 | } 1172 | 1173 | result, err := Map(src) 1174 | if err != nil { 1175 | t.Error("Error occurred while Map export.") 1176 | } 1177 | 1178 | logSrcDst(t, src, result) 1179 | 1180 | // Assertion 1181 | 1182 | value1, found1 := result["Boolean"] 1183 | assertEqual(t, true, found1) 1184 | assertEqual(t, src.Boolean, value1) 1185 | 1186 | value2, found2 := result["BooleanPtr"] 1187 | assertEqual(t, true, found2) 1188 | assertEqual(t, *src.BooleanPtr, *value2.(*bool)) 1189 | } 1190 | 1191 | func TestMapByteAndByteSlice(t *testing.T) { 1192 | type SampleStruct struct { 1193 | Byte byte 1194 | SliceBytes []byte 1195 | SliceBytesPtr *[]byte 1196 | } 1197 | 1198 | bytesPtr := []byte("This is byte pointer value") 1199 | 1200 | src := SampleStruct{ 1201 | Byte: byte('A'), 1202 | SliceBytes: []byte("This is byte value"), 1203 | SliceBytesPtr: &bytesPtr, 1204 | } 1205 | 1206 | result, err := Map(src) 1207 | if err != nil { 1208 | t.Error("Error occurred while Map export.") 1209 | } 1210 | 1211 | logSrcDst(t, src, result) 1212 | 1213 | value1, found1 := result["Byte"] 1214 | assertEqual(t, true, found1) 1215 | assertEqual(t, src.Byte, value1.(byte)) 1216 | 1217 | value2, found2 := result["SliceBytes"] 1218 | assertEqual(t, true, found2) 1219 | assertEqual(t, src.SliceBytes, value2.([]byte)) 1220 | assertEqual(t, string(src.SliceBytes), string(value2.([]byte))) 1221 | 1222 | value3, found3 := result["SliceBytesPtr"] 1223 | assertEqual(t, true, found3) 1224 | assertEqual(t, true, reflect.DeepEqual(src.SliceBytesPtr, value3.(*[]byte))) 1225 | } 1226 | 1227 | func TestMapSliceStringAndSliceStringPtr(t *testing.T) { 1228 | type SampleStruct struct { 1229 | SliceString []string 1230 | SliceStringPtr *[]string 1231 | } 1232 | 1233 | sliceStrPtr := []string{ 1234 | "Val1: This is slice string test pointer.", 1235 | "Val2: This is slice string test pointer.", 1236 | } 1237 | 1238 | src := SampleStruct{ 1239 | SliceString: []string{ 1240 | "Val1: This is slice string test.", 1241 | "Val2: This is slice string test.", 1242 | }, 1243 | SliceStringPtr: &sliceStrPtr, 1244 | } 1245 | 1246 | result, err := Map(src) 1247 | if err != nil { 1248 | t.Error("Error occurred while Map export.") 1249 | } 1250 | 1251 | logSrcDst(t, src, result) 1252 | 1253 | // Assertion 1254 | 1255 | value1, found1 := result["SliceString"] 1256 | assertEqual(t, true, found1) 1257 | assertEqual(t, src.SliceString, value1) 1258 | 1259 | value2, found2 := result["SliceStringPtr"] 1260 | assertEqual(t, true, found2) 1261 | assertEqual(t, *src.SliceStringPtr, *value2.(*[]string)) 1262 | } 1263 | 1264 | func TestMapSliceElementsPtr(t *testing.T) { 1265 | type SampleSubInfo2 struct { 1266 | SliceIntPtr []*int 1267 | SliceInt64Ptr []*int64 1268 | SliceStringPtr []*string `model:"stringPtr"` 1269 | SliceFloat32Omit []*float32 `model:"-"` 1270 | SliceFloat32 []*float32 1271 | SliceFloat64 []*float64 1272 | SliceInterface []interface{} `model:"interface"` 1273 | } 1274 | 1275 | type SampleSubInfo1 struct { 1276 | SliceIntPtr []*int 1277 | SliceInt64Ptr []*int64 `model:"int64Ptr"` 1278 | SliceStringPtr []*string 1279 | SliceFloat32 []*float32 1280 | SliceFloat64 []*float64 `model:"float64Ptr"` 1281 | SliceInterface []interface{} `model:"interface"` 1282 | Level2 SampleSubInfo2 `model:"level2"` 1283 | } 1284 | 1285 | type SampleStruct struct { 1286 | SliceIntPtr []*int `model:"intPtr"` 1287 | SliceInt64Ptr []*int64 1288 | SliceStringPtr []*string `model:"stringPtr"` 1289 | SliceFloat32 []*float32 `model:"float32"` 1290 | SliceFloat64 []*float64 1291 | SliceInterface []interface{} `model:"interface"` 1292 | Level1 SampleSubInfo1 `model:"level1"` 1293 | } 1294 | 1295 | i1 := int(1) 1296 | i2 := int(2) 1297 | i3 := int(3) 1298 | 1299 | i11 := int64(11) 1300 | i12 := int64(12) 1301 | i13 := int64(13) 1302 | 1303 | str1 := "This is string pointer 1" 1304 | str2 := "This is string pointer 2" 1305 | str3 := "This is string pointer 3" 1306 | 1307 | f1 := float32(0.1) 1308 | f2 := float32(0.2) 1309 | f3 := float32(0.3) 1310 | 1311 | f11 := float64(0.11) 1312 | f12 := float64(0.12) 1313 | f13 := float64(0.13) 1314 | 1315 | src := SampleStruct{ 1316 | SliceIntPtr: []*int{&i1, &i2, &i3}, 1317 | SliceInt64Ptr: []*int64{&i11, &i12, &i13}, 1318 | SliceStringPtr: []*string{&str1, &str2, &str3}, 1319 | SliceFloat32: []*float32{&f1, &f2, &f3}, 1320 | SliceFloat64: []*float64{&f11, &f12, &f13}, 1321 | SliceInterface: []interface{}{&i1, i11, &str1, &f1, f11}, 1322 | Level1: SampleSubInfo1{ 1323 | SliceIntPtr: []*int{&i1, &i2, &i3}, 1324 | SliceInt64Ptr: []*int64{&i11, &i12, &i13}, 1325 | SliceStringPtr: []*string{&str1, &str2, &str3}, 1326 | SliceFloat32: []*float32{&f1, &f2, &f3}, 1327 | SliceFloat64: []*float64{&f11, &f12, &f13}, 1328 | SliceInterface: []interface{}{&i1, i11, &str1, &f1, f11}, 1329 | Level2: SampleSubInfo2{ 1330 | SliceIntPtr: []*int{&i1, &i2, &i3}, 1331 | SliceInt64Ptr: []*int64{&i11, &i12, &i13}, 1332 | SliceStringPtr: []*string{&str1, &str2, &str3}, 1333 | SliceFloat32: []*float32{&f1, &f2, &f3}, 1334 | SliceFloat64: []*float64{&f11, &f12, &f13}, 1335 | SliceInterface: []interface{}{&i1, i11, &str1, &f1, f11}, 1336 | }, 1337 | }, 1338 | } 1339 | 1340 | result, err := Map(src) 1341 | if err != nil { 1342 | t.Error("Error occurred while Map export.") 1343 | } 1344 | 1345 | logSrcDst(t, src, result) 1346 | 1347 | // Assertion 1348 | 1349 | // Level 0 assertion 1350 | value1, _ := result["intPtr"] 1351 | assertEqual(t, src.SliceIntPtr, value1.([]*int)) 1352 | 1353 | value2, _ := result["SliceInt64Ptr"] 1354 | assertEqual(t, src.SliceInt64Ptr, value2.([]*int64)) 1355 | 1356 | value3, _ := result["stringPtr"] 1357 | assertEqual(t, src.SliceStringPtr, value3.([]*string)) 1358 | 1359 | value4, _ := result["float32"] 1360 | assertEqual(t, src.SliceFloat32, value4.([]*float32)) 1361 | 1362 | value5, _ := result["SliceFloat64"] 1363 | assertEqual(t, src.SliceFloat64, value5.([]*float64)) 1364 | 1365 | value6, _ := result["interface"] 1366 | assertEqual(t, src.SliceInterface, value6.([]interface{})) 1367 | 1368 | // // Level 1 assertion 1369 | l1, _ := result["level1"].(map[string]interface{}) 1370 | 1371 | l1value1, _ := l1["SliceIntPtr"] 1372 | assertEqual(t, src.Level1.SliceIntPtr, l1value1.([]*int)) 1373 | 1374 | l1value2, _ := l1["int64Ptr"] 1375 | assertEqual(t, src.Level1.SliceInt64Ptr, l1value2.([]*int64)) 1376 | 1377 | l1value3, _ := l1["SliceStringPtr"] 1378 | assertEqual(t, src.Level1.SliceStringPtr, l1value3.([]*string)) 1379 | 1380 | l1value4, _ := l1["SliceFloat32"] 1381 | assertEqual(t, src.Level1.SliceFloat32, l1value4.([]*float32)) 1382 | 1383 | l1value5, _ := l1["float64Ptr"] 1384 | assertEqual(t, src.Level1.SliceFloat64, l1value5.([]*float64)) 1385 | 1386 | l1value6, _ := l1["interface"] 1387 | assertEqual(t, src.Level1.SliceInterface, l1value6.([]interface{})) 1388 | 1389 | // // Level 2 assertion 1390 | l2, _ := l1["level2"].(map[string]interface{}) 1391 | 1392 | l2value1, _ := l2["SliceIntPtr"] 1393 | assertEqual(t, src.Level1.Level2.SliceIntPtr, l2value1.([]*int)) 1394 | 1395 | l2value2, _ := l2["SliceInt64Ptr"] 1396 | assertEqual(t, src.Level1.Level2.SliceInt64Ptr, l2value2.([]*int64)) 1397 | 1398 | l2value3, _ := l2["stringPtr"] 1399 | assertEqual(t, src.Level1.Level2.SliceStringPtr, l2value3.([]*string)) 1400 | 1401 | l2value4, _ := l2["SliceFloat32"] 1402 | assertEqual(t, src.Level1.Level2.SliceFloat32, l2value4.([]*float32)) 1403 | 1404 | l2value5, _ := l2["SliceFloat64"] 1405 | assertEqual(t, src.Level1.Level2.SliceFloat64, l2value5.([]*float64)) 1406 | 1407 | l2value6, _ := l2["interface"] 1408 | assertEqual(t, src.Level1.Level2.SliceInterface, l2value6.([]interface{})) 1409 | } 1410 | 1411 | func TestMapMapElements(t *testing.T) { 1412 | type SampleSubInfo struct { 1413 | Name string 1414 | Year int 1415 | } 1416 | 1417 | type SampleStruct struct { 1418 | MapIntInt map[int]int 1419 | MapStringInt map[string]int `model:"stringInt"` 1420 | MapStringString map[string]string 1421 | MapStruct map[string]SampleSubInfo 1422 | MapInterfaces map[string]interface{} 1423 | } 1424 | 1425 | src := SampleStruct{ 1426 | MapIntInt: map[int]int{1: 1001, 2: 1002, 3: 1003, 4: 1004}, 1427 | MapStringInt: map[string]int{"first": 1001, "second": 1002, "third": 1003, "forth": 1004}, 1428 | MapStringString: map[string]string{"first": "1001", "second": "1002", "third": "1003"}, 1429 | MapStruct: map[string]SampleSubInfo{ 1430 | "struct1": {Name: "struct 1 value", Year: 2001}, 1431 | "struct2": {Name: "struct 2 value", Year: 2002}, 1432 | "struct3": {Name: "struct 3 value", Year: 2003}, 1433 | }, 1434 | MapInterfaces: map[string]interface{}{ 1435 | "inter1": 100001, 1436 | "inter2": "This is my interface string", 1437 | "inter3": SampleSubInfo{Name: "inter3: struct 1 value", Year: 2003}, 1438 | "inter4": float32(1.6546565), 1439 | "inter5": float64(1.6546565), 1440 | "inter6": &SampleSubInfo{Name: "inter6: struct 2 value", Year: 2006}, 1441 | "l1map1": map[int]int{1: 1001, 2: 1002, 3: 1003, 4: 1004}, 1442 | "l1map2": map[string]int{"first": 1001, "second": 1002, "third": 1003, "forth": 1004}, 1443 | "l2map1": map[string]interface{}{ 1444 | "struct1": SampleSubInfo{Name: "l2map1: struct 1 value", Year: 2001}, 1445 | "struct2": SampleSubInfo{Name: "l2map1: struct 2 value", Year: 2002}, 1446 | "struct3": SampleSubInfo{Name: "l2map1: struct 3 value", Year: 2003}, 1447 | "l3map1": map[string]string{"first": "1001", "second": "1002", "third": "1003"}, 1448 | }, 1449 | }, 1450 | } 1451 | 1452 | result, err := Map(src) 1453 | if err != nil { 1454 | t.Error("Error occurred while Map export.") 1455 | } 1456 | 1457 | logSrcDst(t, src, result) 1458 | 1459 | // Assertion 1460 | 1461 | // Field: MapIntInt 1462 | value1, found1 := result["MapIntInt"].(map[string]interface{}) 1463 | assertEqual(t, true, found1) 1464 | 1465 | value11, found11 := value1["2"] 1466 | assertEqual(t, true, found11) 1467 | assertEqual(t, 1002, value11) 1468 | 1469 | // Field: MapStringString 1470 | value2, found2 := result["MapStringString"].(map[string]interface{}) 1471 | assertEqual(t, true, found2) 1472 | 1473 | value21, found21 := value2["third"] 1474 | assertEqual(t, true, found21) 1475 | assertEqual(t, "1003", value21) 1476 | 1477 | // Field: MapStruct -> struct2 -> Name 1478 | value3, found3 := result["MapStruct"].(map[string]interface{}) 1479 | assertEqual(t, true, found3) 1480 | 1481 | value31, found31 := value3["struct2"].(map[string]interface{}) 1482 | assertEqual(t, true, found31) 1483 | 1484 | value32, found32 := value31["Name"] 1485 | assertEqual(t, true, found32) 1486 | assertEqual(t, "struct 2 value", value32) 1487 | 1488 | // Field: MapInterfaces -> inter4 1489 | value4, found4 := result["MapInterfaces"].(map[string]interface{}) 1490 | assertEqual(t, true, found4) 1491 | 1492 | value41, found41 := value4["inter4"] 1493 | assertEqual(t, true, found41) 1494 | assertEqual(t, float32(1.6546565), value41.(float32)) 1495 | 1496 | // Field: MapInterfaces -> inter6 -> Name 1497 | value42, found42 := value4["inter6"].(*map[string]interface{}) 1498 | assertEqual(t, true, found42) 1499 | assertEqual(t, true, value42 != nil) 1500 | 1501 | // Field: MapInterfaces -> l1map1 -> 4 1502 | value43, found43 := value4["l1map1"].(map[string]interface{}) 1503 | assertEqual(t, true, found43) 1504 | 1505 | value431, found431 := value43["4"] 1506 | assertEqual(t, true, found431) 1507 | assertEqual(t, 1004, value431.(int)) 1508 | 1509 | // Field: MapInterfaces -> l2map1 -> struct3 -> Name 1510 | value44, found44 := value4["l2map1"].(map[string]interface{}) 1511 | assertEqual(t, true, found44) 1512 | 1513 | value441, found441 := value44["struct3"].(map[string]interface{}) 1514 | assertEqual(t, true, found441) 1515 | 1516 | value4411, found4411 := value441["Name"] 1517 | assertEqual(t, true, found4411) 1518 | assertEqual(t, "l2map1: struct 3 value", value4411.(string)) 1519 | 1520 | // Field: MapInterfaces -> l2map1 -> l3map1 -> first 1521 | value442, found442 := value44["l3map1"].(map[string]interface{}) 1522 | assertEqual(t, true, found442) 1523 | 1524 | value4421, found4421 := value442["first"] 1525 | assertEqual(t, true, found4421) 1526 | assertEqual(t, "1001", value4421.(string)) 1527 | } 1528 | 1529 | func TestMapStructEmbededAndAttribute(t *testing.T) { 1530 | type SampleSubInfo struct { 1531 | Name string 1532 | Year int `model:"year"` 1533 | Goal string 1534 | } 1535 | 1536 | type SampleStruct struct { 1537 | Level1Struct SampleSubInfo `model:"level1Struct"` 1538 | Level1StructNoTraverse SampleSubInfo `model:",notraverse"` 1539 | Level1StructPtr *SampleSubInfo 1540 | Level1StructPtrNoTraverse *SampleSubInfo `model:",notraverse"` 1541 | Level1StructEmpty SampleSubInfo `model:",omitempty"` 1542 | Level1StructPtrEmpty *SampleSubInfo `model:",omitempty"` 1543 | CreatedTime time.Time `model:"created_time"` 1544 | CreatedTimePtr *time.Time `model:"created_time_ptr"` 1545 | UpdateTimeOmitEmpty time.Time `model:"update_time,omitempty"` 1546 | SampleSubInfo 1547 | } 1548 | 1549 | timePtr := time.Now() 1550 | src := SampleStruct{ 1551 | SampleSubInfo: SampleSubInfo{Name: "This embedded struct", Year: 2016}, 1552 | Level1Struct: SampleSubInfo{Name: "This level 1 struct", Year: 2015}, 1553 | Level1StructNoTraverse: SampleSubInfo{Name: "This level 1 struct no traverse", Year: 2014}, 1554 | Level1StructPtr: &SampleSubInfo{Name: "This level 1 struct pointer", Year: 2013}, 1555 | Level1StructPtrNoTraverse: &SampleSubInfo{Name: "This nested no traverse struct", Year: 2012}, 1556 | CreatedTime: time.Now(), 1557 | CreatedTimePtr: &timePtr, 1558 | } 1559 | 1560 | result, err := Map(src) 1561 | if err != nil { 1562 | t.Error("Error occurred while Map export.") 1563 | } 1564 | 1565 | logSrcDst(t, src, result) 1566 | 1567 | // Assertion 1568 | 1569 | // Embedded struct assertion 1570 | // Field: Name 1571 | value1, found1 := result["Name"] 1572 | assertEqual(t, true, found1) 1573 | assertEqual(t, "This embedded struct", value1.(string)) 1574 | 1575 | // Field: year 1576 | value2, found2 := result["year"] 1577 | assertEqual(t, true, found2) 1578 | assertEqual(t, 2016, value2.(int)) 1579 | 1580 | // Field: level1Struct -> Name 1581 | value3, found3 := result["level1Struct"].(map[string]interface{}) 1582 | assertEqual(t, true, found3) 1583 | 1584 | value31, found31 := value3["Name"] 1585 | assertEqual(t, true, found31) 1586 | assertEqual(t, "This level 1 struct", value31.(string)) 1587 | 1588 | // Field: level1Struct -> Goal (should be empty) 1589 | value32, found32 := value3["Goal"] 1590 | assertEqual(t, true, found32) 1591 | assertEqual(t, "", value32.(string)) 1592 | 1593 | // Field: created_time 1594 | value4, found4 := result["created_time"] 1595 | assertEqual(t, true, found4) 1596 | assertEqual(t, true, src.CreatedTime == value4.(time.Time)) 1597 | 1598 | value5, found5 := result["created_time_ptr"] 1599 | assertEqual(t, true, found5) 1600 | assertEqual(t, true, src.CreatedTimePtr != value5.(*time.Time)) 1601 | 1602 | // Field should not exists: Level1StructEmpty, Level1StructPtrEmpty, UpdateTimeOmitEmpty 1603 | _, notfound1 := result["Level1StructEmpty"] 1604 | assertEqual(t, false, notfound1) 1605 | 1606 | _, notfound2 := result["Level1StructPtrEmpty"] 1607 | assertEqual(t, false, notfound2) 1608 | 1609 | _, notfound3 := result["UpdateTimeOmitEmpty"] 1610 | assertEqual(t, false, notfound3) 1611 | } 1612 | 1613 | func TestMapSliceStructAndSliceStructPtr(t *testing.T) { 1614 | type SampleSubInfo struct { 1615 | Name string 1616 | Year int `model:"year"` 1617 | Goal string 1618 | } 1619 | type SampleStruct struct { 1620 | SliceStruct []SampleSubInfo 1621 | SliceStructPtr *[]SampleSubInfo 1622 | } 1623 | 1624 | sliceStructPtr := []SampleSubInfo{ 1625 | {Name: "Struct: Slice Ptr 1", Year: 2016}, 1626 | {Name: "Struct: Slice Ptr 2", Year: 2015}, 1627 | {Name: "Struct: Slice Ptr 3", Year: 2014}, 1628 | } 1629 | src := SampleStruct{ 1630 | SliceStruct: []SampleSubInfo{ 1631 | {Name: "Struct: Slice 1", Year: 2006}, 1632 | {Name: "Struct: Slice 2", Year: 2005}, 1633 | {Name: "Struct: Slice 3", Year: 2004}, 1634 | }, 1635 | SliceStructPtr: &sliceStructPtr, 1636 | } 1637 | 1638 | result, err := Map(src) 1639 | if err != nil { 1640 | t.Error("Error occurred while Map export.") 1641 | } 1642 | 1643 | logSrcDst(t, src, result) 1644 | 1645 | value1 := result["SliceStruct"].([]interface{})[0].(map[string]interface{}) 1646 | 1647 | value11, found11 := value1["Name"] 1648 | assertEqual(t, true, found11) 1649 | assertEqual(t, src.SliceStruct[0].Name, value11.(string)) 1650 | 1651 | value12, found12 := value1["year"] 1652 | assertEqual(t, true, found12) 1653 | assertEqual(t, src.SliceStruct[0].Year, value12.(int)) 1654 | 1655 | value13, found13 := value1["Goal"] 1656 | assertEqual(t, true, found13) 1657 | assertEqual(t, src.SliceStruct[0].Goal, value13.(string)) 1658 | } 1659 | 1660 | func TestCloneInputNil(t *testing.T) { 1661 | result, err := Clone(nil) 1662 | 1663 | assertEqual(t, "Invalid input ", err.Error()) 1664 | assertEqual(t, true, result == nil) 1665 | } 1666 | 1667 | func TestCloneNotAStruct(t *testing.T) { 1668 | result, err := Clone("I'm not a struct") 1669 | 1670 | assertEqual(t, "Input is not a struct", err.Error()) 1671 | assertEqual(t, true, result == nil) 1672 | } 1673 | 1674 | func TestCloneStruct(t *testing.T) { 1675 | type SampleInfo struct { 1676 | Name string 1677 | Year int `model:"year"` 1678 | Goal string 1679 | } 1680 | 1681 | src := SampleInfo{Name: "My name is go-model", Year: 2016} 1682 | 1683 | result, err := Clone(src) 1684 | 1685 | assertEqual(t, true, result != nil) 1686 | assertEqual(t, true, err == nil) 1687 | assertEqual(t, src.Name, result.(*SampleInfo).Name) 1688 | assertEqual(t, src.Year, result.(*SampleInfo).Year) 1689 | } 1690 | 1691 | func TestCloneStructPtr(t *testing.T) { 1692 | type SampleInfo struct { 1693 | Name string 1694 | Year int `model:"year"` 1695 | Goal string 1696 | } 1697 | 1698 | src := SampleInfo{Name: "My name is go-model ptr", Year: 2015} 1699 | 1700 | result, err := Clone(&src) 1701 | 1702 | assertEqual(t, true, result != nil) 1703 | assertEqual(t, true, err == nil) 1704 | assertEqual(t, src.Name, result.(*SampleInfo).Name) 1705 | assertEqual(t, src.Year, result.(*SampleInfo).Year) 1706 | } 1707 | 1708 | // 1709 | // IsZeroInFields test case 1710 | // 1711 | 1712 | func TestIsZeroInFields(t *testing.T) { 1713 | type SampleInfo struct { 1714 | Name string 1715 | Year int 1716 | Level2 float32 1717 | } 1718 | 1719 | type SampleStruct struct { 1720 | Name string 1721 | Year int `model:"year"` 1722 | Goal string 1723 | Level1Struct SampleInfo 1724 | Level1StructPtr *SampleInfo 1725 | Level1StructNoTraverse *SampleInfo `model:",notraverse"` 1726 | CreatedTime time.Time 1727 | } 1728 | 1729 | _, basic1 := IsZeroInFields(nil, "TestField") 1730 | assertEqual(t, true, basic1) 1731 | 1732 | _, basic2 := IsZeroInFields(SampleStruct{}) 1733 | assertEqual(t, true, basic2) 1734 | 1735 | _, basic3 := IsZeroInFields("I'm not a struct", "TestField") 1736 | assertEqual(t, false, basic3) 1737 | 1738 | src1 := SampleStruct{ 1739 | Name: "I'm Name", 1740 | Year: 2016, 1741 | Goal: "To test IsZeroInFields", 1742 | Level1Struct: SampleInfo{Name: "This level 1 struct", Year: 2015}, 1743 | Level1StructPtr: &SampleInfo{Name: "This level 1 ptr struct", Year: 2014}, 1744 | Level1StructNoTraverse: &SampleInfo{Name: "This nested no traverse struct", Year: 2013}, 1745 | CreatedTime: time.Now(), 1746 | } 1747 | 1748 | name1, zero1 := IsZeroInFields(src1, "Name", "Year", "Level1StructNoTraverse") 1749 | assertEqual(t, false, zero1) 1750 | assertEqual(t, "", name1) 1751 | 1752 | src2 := SampleStruct{ 1753 | Name: "I'm Name", 1754 | Year: 2016, 1755 | Goal: "To test IsZeroInFields", 1756 | Level1Struct: SampleInfo{Name: "This level 1 struct", Year: 2015}, 1757 | Level1StructNoTraverse: &SampleInfo{Name: "This nested no traverse struct", Year: 2013}, 1758 | CreatedTime: time.Now(), 1759 | } 1760 | name2, zero2 := IsZeroInFields(src2, "Goal", "Level1Struct", "Level1StructNoTraverse", "Level1StructPtr") 1761 | assertEqual(t, true, zero2) 1762 | assertEqual(t, "Level1StructPtr", name2) 1763 | 1764 | src3 := SampleStruct{ 1765 | Name: "I'm Name", 1766 | Year: 2016, 1767 | Goal: "To test IsZeroInFields", 1768 | Level1Struct: SampleInfo{Name: "This level 1 struct", Year: 2015}, 1769 | Level1StructPtr: &SampleInfo{Name: "This level 1 ptr struct", Year: 2014}, 1770 | Level1StructNoTraverse: &SampleInfo{Name: "This nested no traverse struct"}, 1771 | CreatedTime: time.Now(), 1772 | } 1773 | name3, zero3 := IsZeroInFields(src3, "Year1") 1774 | assertEqual(t, false, zero3) 1775 | assertEqual(t, "", name3) 1776 | } 1777 | 1778 | func TestIsZeroInFieldsEmbedded(t *testing.T) { 1779 | type SampleInfo struct { 1780 | Name string 1781 | Year int 1782 | Level2 float32 1783 | } 1784 | 1785 | type SampleStruct struct { 1786 | Level1Struct SampleInfo 1787 | Level1StructPtr *SampleInfo 1788 | Level1StructNoTraverse SampleInfo `model:",notraverse"` 1789 | CreatedTime time.Time 1790 | SampleInfo 1791 | } 1792 | 1793 | src1 := SampleStruct{ 1794 | Level1Struct: SampleInfo{Name: "This level 1 struct", Year: 2015}, 1795 | Level1StructPtr: &SampleInfo{Name: "This level 1 ptr struct", Year: 2014}, 1796 | CreatedTime: time.Now(), 1797 | SampleInfo: SampleInfo{ 1798 | Name: "I'm Name", 1799 | Year: 2016, 1800 | }, 1801 | } 1802 | 1803 | name1, zero1 := IsZeroInFields(src1, "SampleInfo") 1804 | assertEqual(t, false, zero1) 1805 | assertEqual(t, "", name1) 1806 | 1807 | name2, zero2 := IsZeroInFields(SampleStruct{}, "SampleInfo") 1808 | assertEqual(t, true, zero2) 1809 | assertEqual(t, "SampleInfo", name2) 1810 | } 1811 | 1812 | func TestFields(t *testing.T) { 1813 | type SampleInfo struct { 1814 | Name string 1815 | Year int 1816 | Level2 float32 1817 | } 1818 | 1819 | type SampleStruct struct { 1820 | Level1Struct SampleInfo 1821 | Level1StructPtr *SampleInfo 1822 | Level1StructNoTraverse SampleInfo `model:",notraverse"` 1823 | CreatedTime time.Time 1824 | SampleInfo 1825 | } 1826 | 1827 | fields1, err1 := Fields(nil) 1828 | assertEqual(t, true, err1.Error() == "Invalid input ") 1829 | assertEqual(t, true, fields1 == nil) 1830 | 1831 | fields2, err2 := Fields(&SampleStruct{}) 1832 | 1833 | assertError(t, err2) 1834 | assertEqual(t, true, len(fields2) > 0) 1835 | } 1836 | 1837 | func TestKind(t *testing.T) { 1838 | type SampleInfo struct { 1839 | MapIntInt map[int]int 1840 | MapStringInt map[string]int `model:"stringInt"` 1841 | MapStringString map[string]string 1842 | } 1843 | 1844 | type SampleStruct struct { 1845 | Name string `json:"name,omitempty"` 1846 | Year int `json:"year"` 1847 | Level2 float32 `json:"level2"` 1848 | Struct SampleInfo `json:"struct"` 1849 | StructPtr *SampleInfo `json:"struct_ptr"` 1850 | Level1StructNoTraverse SampleInfo `model:",notraverse"` 1851 | CreatedTime time.Time `json:"created_time,omitempty"` 1852 | } 1853 | 1854 | s := SampleStruct{} 1855 | 1856 | kind1, err1 := Kind(s, "Name") 1857 | assertError(t, err1) 1858 | assertEqual(t, true, reflect.String == kind1) 1859 | 1860 | kind2, err2 := Kind(s, "StructPtr") 1861 | assertError(t, err2) 1862 | assertEqual(t, true, reflect.Ptr == kind2) 1863 | 1864 | kind3, err3 := Kind(s, "CreatedTime") 1865 | assertError(t, err3) 1866 | assertEqual(t, true, reflect.Struct == kind3) 1867 | 1868 | kind4, err4 := Kind(s, "Level2") 1869 | assertError(t, err4) 1870 | assertEqual(t, true, reflect.Float32 == kind4) 1871 | 1872 | kind5, err5 := Kind(nil, "NoExists") 1873 | assertEqual(t, "Invalid input ", err5.Error()) 1874 | assertEqual(t, true, reflect.Invalid == kind5) 1875 | 1876 | kind6, err6 := Kind(s, "NoExists") 1877 | assertEqual(t, "Field: 'NoExists', does not exists", err6.Error()) 1878 | assertEqual(t, true, reflect.Invalid == kind6) 1879 | } 1880 | 1881 | func TestNestedStructToStructMapping(t *testing.T) { 1882 | type C struct { 1883 | X string 1884 | } 1885 | 1886 | type A struct { 1887 | V C 1888 | } 1889 | 1890 | type B struct { 1891 | V C 1892 | } 1893 | 1894 | a := A{V: C{"1"}} 1895 | b := B{} 1896 | 1897 | Copy(&b, &a) 1898 | 1899 | assertEqual(t, a.V.X, b.V.X) 1900 | } 1901 | 1902 | func TestStructToStructPtrWithConverter(t *testing.T) { 1903 | type C struct { 1904 | X string 1905 | } 1906 | 1907 | type D struct { 1908 | X *string 1909 | } 1910 | 1911 | type A struct { 1912 | V C 1913 | } 1914 | 1915 | type B struct { 1916 | V D 1917 | } 1918 | 1919 | a := A{V: C{"1"}} 1920 | b := B{} 1921 | 1922 | AddConversion(&C{}, &D{}, func(in reflect.Value) (reflect.Value, error) { 1923 | x := in.Interface().(C).X 1924 | d := D{X: &x} 1925 | return reflect.ValueOf(d), nil 1926 | }) 1927 | 1928 | Copy(&b, &a) 1929 | 1930 | assertEqual(t, a.V.X, *b.V.X) 1931 | } 1932 | 1933 | func TestStructWithConverter(t *testing.T) { 1934 | 1935 | type C struct { 1936 | X string 1937 | } 1938 | 1939 | type D struct { 1940 | X string 1941 | } 1942 | 1943 | type A struct { 1944 | V C 1945 | } 1946 | 1947 | type B struct { 1948 | V D 1949 | } 1950 | 1951 | a := A{V: C{"1"}} 1952 | b := B{} 1953 | 1954 | AddConversion(&C{}, &D{}, func(in reflect.Value) (reflect.Value, error) { 1955 | x := in.Interface().(C).X 1956 | d := D{X: x} 1957 | return reflect.ValueOf(d), nil 1958 | }) 1959 | 1960 | Copy(&b, &a) 1961 | 1962 | assertEqual(t, a.V.X, b.V.X) 1963 | } 1964 | 1965 | func TestSliceWithConverter(t *testing.T) { 1966 | 1967 | type C struct { 1968 | X string 1969 | } 1970 | 1971 | type D struct { 1972 | X string 1973 | } 1974 | 1975 | type A struct { 1976 | V []C 1977 | } 1978 | 1979 | type B struct { 1980 | V []D 1981 | } 1982 | 1983 | a := A{V: []C{{"1"}, {"2"}}} 1984 | b := B{} 1985 | 1986 | AddConversion(&C{}, &D{}, func(in reflect.Value) (reflect.Value, error) { 1987 | x := in.Interface().(C).X 1988 | d := D{X: x} 1989 | return reflect.ValueOf(d), nil 1990 | }) 1991 | 1992 | Copy(&b, &a) 1993 | 1994 | assertEqual(t, a.V[0].X, b.V[0].X) 1995 | assertEqual(t, a.V[1].X, b.V[1].X) 1996 | 1997 | } 1998 | 1999 | func TestMapWithConverter(t *testing.T) { 2000 | type C struct { 2001 | X string 2002 | } 2003 | 2004 | type D struct { 2005 | X string 2006 | } 2007 | 2008 | type A struct { 2009 | M map[string]C 2010 | } 2011 | 2012 | type B struct { 2013 | M map[string]D 2014 | } 2015 | 2016 | a := A{M: map[string]C{"1": {"1"}, "2": {"2"}, "3": {"error"}}} 2017 | b := B{} 2018 | 2019 | AddConversion(&C{}, &D{}, func(in reflect.Value) (reflect.Value, error) { 2020 | x := in.Interface().(C).X 2021 | d := D{X: x} 2022 | if x == "error" { 2023 | return reflect.ValueOf(d), errors.New("Custom conversion failed.") 2024 | } 2025 | return reflect.ValueOf(d), nil 2026 | }) 2027 | 2028 | errs := Copy(&b, &a) 2029 | assertEqual(t, a.M["1"].X, b.M["1"].X) 2030 | assertEqual(t, a.M["2"].X, b.M["2"].X) 2031 | assertEqual(t, "Custom conversion failed.", errs[0].Error()) 2032 | } 2033 | 2034 | func TestGetField(t *testing.T) { 2035 | type SampleStruct struct { 2036 | Int int 2037 | String string 2038 | } 2039 | 2040 | src := SampleStruct{ 2041 | Int: 10, 2042 | String: "go-model", 2043 | } 2044 | 2045 | // scenario 1 int 2046 | value1, err1 := Get(src, "Int") 2047 | assertEqual(t, 10, value1) 2048 | assertError(t, err1) 2049 | 2050 | // scenario 2 string 2051 | value2, err2 := Get(src, "String") 2052 | assertEqual(t, "go-model", value2) 2053 | assertError(t, err2) 2054 | 2055 | // scenario 3 field not exists 2056 | _, err := Get(src, "NotExists") 2057 | assertEqual(t, "Field: 'NotExists', does not exists", err.Error()) 2058 | 2059 | // scenario 4 struct is nil 2060 | _, err = Get(nil, "Int") 2061 | assertEqual(t, "Invalid input ", err.Error()) 2062 | } 2063 | 2064 | func TestSetField(t *testing.T) { 2065 | type SampleStruct struct { 2066 | Int int 2067 | String string 2068 | } 2069 | 2070 | src := SampleStruct{ 2071 | Int: 10, 2072 | String: "go-model", 2073 | } 2074 | 2075 | // scenario 1 direct value int 2076 | err := Set(&src, "Int", 20) 2077 | assertError(t, err) 2078 | 2079 | value1, err1 := Get(src, "Int") 2080 | assertEqual(t, 20, value1) 2081 | assertError(t, err1) 2082 | 2083 | // scenario 2 direct value string 2084 | err = Set(&src, "String", "go-model set") 2085 | assertError(t, err) 2086 | 2087 | value2, err2 := Get(src, "String") 2088 | assertEqual(t, "go-model set", value2) 2089 | assertError(t, err2) 2090 | 2091 | // scenario 3 value is pointer 2092 | newVal := "go-model set ptr" 2093 | err = Set(&src, "String", &newVal) 2094 | assertError(t, err) 2095 | 2096 | value3, err3 := Get(src, "String") 2097 | assertEqual(t, "go-model set ptr", value3) 2098 | assertError(t, err3) 2099 | 2100 | // scenario 4 struct is not pointer 2101 | err = Set(src, "Int", 10) 2102 | assertEqual(t, "Destination struct is not a pointer", err.Error()) 2103 | 2104 | // scenario 5 field not exists 2105 | err = Set(&src, "NotExists", "test value") 2106 | assertEqual(t, "Field: 'NotExists', does not exists", err.Error()) 2107 | 2108 | // scenario 6 struct is nil 2109 | err = Set(nil, "Int", 30) 2110 | assertEqual(t, "Invalid input ", err.Error()) 2111 | 2112 | // scenario 7 different type 2113 | err = Set(&src, "String", 30) 2114 | assertEqual(t, "Field: String, type/kind did not match", err.Error()) 2115 | } 2116 | 2117 | func TestImprovedCopy(t *testing.T) { 2118 | type DomainObject struct { 2119 | Name string 2120 | Address string 2121 | Phone string 2122 | } 2123 | 2124 | type LoginGreeterDTO struct { 2125 | Name string 2126 | } 2127 | 2128 | src := DomainObject{ 2129 | Name: "go-model", 2130 | Address: "123 sample street", 2131 | Phone: "000-000-0000", 2132 | } 2133 | 2134 | dst := LoginGreeterDTO{} 2135 | 2136 | errs := Copy(&dst, src) 2137 | assertEqual(t, 0, len(errs)) 2138 | assertEqual(t, "go-model", dst.Name) 2139 | } 2140 | 2141 | // 2142 | // helper test methods 2143 | // 2144 | 2145 | func assertError(t *testing.T, err error) { 2146 | if err != nil { 2147 | t.Errorf("Error occurred [%v]", err) 2148 | } 2149 | } 2150 | 2151 | func assertEqual(t *testing.T, e, g interface{}) (r bool) { 2152 | r = compare(e, g) 2153 | if !r { 2154 | t.Errorf("Expected [%v], got [%v]", e, g) 2155 | } 2156 | 2157 | return 2158 | } 2159 | 2160 | func compare(e, g interface{}) (r bool) { 2161 | ev := reflect.ValueOf(e) 2162 | gv := reflect.ValueOf(g) 2163 | 2164 | if ev.Kind() != gv.Kind() { 2165 | return 2166 | } 2167 | 2168 | switch ev.Kind() { 2169 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 2170 | r = (ev.Int() == gv.Int()) 2171 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 2172 | r = (ev.Uint() == gv.Uint()) 2173 | case reflect.Float32, reflect.Float64: 2174 | r = (ev.Float() == gv.Float()) 2175 | case reflect.String: 2176 | r = (ev.String() == gv.String()) 2177 | case reflect.Bool: 2178 | r = (ev.Bool() == gv.Bool()) 2179 | case reflect.Slice, reflect.Map: 2180 | r = reflect.DeepEqual(e, g) 2181 | } 2182 | 2183 | return 2184 | } 2185 | 2186 | func logSrcDst(t *testing.T, src, dst interface{}) { 2187 | logIt(t, "Source", src) 2188 | t.Log("") 2189 | logIt(t, "Destination", dst) 2190 | } 2191 | 2192 | func logIt(t *testing.T, str string, v interface{}) { 2193 | t.Logf("%v: %#v", str, v) 2194 | } 2195 | -------------------------------------------------------------------------------- /tag.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jeevanandam M. (https://github.com/jeevatkm). 2 | // go-model source code and usage is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | package model 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | type tag struct { 14 | Name string 15 | Options string 16 | } 17 | 18 | // Tag method returns the exported struct field `Tag` value from the given struct. 19 | // Example: 20 | // 21 | // src := SampleStruct { 22 | // BookCount int `json:"-"` 23 | // BookCode string `json:"-"` 24 | // ArchiveInfo BookArchive `json:"archive_info,omitempty"` 25 | // Region BookLocale `json:"region,omitempty"` 26 | // } 27 | // 28 | // tag, _ := model.Tag(src, "ArchiveInfo") 29 | // fmt.Println("Tag Value:", tag.Get("json")) 30 | // 31 | // // Output: 32 | // Tag Value: archive_info,omitempty 33 | // 34 | func Tag(s interface{}, name string) (reflect.StructTag, error) { 35 | sv, err := structValue(s) 36 | if err != nil { 37 | return "", err 38 | } 39 | 40 | if fv, ok := sv.Type().FieldByName(name); ok { 41 | return fv.Tag, nil 42 | } 43 | 44 | return "", fmt.Errorf("Field: '%v', does not exists", name) 45 | } 46 | 47 | // Tags method returns the exported struct fields `Tag` value from the given struct. 48 | // Example: 49 | // 50 | // src := SampleStruct { 51 | // BookCount int `json:"-"` 52 | // BookCode string `json:"-"` 53 | // ArchiveInfo BookArchive `json:"archive_info,omitempty"` 54 | // Region BookLocale `json:"region,omitempty"` 55 | // } 56 | // 57 | // tags, _ := model.Tags(src) 58 | // fmt.Println("Tags:", tags) 59 | // 60 | func Tags(s interface{}) (map[string]reflect.StructTag, error) { 61 | sv, err := structValue(s) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | tags := map[string]reflect.StructTag{} 67 | 68 | fields := modelFields(sv) 69 | for _, f := range fields { 70 | tags[f.Name] = f.Tag 71 | } 72 | 73 | return tags, nil 74 | } 75 | 76 | func newTag(modelTag string) *tag { 77 | t := tag{} 78 | values := strings.Split(modelTag, ",") 79 | 80 | t.Name = values[0] 81 | t.Options = strings.Join(values[1:], ",") 82 | 83 | return &t 84 | } 85 | 86 | func (t *tag) isOmitField() bool { 87 | return t.Name == OmitField 88 | } 89 | 90 | func (t *tag) isOmitEmpty() bool { 91 | return t.isExists(OmitEmpty) 92 | } 93 | 94 | func (t *tag) isNoTraverse() bool { 95 | return t.isExists(NoTraverse) 96 | } 97 | 98 | func (t *tag) isExists(opt string) bool { 99 | return strings.Contains(t.Options, opt) 100 | } 101 | 102 | func isStringEmpty(str string) bool { 103 | return (len(strings.TrimSpace(str)) == 0) 104 | } 105 | -------------------------------------------------------------------------------- /tag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jeevanandam M. (https://github.com/jeevatkm). 2 | // go-model source code and usage is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | package model 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestTag(t *testing.T) { 13 | type SampleInfo struct { 14 | MapIntInt map[int]int 15 | MapStringInt map[string]int `model:"stringInt"` 16 | MapStringString map[string]string 17 | } 18 | 19 | type SampleStruct struct { 20 | Name string `json:"name,omitempty"` 21 | Year int `json:"year"` 22 | Level2 float32 `json:"level2"` 23 | Struct SampleInfo `json:"struct"` 24 | StructPtr *SampleInfo `json:"struct_ptr"` 25 | Level1StructNoTraverse SampleInfo `model:",notraverse"` 26 | CreatedTime *time.Time `json:"created_time,omitempty"` 27 | } 28 | 29 | s := SampleStruct{} 30 | 31 | tag1, err1 := Tag(s, "StructPtr") 32 | assertError(t, err1) 33 | assertEqual(t, "struct_ptr", tag1.Get("json")) 34 | 35 | tag2, err2 := Tag(s, "CreatedTime") 36 | assertError(t, err2) 37 | assertEqual(t, "created_time,omitempty", tag2.Get("json")) 38 | 39 | tag3, err3 := Tag(s, "Level1StructNoTraverse") 40 | assertError(t, err3) 41 | assertEqual(t, "", tag3.Get("json")) 42 | 43 | _, err4 := Tag(nil, "Level1StructNoTraverse") 44 | assertEqual(t, "Invalid input ", err4.Error()) 45 | 46 | _, err5 := Tag(s, "NotExists") 47 | assertEqual(t, "Field: 'NotExists', does not exists", err5.Error()) 48 | } 49 | 50 | func TestTags(t *testing.T) { 51 | type SampleInfo struct { 52 | MapIntInt map[int]int 53 | MapStringInt map[string]int `model:"stringInt"` 54 | MapStringString map[string]string 55 | } 56 | 57 | type SampleStruct struct { 58 | Name string `json:"name,omitempty"` 59 | Year int `json:"year"` 60 | Level2 float32 `json:"level2"` 61 | Struct SampleInfo `json:"struct"` 62 | StructPtr *SampleInfo `json:"struct_ptr"` 63 | Level1StructNoTraverse SampleInfo `model:",notraverse"` 64 | CreatedTime *time.Time `json:"created_time,omitempty"` 65 | } 66 | 67 | s := SampleStruct{} 68 | 69 | tags, err1 := Tags(s) 70 | assertError(t, err1) 71 | assertEqual(t, "struct_ptr", tags["StructPtr"].Get("json")) 72 | assertEqual(t, "created_time,omitempty", tags["CreatedTime"].Get("json")) 73 | 74 | _, err2 := Tags(nil) 75 | assertEqual(t, "Invalid input ", err2.Error()) 76 | } 77 | 78 | func TestNewTag(t *testing.T) { 79 | tag := newTag("fieldName,omitempty,notraverse") 80 | 81 | logIt(t, "Model Tag", tag) 82 | 83 | assertEqual(t, "fieldName", tag.Name) 84 | assertEqual(t, "omitempty,notraverse", tag.Options) 85 | } 86 | 87 | func TestNewTagWithoutName(t *testing.T) { 88 | tag := newTag(",omitempty,notraverse") 89 | 90 | logIt(t, "Model Tag", tag) 91 | 92 | assertEqual(t, "", tag.Name) 93 | assertEqual(t, "omitempty,notraverse", tag.Options) 94 | } 95 | 96 | func TestNewTagNoValues(t *testing.T) { 97 | tag := newTag("") 98 | 99 | logIt(t, "Model Tag", tag) 100 | 101 | assertEqual(t, "", tag.Name) 102 | assertEqual(t, "", tag.Options) 103 | } 104 | 105 | func TestNewTagEmptyValues(t *testing.T) { 106 | tag := newTag(",") 107 | 108 | logIt(t, "Model Tag", tag) 109 | 110 | assertEqual(t, "", tag.Name) 111 | assertEqual(t, "", tag.Options) 112 | } 113 | 114 | func TestNewTagSkipField(t *testing.T) { 115 | tag := newTag("-") 116 | 117 | logIt(t, "Model Tag", tag) 118 | 119 | assertEqual(t, "-", tag.Name) 120 | assertEqual(t, "", tag.Options) 121 | assertEqual(t, true, tag.isOmitField()) 122 | } 123 | 124 | func TestIsOmitEmpty(t *testing.T) { 125 | tag1 := newTag("fieldName,omitempty,notraverse") 126 | logIt(t, "Model Tag", tag1) 127 | assertEqual(t, true, tag1.isOmitEmpty()) 128 | 129 | tag2 := newTag(",omitempty") 130 | logIt(t, "Model Tag", tag2) 131 | assertEqual(t, true, tag2.isOmitEmpty()) 132 | 133 | tag3 := newTag(",omitempty,notraverse") 134 | logIt(t, "Model Tag", tag3) 135 | assertEqual(t, true, tag3.isOmitEmpty()) 136 | 137 | tag4 := newTag(",notraverse") 138 | logIt(t, "Model Tag", tag4) 139 | assertEqual(t, false, tag4.isOmitEmpty()) 140 | 141 | tag5 := newTag("fieldName") 142 | logIt(t, "Model Tag", tag5) 143 | assertEqual(t, false, tag5.isOmitEmpty()) 144 | } 145 | 146 | func TestIsNoTraverse(t *testing.T) { 147 | tag1 := newTag("fieldName,omitempty,notraverse") 148 | logIt(t, "Model Tag", tag1) 149 | assertEqual(t, true, tag1.isNoTraverse()) 150 | 151 | tag2 := newTag(",notraverse") 152 | logIt(t, "Model Tag", tag2) 153 | assertEqual(t, true, tag2.isNoTraverse()) 154 | 155 | tag3 := newTag(",omitempty,notraverse") 156 | logIt(t, "Model Tag", tag3) 157 | assertEqual(t, true, tag3.isNoTraverse()) 158 | 159 | tag4 := newTag(",omitempty") 160 | logIt(t, "Model Tag", tag4) 161 | assertEqual(t, false, tag4.isNoTraverse()) 162 | 163 | tag5 := newTag("fieldName") 164 | logIt(t, "Model Tag", tag5) 165 | assertEqual(t, false, tag5.isNoTraverse()) 166 | } 167 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jeevanandam M. (https://github.com/jeevatkm). 2 | // go-model source code and usage is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | package model 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | var errFieldNotExists = errors.New("Field does not exists") 14 | 15 | func isFieldZero(f reflect.Value) bool { 16 | // zero value of the given field 17 | // For example: reflect.Zero(reflect.TypeOf(42)) returns a Value with Kind Int and value 0 18 | zero := reflect.Zero(f.Type()).Interface() 19 | 20 | return reflect.DeepEqual(f.Interface(), zero) 21 | } 22 | 23 | func isNoTraverseType(v reflect.Value) bool { 24 | if !isStruct(v) { 25 | return false 26 | } 27 | 28 | t := deepTypeOf(v) 29 | 30 | _, found := noTraverseTypeList[t] 31 | return found 32 | } 33 | 34 | func validateCopyField(f reflect.StructField, sfv, dfv reflect.Value) error { 35 | // check dst field is exists, if not valid move on 36 | if !dfv.IsValid() { 37 | return errFieldNotExists 38 | //return fmt.Errorf("Field does not exists in dst", f.Name) 39 | } 40 | 41 | if conversionExists(sfv.Type(), dfv.Type()) { 42 | return nil 43 | } 44 | 45 | // check kind of src and dst, if doesn't match move on 46 | if (sfv.Kind() != dfv.Kind()) && !isInterface(dfv) { 47 | return fmt.Errorf("Field: '%v', src [%v] & dst [%v] kind didn't match", 48 | f.Name, 49 | sfv.Kind(), 50 | dfv.Kind(), 51 | ) 52 | } 53 | 54 | // check type of src and dst, if doesn't match move on 55 | sfvt := deepTypeOf(sfv) 56 | dfvt := deepTypeOf(dfv) 57 | 58 | if (sfvt.Kind() == reflect.Slice || sfvt.Kind() == reflect.Map) && sfvt.Kind() == dfvt.Kind() && conversionExists(sfvt.Elem(), dfvt.Elem()) { 59 | return nil 60 | } 61 | 62 | if (sfvt != dfvt) && !isInterface(dfv) { 63 | return fmt.Errorf("Field: '%v', src [%v] & dst [%v] type didn't match", 64 | f.Name, 65 | sfvt, 66 | dfvt, 67 | ) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func modelFields(v reflect.Value) []reflect.StructField { 74 | v = indirect(v) 75 | t := v.Type() 76 | 77 | var fs []reflect.StructField 78 | 79 | for i := 0; i < t.NumField(); i++ { 80 | f := t.Field(i) 81 | 82 | // Only exported fields of a struct can be accessed. 83 | // So, non-exported fields will be ignored 84 | if f.PkgPath == "" { 85 | fs = append(fs, f) 86 | } 87 | } 88 | 89 | return fs 90 | } 91 | 92 | func structValue(s interface{}) (reflect.Value, error) { 93 | if s == nil { 94 | return reflect.Value{}, errors.New("Invalid input ") 95 | } 96 | 97 | sv := indirect(valueOf(s)) 98 | 99 | if !isStruct(sv) { 100 | return reflect.Value{}, errors.New("Input is not a struct") 101 | } 102 | 103 | return sv, nil 104 | } 105 | 106 | func getField(sv reflect.Value, name string) (reflect.Value, error) { 107 | field := sv.FieldByName(name) 108 | if !field.IsValid() { 109 | return reflect.Value{}, fmt.Errorf("Field: '%v', does not exists", name) 110 | } 111 | 112 | return field, nil 113 | } 114 | 115 | func zeroOf(f reflect.Value) reflect.Value { 116 | 117 | // get zero value for type 118 | ftz := reflect.Zero(f.Type()) 119 | 120 | if f.Kind() == reflect.Ptr { 121 | return ftz 122 | } 123 | 124 | // if not a pointer then get zero value for interface 125 | return indirect(valueOf(ftz.Interface())) 126 | } 127 | 128 | func deepTypeOf(v reflect.Value) reflect.Type { 129 | if isInterface(v) { 130 | 131 | // check zero or not 132 | if !isFieldZero(v) { 133 | v = valueOf(v.Interface()) 134 | } 135 | 136 | } 137 | 138 | return v.Type() 139 | } 140 | 141 | func valueOf(i interface{}) reflect.Value { 142 | return reflect.ValueOf(i) 143 | } 144 | 145 | func indirect(v reflect.Value) reflect.Value { 146 | return reflect.Indirect(v) 147 | } 148 | 149 | func isPtr(v reflect.Value) bool { 150 | return v.Kind() == reflect.Ptr 151 | } 152 | 153 | func isStruct(v reflect.Value) bool { 154 | if isInterface(v) { 155 | v = valueOf(v.Interface()) 156 | } 157 | 158 | pv := indirect(v) 159 | 160 | // struct is not yet initialized 161 | if pv.Kind() == reflect.Invalid { 162 | return false 163 | } 164 | 165 | return pv.Kind() == reflect.Struct 166 | } 167 | 168 | func isInterface(v reflect.Value) bool { 169 | return v.Kind() == reflect.Interface 170 | } 171 | 172 | func extractType(x interface{}) reflect.Type { 173 | return reflect.TypeOf(x).Elem() 174 | } 175 | 176 | func conversionExists(srcType reflect.Type, destType reflect.Type) bool { 177 | if _, ok := converterMap[srcType]; !ok { 178 | return false 179 | } 180 | if _, ok := converterMap[srcType][destType]; !ok { 181 | return false 182 | } 183 | return true 184 | } 185 | --------------------------------------------------------------------------------