Compare commits

..

10 Commits

Author SHA1 Message Date
Huangzj 31768a5cd9 feat(Go-StudyExample):代码删除、提交csapp pdf 2021-03-30 09:29:24 +08:00
Huangzj 7413126b5a feat(Go-StudyExample):更新readme 2021-03-11 15:09:04 +08:00
Huangzj 2c323d997e feat(Go-StudyExample):2021/02/23:新增linq包使用示例 2021-02-23 10:58:42 +08:00
Huangzj cf02f0bf10 feat(Go-StudyExample):2021/02/09:新增merge源码阅读和单测代码 2021-02-09 16:00:31 +08:00
Huangzj 05f55b0eac feat(Go-StudyExample):2021/02/09:新增merge源码阅读和单测代码 2021-02-09 15:59:37 +08:00
Huangzj b2315db276 feat(Go-StudyExample):
2021/01/27:新增mapstructure源码阅读和单测代码
2021-01-27 14:01:19 +08:00
Huangzj b5a1909eb0 feat(Go-StudyExample):
2021/01/07:新增copier源码阅读和单测代码
2021-01-07 09:23:37 +08:00
Huangzj 9c93a35685 feat(Go-StudyExample):
2020/12/31:添加container包使用示例和源码分析

                       2020/12/31:添加validator.v8源码解析和使用示例
2020-12-31 16:48:30 +08:00
Huangzj 6d079549f1 feat(Go-StudyExample):2020/12/31:新增【Go每日一库】go-cmp结构体比较工具 2020-12-31 14:48:58 +08:00
Huangzj 20c2c7fab0 feat(Go-StudyExample):2020/12/29:新增【Go每日一库】emial发送邮件 2020-12-29 11:55:19 +08:00
111 changed files with 12273 additions and 147 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

+9
View File
@@ -0,0 +1,9 @@
validate.V8 源码分析文章:https://blog.csdn.net/qq_34326321/article/details/111030128
container源码分析文章:https://blog.csdn.net/qq_34326321/article/details/110804660
copier源码分析文章:https://blog.csdn.net/qq_34326321/article/details/112177087
mapstructure源码分析文章:https://blog.csdn.net/qq_34326321/article/details/113182689
merge源码分析文章:https://blog.csdn.net/qq_34326321/article/details/113182689
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto, William Kennedy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,219 @@
# mapstructure
mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.
This library is most useful when decoding values from some data stream (JSON,
Gob, etc.) where you don't _quite_ know the structure of the underlying data
until you read a part of it. You can therefore read a `map[string]interface{}`
and use this library to decode it into the proper underlying native Go
structure.
## Installation
Standard `go get`:
```
$ go get github.com/goinggo/mapstructure
```
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
The `Decode`, `DecodePath` and `DecodeSlicePath` functions have examples associated with it there.
## But Why?!
Go offers fantastic standard libraries for decoding formats such as JSON.
The standard method is to have a struct pre-created, and populate that struct
from the bytes of the encoded format. This is great, but the problem is if
you have configuration or an encoding that changes slightly depending on
specific fields. For example, consider this JSON:
```json
{
"type": "person",
"name": "Mitchell"
}
```
Perhaps we can't populate a specific structure without first reading
the "type" field from the JSON. We could always do two passes over the
decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a `map[string]interface{}`
structure, read the "type" key, then use something like this library
to decode it into the proper structure.
## DecodePath
Sometimes you have a large and complex JSON document where you only need to decode
a small part.
```
{
"userContext": {
"conversationCredentials": {
"sessionToken": "06142010_1:75bf6a413327dd71ebe8f3f30c5a4210a9b11e93c028d6e11abfca7ff"
},
"valid": true,
"isPasswordExpired": false,
"cobrandId": 10000004,
"channelId": -1,
"locale": "en_US",
"tncVersion": 2,
"applicationId": "17CBE222A42161A3FF450E47CF4C1A00",
"cobrandConversationCredentials": {
"sessionToken": "06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b"
},
"preferenceInfo": {
"currencyCode": "USD",
"timeZone": "PST",
"dateFormat": "MM/dd/yyyy",
"currencyNotationType": {
"currencyNotationType": "SYMBOL"
},
"numberFormat": {
"decimalSeparator": ".",
"groupingSeparator": ",",
"groupPattern": "###,##0.##"
}
}
},
"lastLoginTime": 1375686841,
"loginCount": 299,
"passwordRecovered": false,
"emailAddress": "johndoe@email.com",
"loginName": "sptest1",
"userId": 10483860,
"userType":
{
"userTypeId": 1,
"userTypeName": "normal_user"
}
}
```
It is nice to be able to define and pull the documents and fields you need without
having to map the entire JSON structure.
```
type UserType struct {
UserTypeId int
UserTypeName string
}
type NumberFormat struct {
DecimalSeparator string `jpath:"userContext.preferenceInfo.numberFormat.decimalSeparator"`
GroupingSeparator string `jpath:"userContext.preferenceInfo.numberFormat.groupingSeparator"`
GroupPattern string `jpath:"userContext.preferenceInfo.numberFormat.groupPattern"`
}
type User struct {
Session string `jpath:"userContext.cobrandConversationCredentials.sessionToken"`
CobrandId int `jpath:"userContext.cobrandId"`
UserType UserType `jpath:"userType"`
LoginName string `jpath:"loginName"`
NumberFormat // This can also be a pointer to the struct (*NumberFormat)
}
docScript := []byte(document)
var docMap map[string]interface{}
json.Unmarshal(docScript, &docMap)
var user User
mapstructure.DecodePath(docMap, &user)
```
## DecodeSlicePath
Sometimes you have a slice of documents that you need to decode into a slice of structures
```
[
{"name":"bill"},
{"name":"lisa"}
]
```
Just Unmarshal your document into a slice of maps and decode the slice
```
type NameDoc struct {
Name string `jpath:"name"`
}
sliceScript := []byte(document)
var sliceMap []map[string]interface{}
json.Unmarshal(sliceScript, &sliceMap)
var myslice []NameDoc
err := DecodeSlicePath(sliceMap, &myslice)
var myslice []*NameDoc
err := DecodeSlicePath(sliceMap, &myslice)
```
## Decode Structs With Embedded Slices
Sometimes you have a document with arrays
```
{
"cobrandId": 10010352,
"channelId": -1,
"locale": "en_US",
"tncVersion": 2,
"people": [
{
"name": "jack",
"age": {
"birth":10,
"year":2000,
"animals": [
{
"barks":"yes",
"tail":"yes"
},
{
"barks":"no",
"tail":"yes"
}
]
}
},
{
"name": "jill",
"age": {
"birth":11,
"year":2001
}
}
]
}
```
You can decode within those arrays
```
type Animal struct {
Barks string `jpath:"barks"`
}
type People struct {
Age int `jpath:"age.birth"` // jpath is relative to the array
Animals []Animal `jpath:"age.animals"`
}
type Items struct {
Categories []string `jpath:"categories"`
Peoples []People `jpath:"people"` // Specify the location of the array
}
docScript := []byte(document)
var docMap map[string]interface{}
json.Unmarshal(docScript, &docMap)
var items Items
DecodePath(docMap, &items)
```
@@ -0,0 +1,32 @@
package mapstructure
import (
"fmt"
"strings"
)
// Error implements the error interface and can represents multiple
// errors that occur in the course of a single decode.
type Error struct {
Errors []string
}
func (e *Error) Error() string {
points := make([]string, len(e.Errors))
for i, err := range e.Errors {
points[i] = fmt.Sprintf("* %s", err)
}
return fmt.Sprintf(
"%d error(s) decoding:\n\n%s",
len(e.Errors), strings.Join(points, "\n"))
}
func appendErrors(errors []string, err error) []string {
switch e := err.(type) {
case *Error:
return append(errors, e.Errors...)
default:
return append(errors, e.Error())
}
}
@@ -0,0 +1,990 @@
// The mapstructure package exposes functionality to convert an
// abitrary map[string]interface{} into a native Go structure.
//
// The Go structure can be arbitrarily complex, containing slices,
// other structs, etc. and the decoder will properly decode nested
// maps and so on into the proper structures in the native Go struct.
// See the examples to see what the decoder is capable of.
package mapstructure
import (
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
)
//read note 钩子,起作用的地方是在Decode之前,这边主要的作用可以说比如 在转换之前把字符串转成Time类型等等,具体可以查看:https://godoc.org/github.com/mitchellh/mapstructure#DecodeHookFunc
//read note https://github.com/mitchellh/mapstructure/blob/master/decode_hooks.go 这边提供了一些默认的hook方法,可以看一下
type DecodeHookFunc func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
// DecoderConfig is the configuration that is used to create a new decoder
// and allows customization of various aspects of decoding.
type DecoderConfig struct {
// DecodeHook, if set, will be called before any decoding and any
// type conversion (if WeaklyTypedInput is on). This lets you modify
// the values before they're set down onto the resulting struct.
//
// If an error is returned, the entire decode will fail with that
// error.
//read note 在Decode之前会调用该Hook,这边的注释说明,hook可以让你在Decode之前修改你的结构体现有值
DecodeHook DecodeHookFunc
// If ErrorUnused is true, then it is an error for there to exist
// keys in the original map that were unused in the decoding process
// (extra keys).
//read note 如果这个字段设置为true的话,在map当中只要有key不能转换成结构体的字段就会进行报错,感觉正常情况下是不会设置成true...
ErrorUnused bool
// If WeaklyTypedInput is true, the decoder will make the following
// "weak" conversions:
//
// - bools to string (true = "1", false = "0")
// - numbers to string (base 10)
// - bools to int/uint (true = 1, false = 0)
// - strings to int/uint (base implied by prefix)
// - int to bool (true if value != 0)
// - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
// FALSE, false, False. Anything else is an error)
// - empty array = empty map and vice versa
//
//read note 弱类型转换,这个字段如果设置为true,则从某种类型 to 某种类型 会按照上面的转换规则来进行转换
WeaklyTypedInput bool
// Metadata is the struct that will contain extra metadata about
// the decoding. If this is nil, then no metadata will be tracked.
//read note 没搞明白这个是干啥的
Metadata *Metadata
// Result is a pointer to the struct that will contain the decoded
// value.
//read note 转换的结果
Result interface{}
// The tag name that mapstructure reads for field names. This
// defaults to "mapstructure"
//read note tag名称,默认是 mapstructure
TagName string
}
// A Decoder takes a raw interface value and turns it into structured
// data, keeping track of rich error information along the way in case
// anything goes wrong. Unlike the basic top-level Decode method, you can
// more finely control how the Decoder behaves using the DecoderConfig
// structure. The top-level Decode method is just a convenience that sets
// up the most basic Decoder.
type Decoder struct {
//read note 转换工具的配置
config *DecoderConfig
}
// Metadata contains information about decoding a structure that
// is tedious or difficult to get otherwise.
type Metadata struct {
// Keys are the keys of the structure which were successfully decoded
Keys []string
// Unused is a slice of keys that were found in the raw value but
// weren't decoded since there was no matching field in the result interface
Unused []string
}
// Decode takes a map and uses reflection to convert it into the
// given Go native structure. val must be a pointer to a struct.
func Decode(m interface{}, rawVal interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: rawVal,
}
//read note 创建包含配置的Decoder对象
decoder, err := NewDecoder(config)
if err != nil {
return err
}
//read note 进行Decode操作
return decoder.Decode(m)
}
// DecodePath takes a map and uses reflection to convert it into the
// given Go native structure. Tags are used to specify the mapping
// between fields in the map and structure
func DecodePath(m map[string]interface{}, rawVal interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: nil,
}
decoder, err := NewPathDecoder(config)
if err != nil {
return err
}
_, err = decoder.DecodePath(m, rawVal)
return err
}
// DecodeSlicePath decodes a slice of maps against a slice of structures that
// contain specified tags
func DecodeSlicePath(ms []map[string]interface{}, rawSlice interface{}) error {
reflectRawSlice := reflect.TypeOf(rawSlice)
rawKind := reflectRawSlice.Kind()
rawElement := reflectRawSlice.Elem()
//read note 校验是否为切片
if (rawKind == reflect.Ptr && rawElement.Kind() != reflect.Slice) ||
(rawKind != reflect.Ptr && rawKind != reflect.Slice) {
return fmt.Errorf("Incompatible Value, Looking For Slice : %v : %v", rawKind, rawElement.Kind())
}
config := &DecoderConfig{
Metadata: nil,
Result: nil,
}
decoder, err := NewPathDecoder(config)
if err != nil {
return err
}
// Create a slice large enough to decode all the values
//read note 构造一个新的数组
valSlice := reflect.MakeSlice(rawElement, len(ms), len(ms))
// Iterate over the maps and decode each one
//read note 循环被转换的map数组
for index, m := range ms {
sliceElementType := rawElement.Elem()
if sliceElementType.Kind() != reflect.Ptr {
// A slice of objects
//read note 如果转换的结果是结构体类型,处理转换
obj := reflect.New(rawElement.Elem())
decoder.DecodePath(m, reflect.Indirect(obj))
indexVal := valSlice.Index(index)
indexVal.Set(reflect.Indirect(obj))
} else {
// A slice of pointers
//read note 如果转换的结果是指针类型,处理转换
obj := reflect.New(rawElement.Elem().Elem())
decoder.DecodePath(m, reflect.Indirect(obj))
indexVal := valSlice.Index(index)
indexVal.Set(obj)
}
}
// Set the new slice
//read note 设置转换后的数组数据
reflect.ValueOf(rawSlice).Elem().Set(valSlice)
return nil
}
// NewDecoder returns a new decoder for the given configuration. Once
// a decoder has been returned, the same configuration must not be used
// again.
func NewDecoder(config *DecoderConfig) (*Decoder, error) {
val := reflect.ValueOf(config.Result)
if val.Kind() != reflect.Ptr {
return nil, errors.New("result must be a pointer")
}
val = val.Elem()
if !val.CanAddr() {
return nil, errors.New("result must be addressable (a pointer)")
}
if config.Metadata != nil {
if config.Metadata.Keys == nil {
config.Metadata.Keys = make([]string, 0)
}
if config.Metadata.Unused == nil {
config.Metadata.Unused = make([]string, 0)
}
}
if config.TagName == "" {
config.TagName = "mapstructure"
}
result := &Decoder{
config: config,
}
return result, nil
}
// NewPathDecoder returns a new decoder for the given configuration.
// This is used to decode path specific structures
func NewPathDecoder(config *DecoderConfig) (*Decoder, error) {
if config.Metadata != nil {
if config.Metadata.Keys == nil {
config.Metadata.Keys = make([]string, 0)
}
if config.Metadata.Unused == nil {
config.Metadata.Unused = make([]string, 0)
}
}
if config.TagName == "" {
config.TagName = "mapstructure"
}
result := &Decoder{
config: config,
}
return result, nil
}
// Decode decodes the given raw interface to the target pointer specified
// by the configuration.
func (d *Decoder) Decode(raw interface{}) error {
//read note config中的Result配置的是指针,如果不是指针是没办法完成配置的.
// 所有这边的调用是通过 reflect.ValueOf().Elem()
return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem())
}
// DecodePath decodes the raw interface against the map based on the
// specified tags
func (d *Decoder) DecodePath(m map[string]interface{}, rawVal interface{}) (bool, error) {
decoded := false
var val reflect.Value
reflectRawValue := reflect.ValueOf(rawVal)
kind := reflectRawValue.Kind()
// Looking for structs and pointers to structs
//read note 对传入的 转换结果的数据类型进行判断,转换成对应的结构体类型
switch kind {
case reflect.Ptr:
val = reflectRawValue.Elem()
if val.Kind() != reflect.Struct {
return decoded, fmt.Errorf("Incompatible Type : %v : Looking For Struct", kind)
}
case reflect.Struct:
var ok bool
val, ok = rawVal.(reflect.Value)
if ok == false {
return decoded, fmt.Errorf("Incompatible Type : %v : Looking For reflect.Value", kind)
}
default:
return decoded, fmt.Errorf("Incompatible Type : %v", kind)
}
// Iterate over the fields in the struct
//read note 循环结构体的所有Field
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
tag := typeField.Tag
//read note 这边的tag是通过 jpath来识别的
tagValue := tag.Get("jpath")
// Is this a field without a tag
//read note 没有tag的单独处理
if tagValue == "" {
//read note 如果是结构体,调用DecodePath处理
if valueField.Kind() == reflect.Struct {
// We have a struct that may have indivdual tags. Process separately
d.DecodePath(m, valueField)
continue
} else if valueField.Kind() == reflect.Ptr && reflect.TypeOf(valueField).Kind() == reflect.Struct {
//read note 指针的处理,转换成结构体也是类型的DecodePath的处理
// We have a pointer to a struct
if valueField.IsNil() {
// Create the object since it doesn't exist
valueField.Set(reflect.New(valueField.Type().Elem()))
decoded, _ = d.DecodePath(m, valueField.Elem())
if decoded == false {
// If nothing was decoded for this object return the pointer to nil
valueField.Set(reflect.NewAt(valueField.Type().Elem(), nil))
}
continue
}
d.DecodePath(m, valueField.Elem())
continue
}
}
// Use mapstructure to populate the fields
//read note jpath后面支持的别名可以是递进到更进去的层次,通过.标识,比如说 Age.Birth. 表示 Age:{Birth:100,。。。}
keys := strings.Split(tagValue, ".")
//read note 通过keys去查找数据
data := d.findData(m, keys)
//read note 如果在map中找到的数据不为nil。需要开始设值操作
if data != nil {
//read note 如果当前Filed的类型是Slice
if valueField.Kind() == reflect.Slice {
// Ignore a slice of maps - This sucks but not sure how to check
//read note 如果Field是map数组,这边没办法处理,只能通过下面的decode方法进行具体的处理
if strings.Contains(valueField.Type().String(), "map[") {
goto normal_decode
}
// We have a slice
mapSlice := data.([]interface{})
if len(mapSlice) > 0 {
// Test if this is a slice of more maps
//read note 转换成切片的数据如果不是 map的数组,则跳转到下面通过 decode处理.因为这边是切片对切片,map对结构体
_, ok := mapSlice[0].(map[string]interface{})
if ok == false {
goto normal_decode
}
// Extract the maps out and run it through DecodeSlicePath
//read note 组转map数组
ms := make([]map[string]interface{}, len(mapSlice))
for index, m2 := range mapSlice {
ms[index] = m2.(map[string]interface{})
}
//调用DecodeSlicePath,转换 map数组 -》 结构体数组
DecodeSlicePath(ms, valueField.Addr().Interface())
continue
}
}
normal_decode:
//read note 通过decode处理,这边同样应该支持 mapstructure的tag标签
decoded = true
err := d.decode("", data, valueField)
if err != nil {
return false, err
}
}
}
return decoded, nil
}
// Decodes an unknown data type into a specific reflection value.
//read note 入参说明:
// name 字段名称
// data 被转换的数据
// val 转换最终的结构体
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error {
//read note 结构体为空直接返回
if data == nil {
// If the data is nil, then we don't set anything.
return nil
}
dataVal := reflect.ValueOf(data)
//read note 非IsValid的对象,设置零值
if !dataVal.IsValid() {
// If the data value is invalid, then we just set the value
// to be the zero value.
val.Set(reflect.Zero(val.Type()))
return nil
}
//read note hook的调用,调用的结果data会用在下面的判断中
if d.config.DecodeHook != nil {
// We have a DecodeHook, so let's pre-process the data.
var err error
data, err = d.config.DecodeHook(d.getKind(dataVal), d.getKind(val), data)
if err != nil {
return err
}
}
var err error
//read note 获得 转换最终的结构体 的类型
dataKind := d.getKind(val)
//read note 依据类型进行不同的转换处理
switch dataKind {
case reflect.Bool:
err = d.decodeBool(name, data, val)
case reflect.Interface:
err = d.decodeBasic(name, data, val)
case reflect.String:
err = d.decodeString(name, data, val)
case reflect.Int:
err = d.decodeInt(name, data, val)
case reflect.Uint:
err = d.decodeUint(name, data, val) //read note 处理与decodeInt类似
case reflect.Float32:
err = d.decodeFloat(name, data, val) //read note 处理与decodeInt类似
case reflect.Struct:
err = d.decodeStruct(name, data, val)
case reflect.Map:
err = d.decodeMap(name, data, val)
case reflect.Slice:
err = d.decodeSlice(name, data, val)
default:
// If we reached this point then we weren't able to decode it
//read note 没办法处理指针类型,比如本来传入的就是指针的指针,这边是拒绝处理的
return fmt.Errorf("%s: unsupported type: %s", name, dataKind)
}
// If we reached here, then we successfully decoded SOMETHING, so
// mark the key as used if we're tracking metadata.
//read note 对处理玩的Metadata进行组装,但是这边看好像一定是不会处理的,因为传进来的name一直是空字符串
if d.config.Metadata != nil && name != "" {
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
}
return err
}
// findData locates the data by walking the keys down the map
func (d *Decoder) findData(m map[string]interface{}, keys []string) interface{} {
//read note 这是一个递归方法,所以递归的结束就是keys最终变成一个值,查找该值,能找到则返回,否则返回nil
if len(keys) == 1 {
if value, ok := m[keys[0]]; ok == true {
return value
}
return nil
}
//read note 继续进行递归,下一次递归就是往下一个key开始,可以理解就是map一层一层的下去查找对应的key.
if value, ok := m[keys[0]]; ok == true {
if m, ok := value.(map[string]interface{}); ok == true {
return d.findData(m, keys[1:])
}
}
return nil
}
func (d *Decoder) getKind(val reflect.Value) reflect.Kind {
kind := val.Kind()
switch {
case kind >= reflect.Int && kind <= reflect.Int64:
return reflect.Int
case kind >= reflect.Uint && kind <= reflect.Uint64:
return reflect.Uint
case kind >= reflect.Float32 && kind <= reflect.Float64:
return reflect.Float32
default:
return kind
}
}
// This decodes a basic type (bool, int, string, etc.) and sets the
// value to "data" of that type.
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
//read note 如果被转换的结果是interface,则只判断是否 AssignableTo
dataVal := reflect.ValueOf(data)
dataValType := dataVal.Type()
if !dataValType.AssignableTo(val.Type()) {
return fmt.Errorf(
"'%s' expected type '%s', got '%s'",
name, val.Type(), dataValType)
}
val.Set(dataVal)
return nil
}
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := d.getKind(dataVal)
//read note string这边的转换规则如下:
// 1、如果被转换的就是string,则直接转换成string
// 开启了弱类型转换标识的
// 2、bool转换从1或0
// 3、数值类型按照十进制转换成字符串
// 4、float类型,按照64位转换
switch {
case dataKind == reflect.String:
val.SetString(dataVal.String())
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetString("1")
} else {
val.SetString("0")
}
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := d.getKind(dataVal)
//read note int这边的转换大致如下
// 1、数值类型,直接转换成int,不考虑精度
// 如果开启了弱类型转换标识
// 2、bool类型按照0,1转换
// 3、字符串通过 ParseInt转换
// 4、否则错误
switch {
case dataKind == reflect.Int:
val.SetInt(dataVal.Int())
case dataKind == reflect.Uint:
val.SetInt(int64(dataVal.Uint()))
case dataKind == reflect.Float32:
val.SetInt(int64(dataVal.Float()))
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetInt(1)
} else {
val.SetInt(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
if err == nil {
val.SetInt(i)
} else {
return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := d.getKind(dataVal)
switch {
case dataKind == reflect.Int:
val.SetUint(uint64(dataVal.Int()))
case dataKind == reflect.Uint:
val.SetUint(dataVal.Uint())
case dataKind == reflect.Float32:
val.SetUint(uint64(dataVal.Float()))
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetUint(1)
} else {
val.SetUint(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
if err == nil {
val.SetUint(i)
} else {
return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
//read note 入参说明:
// name 字段名称
// data 被转换的数据
// val 转换最终的结构体
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := d.getKind(dataVal)
//read note 这边的转换规则如下:
// 1、bool类型直接转换
// 开启了弱类型转换标识的
// 2、数值类型判断是否为0,0是false
// 3、string类型,先进行bool转换,如果不能转换,空字符表示false,否则错误
// 4、其他类型错误
switch {
case dataKind == reflect.Bool:
val.SetBool(dataVal.Bool())
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
val.SetBool(dataVal.Int() != 0)
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
val.SetBool(dataVal.Uint() != 0)
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
val.SetBool(dataVal.Float() != 0)
case dataKind == reflect.String && d.config.WeaklyTypedInput:
b, err := strconv.ParseBool(dataVal.String())
if err == nil {
val.SetBool(b)
} else if dataVal.String() == "" {
val.SetBool(false)
} else {
return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.ValueOf(data)
dataKind := d.getKind(dataVal)
switch {
case dataKind == reflect.Int:
val.SetFloat(float64(dataVal.Int()))
case dataKind == reflect.Uint:
val.SetFloat(float64(dataVal.Uint()))
case dataKind == reflect.Float32:
val.SetFloat(float64(dataVal.Float()))
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
if dataVal.Bool() {
val.SetFloat(1)
} else {
val.SetFloat(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
if err == nil {
val.SetFloat(f)
} else {
return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
}
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type())
}
return nil
}
//read note 入参说明:
// name 字段名称
// data 被转换的数据
// val 转换最终的结构体
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
// Make a new map to hold our result
//read note 通过反射创建新的map
mapType := reflect.MapOf(valKeyType, valElemType)
valMap := reflect.MakeMap(mapType)
// Check input type
dataVal := reflect.Indirect(reflect.ValueOf(data))
//read note 如果被转换的结构体不是map,这边需要满足
// 1、弱类型标识打开
// 2、空切片或空数组
// 才会返回空map,否则组装错误信息
if dataVal.Kind() != reflect.Map {
// Accept empty array/slice instead of an empty map in weakly typed mode
if d.config.WeaklyTypedInput &&
(dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) &&
dataVal.Len() == 0 {
val.Set(valMap)
return nil
} else {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
}
}
// Accumulate errors
errors := make([]string, 0)
//read note 遍历被转换结构体的所有key
for _, k := range dataVal.MapKeys() {
fieldName := fmt.Sprintf("%s[%s]", name, k)
// First decode the key into the proper type
//read note 对key进行转换 decode
currentKey := reflect.Indirect(reflect.New(valKeyType))
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
errors = appendErrors(errors, err)
continue
}
// Next decode the data into the proper type
//read note 对value进行转换 decode
v := dataVal.MapIndex(k).Interface()
currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil {
errors = appendErrors(errors, err)
continue
}
//read note 对结果map进行组装
valMap.SetMapIndex(currentKey, currentVal)
}
// Set the built up map to the value
//read note map设值
val.Set(valMap)
// If we had errors, return those
if len(errors) > 0 {
return &Error{errors}
}
return nil
}
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataValKind := dataVal.Kind()
valType := val.Type()
valElemType := valType.Elem()
// Make a new slice to hold our result, same size as the original data.
//read note 构造新的切片
sliceType := reflect.SliceOf(valElemType)
valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
// Check input type
//read note 同样的弱类型转换,空的map直接返回空数组(弱类型标识为true的情况下)
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
// Accept empty map instead of array/slice in weakly typed mode
if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 {
val.Set(valSlice)
return nil
} else {
return fmt.Errorf(
"'%s': source data must be an array or slice, got %s", name, dataValKind)
}
}
// Accumulate any errors
errors := make([]string, 0)
//read note 遍历原来的数组,进行每个元素的转换 decode
for i := 0; i < dataVal.Len(); i++ {
currentData := dataVal.Index(i).Interface()
currentField := valSlice.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i)
if err := d.decode(fieldName, currentData, currentField); err != nil {
errors = appendErrors(errors, err)
}
}
// Finally, set the value to the slice we built up
//read note 设置切片的值
val.Set(valSlice)
// If there were errors, we return those
if len(errors) > 0 {
return &Error{errors}
}
return nil
}
//read note 入参说明:
// name 字段名称
// data 被转换的数据
// val 转换最终的结构体
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataValKind := dataVal.Kind()
//read note 被转换的类型不是map,错误
if dataValKind != reflect.Map {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
}
dataValType := dataVal.Type()
//read note map的key不是string或者Interface类型,错误
if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
return fmt.Errorf("'%s' needs a map with string keys, has '%s' keys", name, dataValType.Key().Kind())
}
//read note 这边组装了两个数组,一个是映射成功的key数组,一个是未映射的key数组
dataValKeys, dataValKeysUnused := d.getUseAndUnUseKeyList(dataVal)
// This slice will keep track of all the structs we'll be decoding. There can be more than one struct if there are embedded structs that are squashed.
//read note 正常情况下只会有一个结构体才对,但是这边的处理,如果结构体中的某一个字段是匿名结构体,则会加入到structs中进行处理
// structs[0]是最外层的结构体,structs[1:]是最外层结构体中的匿名字段代表的结构体
structs := make([]reflect.Value, 1, 5)
structs[0] = val
errors := make([]string, 0)
// Compile the list of all the fields that we're going to be decoding from all the structs.
fields := make(map[*reflect.StructField]reflect.Value) //read note 创建【field->value】的map
//read note 循环structs数组,在循环的过程中遇到 【匿名结构体并且标注了squash的结构体字段会加入到structs数组中】
d.structLoopForFieldList(structs, errors, val, fields)
//read note 循环Filed数组,对每一个map能映射到的字段进行赋值(字段名不区分大小写)
d.fieldsLoopForDecode(fields, errors, dataVal, dataValKeys, dataValKeysUnused, name)
//read note errorUnused标识打开,如果map有未转换的可以,则组装错误信息
d.produceErrorWithUnused(dataValKeysUnused, name, errors)
if len(errors) > 0 {
return &Error{errors}
}
// Add the unused keys to the list of unused keys if we're tracking metadata
//read note 组装metadata.这边还要判断name不为空
d.produceMetadata(dataValKeysUnused, name)
return nil
}
func (d *Decoder) structLoopForFieldList(structs []reflect.Value, errors []string, val reflect.Value, fields map[*reflect.StructField]reflect.Value) {
for len(structs) > 0 {
structVal := structs[0]
structs = structs[1:]
structType := structVal.Type()
//read note 循环结构体的所有Field字段
for i := 0; i < structType.NumField(); i++ {
fieldType := structType.Field(i)
//read note 匿名字段处理
if fieldType.Anonymous {
fieldKind := fieldType.Type.Kind()
//read note 非结构体,错误
if fieldKind != reflect.Struct {
errors = appendErrors(errors,
fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind))
continue
}
// We have an embedded field. We "squash" the fields down
// if specified in the tag.
//read note squash标签只作用在匿名字段上,会对匿名字段结构体的字段进行深入赋值.
squash := false
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
for _, tag := range tagParts[1:] {
if tag == "squash" {
squash = true
break
}
}
//read note 如果是squash标签标识,会加入到structs数组中,在下一次循环开始之前进行循环
if squash {
structs = append(structs, val.FieldByName(fieldType.Name))
continue
}
}
// Normal struct field, store it away
//read note 组装map指向
fields[&fieldType] = structVal.Field(i)
}
}
}
func (d *Decoder) fieldsLoopForDecode(fields map[*reflect.StructField]reflect.Value, errors []string, dataVal reflect.Value, dataValKeys map[reflect.Value]struct{}, dataValKeysUnused map[interface{}]struct{}, name string) {
//read note 循环所有的Field,这边的field数组可能是当前结构体的,也可能是结构体中的匿名字段的field
for fieldType, field := range fields {
fieldName := fieldType.Name
//read note 获取tag标签,进行【,】切割,这边的标识默认是 mapstructure,比如说 mapstructurename.获取的就是name
tagValue := fieldType.Tag.Get(d.config.TagName)
tagValue = strings.SplitN(tagValue, ",", 2)[0]
if tagValue != "" {
fieldName = tagValue
}
//read note 根据key获取map中的值
rawMapKey := reflect.ValueOf(fieldName)
rawMapVal := dataVal.MapIndex(rawMapKey)
//read note 循环Key数组,进行大小写不敏感匹配,获得map中可以,对应的value
if !rawMapVal.IsValid() {
// Do a slower search by iterating over each key and
// doing case-insensitive search.
for dataValKey, _ := range dataValKeys {
mK, ok := dataValKey.Interface().(string)
if !ok {
// Not a string key
continue
}
//read note 不区分大小写
if strings.EqualFold(mK, fieldName) {
rawMapKey = dataValKey
rawMapVal = dataVal.MapIndex(dataValKey)
break
}
}
if !rawMapVal.IsValid() {
// There was no matching key in the map for the value in
// the struct. Just ignore.
continue
}
}
// Delete the key we're using from the unused map so we stop tracking
//read note 未使用的key数组把被转换的key删掉
delete(dataValKeysUnused, rawMapKey.Interface())
//read note 非IsValid直接报错
if !field.IsValid() {
// This should never happen
panic("field is not valid")
}
// If we can't set the field, then it is unexported or something,
// and we just continue onwards.
if !field.CanSet() {
continue
}
// If the name is empty string, then we're at the root, and we
// don't dot-join the fields.
if name != "" {
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
}
//read note 字段再递归进去处理,可能是对应的不同的类型
if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil {
errors = appendErrors(errors, err)
}
}
}
func (d *Decoder) produceErrorWithUnused(dataValKeysUnused map[interface{}]struct{}, name string, errors []string) {
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
keys := make([]string, 0, len(dataValKeysUnused))
for rawKey, _ := range dataValKeysUnused {
keys = append(keys, rawKey.(string))
}
sort.Strings(keys)
err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
errors = appendErrors(errors, err)
}
}
func (d *Decoder) produceMetadata(dataValKeysUnused map[interface{}]struct{}, name string) {
if d.config.Metadata != nil {
for rawKey, _ := range dataValKeysUnused {
key := rawKey.(string)
if name != "" {
key = fmt.Sprintf("%s.%s", name, key)
}
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
}
}
}
func (d *Decoder) getUseAndUnUseKeyList(dataVal reflect.Value) (map[reflect.Value]struct{}, map[interface{}]struct{}) {
dataValKeys := make(map[reflect.Value]struct{})
dataValKeysUnused := make(map[interface{}]struct{})
//read note 循环map的key,组装key数组和未使用的key数组
for _, dataValKey := range dataVal.MapKeys() {
dataValKeys[dataValKey] = struct{}{}
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
}
return dataValKeys, dataValKeysUnused
}
@@ -0,0 +1,157 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package heap provides heap operations for any type that implements
// heap.Interface. A heap is a tree with the property that each node is the
// minimum-valued node in its subtree.
//
// The minimum element in the tree is the root, at index 0.
//
// A heap is a common way to implement a priority queue. To build a priority
// queue, implement the Heap interface with the (negative) priority as the
// ordering for the Less method, so Push adds items while Pop removes the
// highest-priority item from the queue. The Examples include such an
// implementation; the file example_pq_test.go has the complete source.
//
//read note 拷贝一个代码过来解析.感觉在go里面拷贝代码简单多了,依赖可以直接使用。我在这边使用read note(自定义todo标识)来标识我的解析.
package sourceAnalysis
import "sort"
// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
// Init establishes the heap invariants required by the other routines in this package.
// Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// The complexity is O(n) where n = h.Len().
//read note 对整个Interface 进行重构,时间复杂度是 O(n)
func Init(h Interface) {
// heapify
n := h.Len()
//read note 从最小父节点,到第0位的根节点,分别向下进行重构处理(之所以要用循环是因为一次向下的重构,只能对一条连续的分支进行重构,不彻底.)
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}
// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
// read note 这个Push方法是往 Interface里面去新增一个元素.和我们继承的Push方法有差别.这边同时候做了重构操作.
// 一次Push的时间复杂度是 O(logN),一次Init的时间复杂度是O(N),按道理Push的元素越多,Init的效率越高
func Push(h Interface, x interface{}) {
//read note 先把元素添加进去
h.Push(x)
//read note 然后在从下往上进行重构处理,正常情况下Push都是把元素添加在最后一个位置,如果实现的方法,把Push添加到其他位置如果不进行Init,应该是会有问题.
up(h, h.Len()-1)
}
// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
//read note 把最小的元素输出,需要注意的是真正的Pop必须是调用这个方法,而不是我们继承的那个方法.
// 时间复杂度是O(logN)
func Pop(h Interface) interface{} {
//read note 真正的Pop输出的是最小的元素,也就是最小堆的第0位置的元素
// 所以这边的操作是把第0位的数组放到最后一位,然后从位置0开始,到N-1的位置,对所有元素进行down(父子节点比较交换)的操作
n := h.Len() - 1
h.Swap(0, n)
down(h, 0, n)
return h.Pop()
}
// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
//read note 移除某个位置的元素,时间复杂度是 o(logn)
func Remove(h Interface, i int) interface{} {
//read note 判断移除的下标不等于最后一个元素位置,要特殊处理
n := h.Len() - 1
if n != i {
//read note 交换第i个元素和最后一个元素
h.Swap(i, n)
//read note Fix的处理操作,这边只会处理到移除一个元素后的位置,也就是说被移除的那个元素(在最后的位置)不会参与重构数组的操作
if !down(h, i, n) {
up(h, i)
}
}
//read note 调用Pop,把最后一个元素返回回去
return h.Pop()
}
// Fix re-establishes the heap ordering after the element at index i has changed its value.
// Changing the value of the element at index i and then calling Fix is equivalent to,
// but less expensive than, calling Remove(h, i) followed by a Push of the new value.
// The complexity is O(log n) where n = h.Len().
//read note 当下标为i的元素发生改变,需要进行一次Fix的处理,时间复杂度是 o(logn)
func Fix(h Interface, i int) {
//read note 想从下标i的这个元素往下查找处理,如果往下没有交换元素,再往上进行处理.
if !down(h, i, h.Len()) {
up(h, i)
}
}
//read note h: 对应的数组数据
//read note j:子节点的下标
//read note 方法作用
func up(h Interface, j int) {
for {
//read note 拿到对应的父节点的下标
i := (j - 1) / 2 // parent
//read note 找到最后一个父节点 || 父节点比子节点小,则跳出循环
if i == j || !h.Less(j, i) {
break
}
//read note 否则就是父节点比子节点的值大,需要交换对应的元素,然后找到父节点的下标,再往上找其父节点的关系
h.Swap(i, j)
j = i
}
}
//read note h: 对应的数组数据
//read note i0: 需要下发处理的坐标,这边也就是左右子节点的父节点下标.
//read note n: 数组对应的总长度
//read note 方法作用:从父节点开始,循环向下判断对应的元素是否在对应的环境上
func down(h Interface, i0, n int) bool {
//read note 把父节点的下标拿出来
i := i0
//read note 循环的结束条件是:
for {
//read note 找到左边子节点下标
j1 := 2*i + 1
//read note 退出条件1:超过最大长度或者是负值(这个应该是针对传入就有问题的处理)
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
//read note 拿到左孩子和右孩子中比较小的那个元素(的下标)
j := j1 // left child
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
j = j2 // = 2*i + 2 // right child
}
//read note 判断父节点和子节点的大小关系,小的元素应该在父节点,所以如果子节点本身就比较小,直接退出循环,否则交换元素
//read note 然后再把下标移动到被交换的这个元素上,计算被交换的这个元素和它的左右子节点的大小关系,进入下一个循环
if !h.Less(j, i) {
break
}
h.Swap(i, j)
i = j
}
//read note 判断是否发生了交换操作
return i > i0
}
@@ -0,0 +1,255 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package list implements a doubly linked list.
//
// To iterate over a list (where l is a *List):
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.Value
// }
//
package sourceAnalysis
// Element is an element of a linked list.
//read note list中对应的元素,list相当是通过一个链表来链接所有的Element元素.
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
//read note 指向下一个和前一个元素的指针.internally a list l is implemented as a ring:内部实现实际是一个环
next, prev *Element
// The list to which this element belongs.
//read note 链表头,这边是设置表头为哨兵的模式进行链表的处理的
list *List
// The value stored with this element.
//read note Element中实际的元素值
Value interface{}
}
// Next returns the next list element or nil.
func (e *Element) Next() *Element {
//read note 获取下一个元素,这边判断条件包含 list不为空,以及next不为链表头结点(哨兵).
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// Prev returns the previous list element or nil.
func (e *Element) Prev() *Element {
//read note 与Next一样的道理,判断list不为空,以及pre不等于链表头结点
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
//read note 设置哨兵的链表实现
type List struct {
//read note 哨兵结点
root Element // sentinel list element, only &root, root.prev, and root.next are used
//read note 链表长度
len int // current list length excluding (this) sentinel element
}
// Init initializes or clears list l.
func (l *List) Init() *List {
//read note 初始化的时候,链表的哨兵结点pre和next是互相指向的,这也可以作为判断链表是否为空的依据
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// New returns an initialized list.
func New() *List { return new(List).Init() }
// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List) Len() int { return l.len }
// Front returns the first element of list l or nil if the list is empty.
// read note 返回第一个元素,如果链表不为空,则返回哨兵结点的next就是一个链表结点
func (l *List) Front() *Element {
if l.len == 0 {
return nil
}
return l.root.next
}
// Back returns the last element of list l or nil if the list is empty.
// read note 所以list是一个环,如果链表不为空,则最后一个元素可以通过哨兵的prev来获取到.
func (l *List) Back() *Element {
if l.len == 0 {
return nil
}
return l.root.prev
}
// lazyInit lazily initializes a zero List value.
func (l *List) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
// insert inserts e after at, increments l.len, and returns e.
//read note 在某一个位置后面加入一个element,只需要设置一下长度,前后链表地址的指向即可。
func (l *List) insert(e, at *Element) *Element {
n := at.next
at.next = e
e.prev = at
e.next = n
n.prev = e
e.list = l
l.len++
return e
}
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List) insertValue(v interface{}, at *Element) *Element {
return l.insert(&Element{Value: v}, at)
}
// remove removes e from its list, decrements l.len, and returns e.
//read note 移除某个元素,返回该元素并设置不相关的属性为空,防止内存泄露.
func (l *List) remove(e *Element) *Element {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--
return e
}
// move moves e to next to at and returns e.
//read note 移动某个元素,本质上和插入某个元素有点类似.即移除对应节点后,再在对应的节点后面插入该元素
func (l *List) move(e, at *Element) *Element {
if e == at {
return e
}
e.prev.next = e.next
e.next.prev = e.prev
n := at.next
at.next = e
e.prev = at
e.next = n
n.prev = e
return e
}
// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List) Remove(e *Element) interface{} {
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return e.Value
}
// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List) PushFront(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, &l.root)
}
// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List) PushBack(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List) InsertBefore(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark.prev)
}
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List) InsertAfter(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark)
}
// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
// MoveToBack moves element e to the back of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List) MoveToBack(e *Element) {
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
// MoveBefore moves element e to its new position before mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List) MoveBefore(e, mark *Element) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
// MoveAfter moves element e to its new position after mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List) MoveAfter(e, mark *Element) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
// PushBackList inserts a copy of an other list at the back of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List) PushBackList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
// PushFrontList inserts a copy of an other list at the front of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List) PushFrontList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}
@@ -0,0 +1,150 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ring implements operations on circular lists.
package sourceAnalysis
// A Ring is an element of a circular list, or ring.
// Rings do not have a beginning or end; a pointer to any ring element
// serves as reference to the entire ring. Empty rings are represented
// as nil Ring pointers. The zero value for a Ring is a one-element
// ring with a nil Value.
//
//read note 环结构:指针可能指向任意元素,空链表只有一个nil元素
type Ring struct {
next, prev *Ring
Value interface{} // for use by client; untouched by this library
}
//read note 初始化只是做了指针指向,不会增加环的值
func (r *Ring) init() *Ring {
r.next = r
r.prev = r
return r
}
// Next returns the next ring element. r must not be empty.
//read note:环的下一个值,如果环的next为空,则初始化该环
func (r *Ring) Next() *Ring {
if r.next == nil {
return r.init()
}
return r.next
}
// Prev returns the previous ring element. r must not be empty.
func (r *Ring) Prev() *Ring {
if r.next == nil {
return r.init()
}
return r.prev
}
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
// in the ring and returns that ring element. r must not be empty.
//
//read note 对环上的当前元素进行n次位置的移动,小于0则向后移动,大于0则向前移动
func (r *Ring) Move(n int) *Ring {
if r.next == nil {
return r.init()
}
switch {
case n < 0:
for ; n < 0; n++ {
r = r.prev
}
case n > 0:
for ; n > 0; n-- {
r = r.next
}
}
return r
}
// New creates a ring of n elements.
//read note 创建n个节点的环
func New1(n int) *Ring {
if n <= 0 {
return nil
}
r := new(Ring)
p := r
for i := 1; i < n; i++ {
p.next = &Ring{prev: p}
p = p.next
}
p.next = r
r.prev = p
return r
}
// Link connects ring r with ring s such that r.Next()
// becomes s and returns the original value for r.Next().
// r must not be empty.
//
// If r and s point to the same ring, linking
// them removes the elements between r and s from the ring.
// The removed elements form a subring and the result is a
// reference to that subring (if no elements were removed,
// the result is still the original value for r.Next(),
// and not nil).
//
// If r and s point to different rings, linking
// them creates a single ring with the elements of s inserted
// after r. The result points to the element following the
// last element of s after insertion.
//
//read note 链接环,如果是两个相同的环则环不改变,链接完环会少掉原来的第一个元素....(神奇)
// 如果是两个不同的环,则把另一个环接到上一个环的后面,链接完的第一个元素是原来环的next的元素.第二个元素
func (r *Ring) Link(s *Ring) *Ring {
n := r.Next()
if s != nil {
p := s.Prev()
// Note: Cannot use multiple assignment because
// evaluation order of LHS is not specified.
r.next = s
s.prev = r
n.prev = p
p.next = n
}
return n
}
// Unlink removes n % r.Len() elements from the ring r, starting
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
// The result is the removed subring. r must not be empty.
//
//read note 断开环上n个元素,这里会根据n%r.Len 得到的余数进行Unlink,因为环是没有起点终点的,所以超过或者不超过环的长度都是按照余数进行处理
func (r *Ring) Unlink(n int) *Ring {
if n <= 0 {
return nil
}
return r.Link(r.Move(n + 1))
}
// Len computes the number of elements in ring r.
// It executes in time proportional to the number of elements.
//
func (r *Ring) Len() int {
n := 0
if r != nil {
n = 1
for p := r.Next(); p != r; p = p.next {
n++
}
}
return n
}
// Do calls function f on each element of the ring, in forward order.
// The behavior of Do is undefined if f changes *r.
//read note 对环上的所有元素进行函数操作,但是这边的操作好像是没办法改变环中的元素的,只能进行提取或者输出
func (r *Ring) Do(f func(interface{})) {
if r != nil {
f(r.Value)
for p := r.Next(); p != r; p = p.next {
f(p.Value)
}
}
}
@@ -0,0 +1,36 @@
/*
* @Author : huangzj
* @Time : 2020/12/2 15:39
* @Description
*/
package testContainer
type Person struct {
Name string //名字
Age int //年龄
Money float64 //身价
}
type heapTool []*Person
func (h *heapTool) Less(i, j int) bool {
return (*h)[i].Age < (*h)[j].Age
}
func (h *heapTool) Swap(i, j int) {
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}
func (h *heapTool) Len() int {
return len(*h)
}
func (h *heapTool) Pop() (v interface{}) {
*h, v = (*h)[:h.Len()-1], (*h)[h.Len()-1]
return
}
func (h *heapTool) Push(v interface{}) {
*h = append(*h, v.(*Person))
}
@@ -0,0 +1,62 @@
/*
* @Author : huangzj
* @Time : 2020/12/2 11:19
* @Description
*/
package testContainer
import (
"container/heap"
"fmt"
"testing"
)
func TestHeapTool(t *testing.T) {
personList := new(heapTool)
personList.Push(&Person{
Name: "小明",
Age: 20,
Money: 100000.99,
})
personList.Push(&Person{
Name: "小施",
Age: 30,
Money: 1002341.99,
})
personList.Push(&Person{
Name: "小康",
Age: 10,
Money: 200.99,
})
personList.Push(&Person{
Name: "老施",
Age: 50,
Money: 10343240000.99,
})
personList.Push(&Person{
Name: "老康",
Age: 70,
Money: 10340.99,
})
personList.Push(&Person{
Name: "老明",
Age: 80,
Money: 13240000.99,
})
personList.Push(&Person{
Name: "老林",
Age: 90,
Money: 10340000.99,
})
heap.Init(personList)
for personList.Len() > 0 {
pop := heap.Pop(personList)
fmt.Println(fmt.Sprintf("%v,%v岁,资产:%v", pop.(*Person).Name, pop.(*Person).Age, pop.(*Person).Money))
}
}
@@ -0,0 +1,69 @@
/*
* @Author : huangzj
* @Time : 2020/12/3 21:58
* @Description:测试list.go包的元素
*/
package testContainer
import (
"container/list"
"fmt"
"testing"
)
func TestList(t *testing.T) {
list2 := list.New()
list2.PushFront(Person{
Name: "康康",
Age: 10,
Money: 20,
})
list2.PushBack(Person{
Name: "老施",
Age: 40,
Money: 1000000,
})
for e := list2.Front(); e != nil; e = e.Next() {
value := e.Value.(Person)
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
}
fmt.Println()
fmt.Println()
frontE := list2.Front()
list2.MoveToBack(frontE)
for e := list2.Front(); e != nil; e = e.Next() {
value := e.Value.(Person)
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
}
fmt.Println()
fmt.Println()
xiaozhang := list2.InsertBefore(Person{
Name: "小张",
Age: 10,
Money: 50,
}, list2.Front())
for e := list2.Front(); e != nil; e = e.Next() {
value := e.Value.(Person)
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
}
fmt.Println()
fmt.Println()
list2.Remove(xiaozhang)
for e := list2.Front(); e != nil; e = e.Next() {
value := e.Value.(Person)
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
}
fmt.Println()
fmt.Println()
front := list2.Front().Value.(Person)
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", front.Name, front.Age, front.Money))
}
@@ -0,0 +1,114 @@
/*
* @Author : huangzj
* @Time : 2020/12/7 9:33
* @Description
*/
package testContainer
import (
"container/ring"
"fmt"
"testing"
)
func TestRing(t *testing.T) {
fmt.Println("测试空链表")
var rings ring.Ring
fmt.Println(rings.Next().Value)
fmt.Println("")
fmt.Println("")
fmt.Println("测试环")
newRing := makeN(10)
Print(newRing)
fmt.Println("")
fmt.Println("")
fmt.Println("测试环Link自己")
newRing = makeN(10)
newRing = newRing.Link(newRing)
Print(newRing)
fmt.Println()
fmt.Println()
fmt.Println("测试环Link其他环")
newRing = makeN(10)
newRing1 := makeN(5)
newRing = newRing.Link(newRing1)
Print(newRing)
fmt.Println()
fmt.Println()
fmt.Println("测试Move")
newRing1 = makeN(5)
newRing1 = newRing1.Move(3)
Print(newRing1)
fmt.Println()
fmt.Println()
fmt.Println("测试Do,没有办法改变环中的元素.")
newRing1 = makeN(5)
newRing1.Do(func(i interface{}) {
i = i.(int) + 1000
})
Print(newRing1)
newRingx := ring.New(3)
for i := 1; i <= newRingx.Len(); i++ {
newRingx.Value = Person{
Name: "小明",
Age: 100,
Money: 19999,
}
newRingx = newRingx.Next()
newRingx.Value = Person{
Name: "小康",
Age: 50,
Money: 19921999,
}
newRingx = newRingx.Next()
newRingx.Value = Person{
Name: "小施",
Age: 20,
Money: 1111999,
}
}
fmt.Println("测试Do,只能对元素进行提取或者输出.")
s := make([]int, 0)
newRingx.Do(func(i interface{}) {
s = append(s, i.(Person).Age)
})
for _, item := range s {
fmt.Println(item)
}
fmt.Println()
fmt.Println()
fmt.Println("测试Unlink")
newRing1 = makeN(5)
newRing2 := newRing1.Unlink(2)
Print(newRing1)
println()
fmt.Println("如果用赋值语句,可以返回被移除的元素")
Print(newRing2)
}
func Print(r *ring.Ring) {
i, n := 0, r.Len()
for p := r; i < n; p = p.Next() {
fmt.Println(fmt.Sprintf("当前元素是%v", p.Value))
i++
}
}
//创造对应的链表
func makeN(n int) *ring.Ring {
r := ring.New(n)
for i := 1; i <= n; i++ {
r.Value = i
r = r.Next()
}
return r
}
@@ -0,0 +1,3 @@
guard 'gotest' do
watch(%r{\.go$})
end
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Jinzhu
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,130 @@
# Copier
I am a copier, I copy everything from one to another
[![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions)
## Features
* Copy from field to field with same name
* Copy from method to field with same name
* Copy from field to method with same name
* Copy from slice to slice
* Copy from struct to slice
* Copy from map to map
* Enforce copying a field with a tag
* Ignore a field with a tag
## Usage
```go
package main
import (
"fmt"
"github.com/jinzhu/copier"
)
type User struct {
Name string
Role string
Age int32
// Explicitly ignored in the destination struct.
Salary int
}
func (user *User) DoubleAge() int32 {
return 2 * user.Age
}
// Tags in the destination Struct provide instructions to copier.Copy to ignore
// or enforce copying and to panic or return an error if a field was not copied.
type Employee struct {
// Tell copier.Copy to panic if this field is not copied.
Name string `copier:"must"`
// Tell copier.Copy to return an error if this field is not copied.
Age int32 `copier:"must,nopanic"`
// Tell copier.Copy to explicitly ignore copying this field.
Salary int `copier:"-"`
DoubleAge int32
EmployeId int64
SuperRule string
}
func (employee *Employee) Role(role string) {
employee.SuperRule = "Super " + role
}
func main() {
var (
user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000}
users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}}
employee = Employee{Salary: 150000}
employees = []Employee{}
)
copier.Copy(&employee, &user)
fmt.Printf("%#v \n", employee)
// Employee{
// Name: "Jinzhu", // Copy from field
// Age: 18, // Copy from field
// Salary:150000, // Copying explicitly ignored
// DoubleAge: 36, // Copy from method
// EmployeeId: 0, // Ignored
// SuperRule: "Super Admin", // Copy to method
// }
// Copy struct to slice
copier.Copy(&employees, &user)
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"}
// }
// Copy slice to slice
employees = []Employee{}
copier.Copy(&employees, &users)
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"},
// {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeId: 0, SuperRule: "Super Dev"},
// }
// Copy map to map
map1 := map[int]int{3: 6, 4: 8}
map2 := map[int32]int8{}
copier.Copy(&map2, map1)
fmt.Printf("%#v \n", map2)
// map[int32]int8{3:6, 4:8}
}
```
### Copy with Option
```go
copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true})
```
## Contributing
You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do.
# Author
**jinzhu**
* <http://github.com/jinzhu>
* <wosmvp@gmail.com>
* <http://twitter.com/zhangjinzhu>
## License
Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License).
@@ -0,0 +1,398 @@
package copier
import (
"database/sql"
"errors"
"fmt"
"reflect"
"strings"
)
// These flags define options for tag handling
const (
// Denotes that a destination field must be copied to. If copying fails then a panic will ensue.
tagMust uint8 = 1 << iota
// Denotes that the program should not panic when the must flag is on and
// value is not copied. The program will return an error instead.
tagNoPanic
// Ignore a destation field from being copied to.
tagIgnore
// Denotes that the value as been copied
hasCopied
)
// Copy copy things
func Copy(toValue interface{}, fromValue interface{}) (err error) {
return copy(toValue, fromValue, false)
}
type Option struct {
IgnoreEmpty bool
}
// CopyWithOption copy with option
func CopyWithOption(toValue interface{}, fromValue interface{}, option Option) (err error) {
return copy(toValue, fromValue, option.IgnoreEmpty)
}
func copy(toValue interface{}, fromValue interface{}, ignoreEmpty bool) (err error) {
var (
isSlice bool
amount = 1
from = indirect(reflect.ValueOf(fromValue)) //read note 判断是指针还是结构体类型来返回对应的参数的值
to = indirect(reflect.ValueOf(toValue)) //read note 判断是指针还是结构体类型来返回对应的参数的值
)
//read note 不可寻址的数据类型可以参考:https://github.com/hyper0x/Golang_Puzzlers/blob/master/src/puzzlers/article15/q1/demo35.go
if !to.CanAddr() {
return errors.New("copy to value is unaddressable")
}
// Return is from value is invalid
if !from.IsValid() {
return
}
//read note 返回参数的具体类型
fromType := indirectType(from.Type())
toType := indirectType(to.Type())
// Just set it if possible to assign
// And need to do copy anyway if the type is struct
//read note 判断是否非结构体并且可以直接赋值
if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) {
to.Set(from)
return
}
//read note from和to都是map
if fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map {
//read note 判断map的key的结构类型是否可以转换,因为这边已经判断是map,所以直接通过 .key来获取,不担心是否报错
if !fromType.Key().ConvertibleTo(toType.Key()) {
return
}
//read note 判断要转换的Map是否为空,为空则进行初始化
if to.IsNil() {
to.Set(reflect.MakeMapWithSize(toType, from.Len()))
}
//read note 遍历Map的所有key
for _, k := range from.MapKeys() {
//read note 根据to的key类型创建一个新的key
toKey := indirect(reflect.New(toType.Key()))
//read note 设置key的值
if !set(toKey, k) {
continue
}
//read note 设置value值
toValue := indirect(reflect.New(toType.Elem()))
if !set(toValue, from.MapIndex(k)) {
//read note 对嵌套的结构体进行copy
err = Copy(toValue.Addr().Interface(), from.MapIndex(k).Interface())
if err != nil {
continue
}
}
to.SetMapIndex(toKey, toValue)
}
}
//read note 只要有一个数据结构不是结构体直接返回?
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
return
}
//read note 切片处理:设置切片的长度
//read note 如果Result是Slice类型
if to.Kind() == reflect.Slice {
//read note 设置isSlice
isSlice = true
//read note amount初始是1,如果被复制的对象也是数组,则这边修改成数组长度,否则1表示只处理结构体
if from.Kind() == reflect.Slice {
amount = from.Len()
}
}
//read note 循环被复制的切片的长度
for i := 0; i < amount; i++ {
var dest, source reflect.Value
//read note Result的结果是数组
if isSlice {
// source
// read note 如果Origin的类型是数组,需要根据index进行获取结构体
if from.Kind() == reflect.Slice {
source = indirect(from.Index(i))
} else {
// read note 如果Origin的类型是结构体,直接获取该结构体
source = indirect(from)
}
// dest
dest = indirect(reflect.New(toType).Elem())
} else {
//read note Result的结果不是数组,直接获取结构体
source = indirect(from)
dest = indirect(to)
}
// Get tag options
//read note 获取tag的所有标签
tagBitFlags := map[string]uint8{}
if dest.IsValid() {
//read note 根据结构体type获取所有的Field对应的tag标签
tagBitFlags = getBitFlags(toType)
}
// check source
//read note 如果是非零值
if source.IsValid() {
//read note 获取结构体的所有Field的信息(数组)
fromTypeFields := deepFields(fromType)
// fmt.Printf("%#v", fromTypeFields)
// Copy from field to field or method
//todo 循环所有的Field处理
for _, field := range fromTypeFields {
name := field.Name
// Get bit flags for field
//read note 根据name获取tag数据
fieldFlags, _ := tagBitFlags[name]
// Check if we should ignore copying
//read note ignore标签对结构体的影响处理
if (fieldFlags & tagIgnore) != 0 {
continue
}
//read note Origin的方法和Result字段同名的处理
if fromField := source.FieldByName(name); fromField.IsValid() && !shouldIgnore(fromField, ignoreEmpty) {
// has field
//read note 根据名称获取Result结构体的字段.
if toField := dest.FieldByName(name); toField.IsValid() {
if toField.CanSet() {
if !set(toField, fromField) {
if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil {
return err
}
} else {
//read note 赋值完成,设置对应的标识
if fieldFlags != 0 {
// Note that a copy was made
//read note 设置复制标识
tagBitFlags[name] = fieldFlags | hasCopied
}
}
}
} else {
// try to set to method
var toMethod reflect.Value
//read note 通过名称找到对应的Method
if dest.CanAddr() {
toMethod = dest.Addr().MethodByName(name)
} else {
toMethod = dest.MethodByName(name)
}
//read note 【被转换对象的方法调用】的校验还比较严格,这边可以看出来只能有一个字段,并且字段类型要对应上
if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
//read note 调用声明的方法
toMethod.Call([]reflect.Value{fromField})
}
}
}
}
//read note Result方法 与Origin字段同名的处理
// Copy from method to field
//read note 处理目标结构体的方法,目标结构体的方法要和被复制结构体的字段名一致,就是这边控制的
for _, field := range deepFields(toType) {
name := field.Name
//read note 根据Result的字段,获取Origin同名的方法
var fromMethod reflect.Value
if source.CanAddr() {
fromMethod = source.Addr().MethodByName(name)
} else {
fromMethod = source.MethodByName(name)
}
//read note 如果方法符合规则,没有入参,有一个出参,则进行对应方法的调用处理
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, ignoreEmpty) {
if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() {
values := fromMethod.Call([]reflect.Value{})
if len(values) >= 1 {
//read note 进行字段的设值
set(toField, values[0])
}
}
}
}
}
//read note 转换结果Result是切片的处理:分成两种情况,被复制的是 结构体指针 和 结构体
if isSlice {
if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
to.Set(reflect.Append(to, dest.Addr()))
} else if dest.Type().AssignableTo(to.Type().Elem()) {
to.Set(reflect.Append(to, dest))
}
}
//read note 这边是不是会有一个问题,就是err是不是会被覆盖,前面的字段有错误,最后一个没有错误则会覆盖之前的error
err = checkBitFlags(tagBitFlags)
}
return
}
func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool {
if !ignoreEmpty {
return false
}
return v.IsZero()
}
//read note 根据结构体类型,获取结构体对应的Field切片,注意这边的【Anonymous】表示的匿名变量,匿名变量的Field这边需要特殊处理
func deepFields(reflectType reflect.Type) []reflect.StructField {
var fields []reflect.StructField
//read note 判断是不是结构体,只能对结构体进行处理
if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
//read note 循环处理对应的所有Field
for i := 0; i < reflectType.NumField(); i++ {
v := reflectType.Field(i)
//read note 对【嵌入(匿名)字段】结构体 的所有结构体进行添加
if v.Anonymous {
fields = append(fields, deepFields(v.Type)...)
} else {
fields = append(fields, v)
}
}
}
return fields
}
func indirect(reflectValue reflect.Value) reflect.Value {
for reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
return reflectValue
}
func indirectType(reflectType reflect.Type) reflect.Type {
for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
reflectType = reflectType.Elem()
}
return reflectType
}
func set(to, from reflect.Value) bool {
//read note IsValid返回是否非零值的结果.所以这边的处理是针对非零值
if from.IsValid() {
//read note 前置条件:处理to的类型,如果from为空,则直接设置空值返回
// to是指针类型特殊处理
if to.Kind() == reflect.Ptr {
// set `to` to nil if from is nil
//read note 如果from是空,则直接设置to为零值返回
if from.Kind() == reflect.Ptr && from.IsNil() {
to.Set(reflect.Zero(to.Type()))
return true
} else if to.IsNil() {
//read note 如果to是nil且不满足上面的from为nil的条件,这个时候要给to设置默认值
to.Set(reflect.New(to.Type().Elem()))
}
//read note 指针的转换处理
to = to.Elem()
}
//read note from和to类型的转换处理,这边当from是ptr类型的时候,会调用set进行递归处理
//read note 如果类型可以进行转换,则要设置对应的值(具体什么类型可以转换需要看一下源码,这里不多赘述)
if from.Type().ConvertibleTo(to.Type()) {
to.Set(from.Convert(to.Type()))
} else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok {
//read note sql.Scanner 这个不知道具体是干嘛的.
err := scanner.Scan(from.Interface())
if err != nil {
return false
}
} else if from.Kind() == reflect.Ptr {
//read note from是指针类型,处理成结构体进行赋值(相当于递归再往下走)
return set(to, from.Elem())
} else {
//read note 其他不能转换的直接返回false
return false
}
}
//read note 零值直接返回true,零值不处理
return true
}
// parseTags Parses struct tags and returns uint8 bit flags.
func parseTags(tag string) (flags uint8) {
for _, t := range strings.Split(tag, ",") {
switch t {
case "-":
flags = tagIgnore
return
case "must":
flags = flags | tagMust
case "nopanic":
flags = flags | tagNoPanic
}
}
return
}
// getBitFlags Parses struct tags for bit flags.
func getBitFlags(toType reflect.Type) map[string]uint8 {
//read note 存储的结构是 FieldName->tag对应的二进制数据(tag标签转换成程序标识)
flags := map[string]uint8{}
//read note 根据结构体的类型获取对应的Field切片
toTypeFields := deepFields(toType)
// Get a list dest of tags
//read note 循环Field切片,获取切片对应的tag数据
for _, field := range toTypeFields {
tags := field.Tag.Get("copier") //tag标签是【copier】
if tags != "" {
//read note tag标签转换成程序处理标识(这边也是使用二进制的处理方式)
flags[field.Name] = parseTags(tags)
}
}
return flags
}
// checkBitFlags Checks flags for error or panic conditions.
func checkBitFlags(flagsList map[string]uint8) (err error) {
// Check flag conditions were met
//read note 循环mapFieldName->tag对应的二进制数据)
for name, flags := range flagsList {
//read note 如果字段没有被复制
if flags&hasCopied == 0 {
switch {
case flags&tagMust != 0 && flags&tagNoPanic != 0:
//read note 处理1:返回错误信息
err = fmt.Errorf("Field %s has must tag but was not copied", name)
return
case flags&(tagMust) != 0:
//read note 处理2:直接报错
panic(fmt.Sprintf("Field %s has must tag but was not copied", name))
}
}
}
return
}
@@ -0,0 +1,3 @@
module github.com/jinzhu/copier
go 1.15
@@ -0,0 +1,17 @@
/*
* @Author : huangzj
* @Time : 2021/1/25 16:09
* @Description
*/
package example
import (
"github.com/mitchellh/mapstructure"
"testing"
)
func TestDefaultHook(t *testing.T) {
//在 mitchellh/mapstructure 这个工程中提供了部分的默认Hook方法,主要作用是进行转换,具体可以查看源码
mapstructure.StringToIPNetHookFunc()
}
@@ -0,0 +1,132 @@
package example
import (
entity2 "Go-StudyExample/entity"
"encoding/json"
"fmt"
"github.com/goinggo/mapstructure"
"testing"
)
//-----------------------json数据---------------------
var document = `{"loginName":"sptest1","userType":{"userTypeId":1,"userTypeName":"normal_user","t":"2026-01-02 15:04:05"}}`
var document1 = `{"cobrandId":10010352,"channelId":-1,"locale":"en_US","tncVersion":2,"people":[{"name":"jack","age":{"birth":10,"year":2000,"animals":[{"barks":"yes","tail":"yes"},{"barks":"no","tail":"yes"}]}},{"name":"jill","age":{"birth":11,"year":2001}}]}`
var document2 = `[{"name":"bill"},{"name":"lisa"}]`
var document3 = `{"categories":["12","222","333"],"people":{"name":"jack","age":{"birth":10,"year":2000,"animals":[{"barks":"yes","tail":"yes"},{"barks":"no","tail":"yes"}]}}}`
//-----------------------------结构体-----------------------
type Animal struct {
Barks string `jpath:"barks"`
}
type People struct {
Age int `jpath:"age.birth"` // jpath is relative to the array
Animals []Animal `jpath:"age.animals"`
}
type Items struct {
Categories []string `jpath:"categories"`
Peoples []People `jpath:"people"` // Specify the location of the array
}
type Items1 struct {
Categories []string `mapstructure:"Categories"`
Peoples People1 `mapstructure:"People"` // Specify the location of the array
}
type People1 struct {
Age int `mapstructure:"age.birth"` // jpath is relative to the array
Animals []Animal `mapstructure:"age.animals"`
}
type NameDoc struct {
Name string `jpath:"name"`
}
//---------------------------------测试方法------------------------
//基本测试
func TestMapStructureTestFunc(t *testing.T) {
var te entity2.Entity
m := make(map[string]interface{})
m["Num"] = 1
m["S"] = "test"
m["T"] = map[string]string{"1": "1", "2": "2"}
err := mapstructure.Decode(m, &te)
if err != nil {
panic(err.Error())
}
fmt.Print(te.Num, " ", te.S, " ", te.T)
}
//基本测试
func TestMapStructureTestFunc1(t *testing.T) {
var docMap map[string]interface{}
_ = json.Unmarshal([]byte(document), &docMap)
var user entity2.User
err := mapstructure.Decode(docMap, &user)
if err != nil {
panic(err.Error())
}
fmt.Println(user.T.Format("2006-01-02 15:04:05"))
fmt.Println(user, " ", user.UserType.UserTypeId, " ", user.UserType.UserTypeName)
}
//测试切片转换
func TestMapStructureTestFunc2(t *testing.T) {
sliceScript := []byte(document2)
var sliceMap []map[string]interface{}
_ = json.Unmarshal(sliceScript, &sliceMap)
var myslice []NameDoc
err := mapstructure.DecodeSlicePath(sliceMap, &myslice)
if err != nil {
panic(err.Error())
}
fmt.Println(myslice[0], " ", myslice[1])
}
//测试DecodePath
func TestMapStructureTestFunc3(t *testing.T) {
docScript := []byte(document1)
var docMap map[string]interface{}
_ = json.Unmarshal(docScript, &docMap)
var items Items
err := mapstructure.DecodePath(docMap, &items)
if err != nil {
panic(err.Error())
}
fmt.Println(items.Peoples[0], items.Peoples[1])
}
//MetaData的测试例子
//decode不支持 mapstructure的标识是 xxxx.xxxx
//并且这边是对tag别名不区分大小写的
func TestMetaData(t *testing.T) {
var items1 Items1
config := &mapstructure.DecoderConfig{
Metadata: &mapstructure.Metadata{
Keys: nil,
Unused: nil,
},
Result: &items1,
}
decoder, _ := mapstructure.NewDecoder(config)
docScript := []byte(document3)
var docMap map[string]interface{}
_ = json.Unmarshal(docScript, &docMap)
_ = decoder.Decode(docMap)
fmt.Println(config.Metadata)
}
@@ -0,0 +1,10 @@
## 官方包获取方式
go get github.com/goinggo/mapstructure
## 官方提供默认Hook
获取方式:
go get github.com/mitchellh/mapstructure
@@ -0,0 +1,12 @@
version = 1
test_patterns = [
"*_test.go"
]
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_path = "github.com/imdario/mergo"
@@ -0,0 +1,4 @@
# These are supported funding model platforms
ko_fi: dariocc
custom: https://beerpay.io/imdario/mergo
@@ -0,0 +1,33 @@
#### joe made this: http://goel.io/joe
#### go ####
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
#### vim ####
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
@@ -0,0 +1,9 @@
language: go
install:
- go get -t
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- go test -race -v ./...
after_script:
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
@@ -0,0 +1,7 @@
{
"go.lintTool": "golangci-lint",
"go.lintFlags": [
"--enable-all",
"--disable=gomnd"
]
}
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,28 @@
Copyright (c) 2013 Dario Castañé. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,247 @@
# Mergo
[![GoDoc][3]][4]
[![GitHub release][5]][6]
[![GoCard][7]][8]
[![Build Status][1]][2]
[![Coverage Status][9]][10]
[![Sourcegraph][11]][12]
[![FOSSA Status][13]][14]
[![GoCenter Kudos][15]][16]
[1]: https://travis-ci.org/imdario/mergo.png
[2]: https://travis-ci.org/imdario/mergo
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
[4]: https://godoc.org/github.com/imdario/mergo
[5]: https://img.shields.io/github/release/imdario/mergo.svg
[6]: https://github.com/imdario/mergo/releases
[7]: https://goreportcard.com/badge/imdario/mergo
[8]: https://goreportcard.com/report/github.com/imdario/mergo
[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
[10]: https://coveralls.io/github/imdario/mergo?branch=master
[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
[15]: https://search.gocenter.io/api/ui/badge/github.com%2Fimdario%2Fmergo
[16]: https://search.gocenter.io/github.com/imdario/mergo
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
## Status
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
### Important note
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds suppot for go modules.
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
### Donations
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
[![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo)
[![Beerpay](https://beerpay.io/imdario/mergo/make-wish.svg)](https://beerpay.io/imdario/mergo)
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
### Mergo in the wild
- [moby/moby](https://github.com/moby/moby)
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
- [vmware/dispatch](https://github.com/vmware/dispatch)
- [Shopify/themekit](https://github.com/Shopify/themekit)
- [imdario/zas](https://github.com/imdario/zas)
- [matcornic/hermes](https://github.com/matcornic/hermes)
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
- [kataras/iris](https://github.com/kataras/iris)
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
- [go-task/task](https://github.com/go-task/task)
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
- [ory/hydra](https://github.com/ory/hydra)
- [sisatech/vcli](https://github.com/sisatech/vcli)
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
- [projectcalico/felix](https://github.com/projectcalico/felix)
- [resin-os/balena](https://github.com/resin-os/balena)
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
- [Telefonica/govice](https://github.com/Telefonica/govice)
- [supergiant/supergiant](supergiant/supergiant)
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
- [divshot/gitling](https://github.com/divshot/gitling)
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
- [elwinar/rambler](https://github.com/elwinar/rambler)
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
- [thoas/picfit](https://github.com/thoas/picfit)
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
- [janoszen/containerssh](https://github.com/janoszen/containerssh)
## Install
go get github.com/imdario/mergo
// use in your .go code
import (
"github.com/imdario/mergo"
)
## Usage
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
```go
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
```
Also, you can merge overwriting values using the transformer `WithOverride`.
```go
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
// ...
}
```
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
```go
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
```
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
Here is a nice example:
```go
package main
import (
"fmt"
"github.com/imdario/mergo"
)
type Foo struct {
A string
B int64
}
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
}
mergo.Merge(&dest, src)
fmt.Println(dest)
// Will print
// {two 2}
}
```
Note: if test are failing due missing package, please execute:
go get gopkg.in/yaml.v2
### Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
```go
package main
import (
"fmt"
"github.com/imdario/mergo"
"reflect"
"time"
)
type timeTransformer struct {
}
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
if typ == reflect.TypeOf(time.Time{}) {
return func(dst, src reflect.Value) error {
if dst.CanSet() {
isZero := dst.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if result[0].Bool() {
dst.Set(src)
}
}
return nil
}
}
return nil
}
type Snapshot struct {
Time time.Time
// ...
}
func main() {
src := Snapshot{time.Now()}
dest := Snapshot{}
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
fmt.Println(dest)
// Will print
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
}
```
## Contact me
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
## About
Written by [Dario Castañé](http://dario.im).
## Top Contributors
[![0](https://sourcerer.io/fame/imdario/imdario/mergo/images/0)](https://sourcerer.io/fame/imdario/imdario/mergo/links/0)
[![1](https://sourcerer.io/fame/imdario/imdario/mergo/images/1)](https://sourcerer.io/fame/imdario/imdario/mergo/links/1)
[![2](https://sourcerer.io/fame/imdario/imdario/mergo/images/2)](https://sourcerer.io/fame/imdario/imdario/mergo/links/2)
[![3](https://sourcerer.io/fame/imdario/imdario/mergo/images/3)](https://sourcerer.io/fame/imdario/imdario/mergo/links/3)
[![4](https://sourcerer.io/fame/imdario/imdario/mergo/images/4)](https://sourcerer.io/fame/imdario/imdario/mergo/links/4)
[![5](https://sourcerer.io/fame/imdario/imdario/mergo/images/5)](https://sourcerer.io/fame/imdario/imdario/mergo/links/5)
[![6](https://sourcerer.io/fame/imdario/imdario/mergo/images/6)](https://sourcerer.io/fame/imdario/imdario/mergo/links/6)
[![7](https://sourcerer.io/fame/imdario/imdario/mergo/images/7)](https://sourcerer.io/fame/imdario/imdario/mergo/links/7)
## License
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
Status
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
Important note
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u github.com/imdario/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
Install
Do your usual installation procedure:
go get github.com/imdario/mergo
// use in your .go code
import (
"github.com/imdario/mergo"
)
Usage
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
Also, you can merge overwriting values using the transformer WithOverride.
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
// ...
}
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
Here is a nice example:
package main
import (
"fmt"
"github.com/imdario/mergo"
)
type Foo struct {
A string
B int64
}
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
}
mergo.Merge(&dest, src)
fmt.Println(dest)
// Will print
// {two 2}
}
Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
package main
import (
"fmt"
"github.com/imdario/mergo"
"reflect"
"time"
)
type timeTransformer struct {
}
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
if typ == reflect.TypeOf(time.Time{}) {
return func(dst, src reflect.Value) error {
if dst.CanSet() {
isZero := dst.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if result[0].Bool() {
dst.Set(src)
}
}
return nil
}
}
return nil
}
type Snapshot struct {
Time time.Time
// ...
}
func main() {
src := Snapshot{time.Now()}
dest := Snapshot{}
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
fmt.Println(dest)
// Will print
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
}
Contact me
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
About
Written by Dario Castañé: https://da.rio.hn
License
BSD 3-Clause license, as Go language.
*/
package mergo
@@ -0,0 +1,5 @@
module github.com/imdario/mergo
go 1.13
require gopkg.in/yaml.v2 v2.3.0
@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -0,0 +1,21 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type issue100s struct {
Member interface{}
}
func TestIssue100(t *testing.T) {
m := make(map[string]interface{})
m["Member"] = "anything"
st := &issue100s{}
if err := mergo.Map(st, m); err != nil {
t.Error(err)
}
}
@@ -0,0 +1,48 @@
package mergo_test
import (
"reflect"
"testing"
"github.com/imdario/mergo"
)
type Record struct {
Data map[string]interface{}
Mapping map[string]string
}
func StructToRecord(in interface{}) *Record {
rec := Record{}
rec.Data = make(map[string]interface{})
rec.Mapping = make(map[string]string)
typ := reflect.TypeOf(in)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
dbFieldName := field.Tag.Get("db")
if dbFieldName != "" {
rec.Mapping[field.Name] = dbFieldName
}
}
if err := mergo.Map(&rec.Data, in); err != nil {
panic(err)
}
return &rec
}
func TestStructToRecord(t *testing.T) {
type A struct {
Name string `json:"name" db:"name"`
CIDR string `json:"cidr" db:"cidr"`
}
type Record struct {
Data map[string]interface{}
Mapping map[string]string
}
a := A{Name: "David", CIDR: "10.0.0.0/8"}
rec := StructToRecord(a)
if len(rec.Mapping) < 2 {
t.Fatalf("struct to record failed, no mapping, struct missing tags?, rec: %+v, a: %+v ", rec, a)
}
}
@@ -0,0 +1,35 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
func TestIssue121WithSliceDeepCopy(t *testing.T) {
dst := map[string]interface{}{
"inter": map[string]interface{}{
"a": "1",
"b": "2",
},
}
src := map[string]interface{}{
"inter": map[string]interface{}{
"a": "3",
"c": "4",
},
}
if err := mergo.Merge(&dst, src, mergo.WithSliceDeepCopy); err != nil {
t.Errorf("Error during the merge: %v", err)
}
if dst["inter"].(map[string]interface{})["a"].(string) != "3" {
t.Error("inter.a should equal '3'")
}
if dst["inter"].(map[string]interface{})["c"].(string) != "4" {
t.Error("inter.c should equal '4'")
}
}
@@ -0,0 +1,47 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
func TestIssue123(t *testing.T) {
src := map[string]interface{}{
"col1": nil,
"col2": 4,
"col3": nil,
}
dst := map[string]interface{}{
"col1": 2,
"col2": 3,
"col3": 3,
}
// Expected behavior
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
t.Fatal(err)
}
testCases := []struct {
key string
expected interface{}
}{
{
"col1",
nil,
},
{
"col2",
4,
},
{
"col3",
nil,
},
}
for _, tC := range testCases {
if dst[tC.key] != tC.expected {
t.Fatalf("expected %v in dst[%q], got %v", tC.expected, tC.key, dst[tC.key])
}
}
}
@@ -0,0 +1,40 @@
package mergo_test
import (
"encoding/json"
"testing"
"github.com/imdario/mergo"
)
type settings struct {
FirstSlice []string `json:"FirstSlice"`
SecondSlice []string `json:"SecondSlice"`
}
func TestIssue125MergeWithOverwrite(t *testing.T) {
var (
defaultSettings = settings{
FirstSlice: []string{},
SecondSlice: []string{},
}
something settings
data = `{"FirstSlice":[], "SecondSlice": null}`
)
if err := json.Unmarshal([]byte(data), &something); err != nil {
t.Errorf("Error while Unmarshalling maprequest: %s", err)
}
if err := mergo.Merge(&something, defaultSettings, mergo.WithOverrideEmptySlice); err != nil {
t.Errorf("Error while merging: %s", err)
}
if something.FirstSlice == nil {
t.Error("Invalid merging first slice")
}
if something.SecondSlice == nil {
t.Error("Invalid merging second slice")
}
}
@@ -0,0 +1,49 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
func TestIssue129Boolean(t *testing.T) {
type Foo struct {
A bool
B bool
}
src := Foo{
A: true,
B: false,
}
dst := Foo{
A: false,
B: true,
}
// Standard behavior
if err := mergo.Merge(&dst, src); err != nil {
t.Error(err)
}
if dst.A != true {
t.Errorf("expected true, got false")
}
if dst.B != true {
t.Errorf("expected true, got false")
}
// Expected behavior
dst = Foo{
A: false,
B: true,
}
if err := mergo.Merge(&dst, src, mergo.WithOverwriteWithEmptyValue); err != nil {
t.Error(err)
}
if dst.A != true {
t.Errorf("expected true, got false")
}
if dst.B != false {
t.Errorf("expected false, got true")
}
}
@@ -0,0 +1,32 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type foz struct {
A *bool
B string
}
func TestIssue131MergeWithOverwriteWithEmptyValue(t *testing.T) {
src := foz{
A: func(v bool) *bool { return &v }(false),
B: "src",
}
dest := foz{
A: func(v bool) *bool { return &v }(true),
B: "dest",
}
if err := mergo.Merge(&dest, src, mergo.WithOverwriteWithEmptyValue); err != nil {
t.Error(err)
}
if *src.A != *dest.A {
t.Errorf("dest.A not merged in properly: %v != %v", *src.A, *dest.A)
}
if src.B != dest.B {
t.Errorf("dest.B not merged in properly: %v != %v", src.B, dest.B)
}
}
@@ -0,0 +1,35 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type embeddedTestA struct {
Name string
Age uint8
}
type embeddedTestB struct {
embeddedTestA
Address string
}
func TestMergeEmbedded(t *testing.T) {
var (
err error
a = &embeddedTestA{
"Suwon", 16,
}
b = &embeddedTestB{}
)
if err := mergo.Merge(&b.embeddedTestA, *a); err != nil {
t.Error(err)
}
if b.Name != "Suwon" {
t.Errorf("%v %v", b.Name, err)
}
}
@@ -0,0 +1,42 @@
package mergo_test
import (
"encoding/json"
"testing"
"github.com/imdario/mergo"
)
const issue138configuration string = `
{
"Port": 80
}
`
func TestIssue138(t *testing.T) {
type config struct {
Port uint16
}
type compatibleConfig struct {
Port float64
}
foo := make(map[string]interface{})
// encoding/json unmarshals numbers as float64
// https://golang.org/pkg/encoding/json/#Unmarshal
json.Unmarshal([]byte(issue138configuration), &foo)
err := mergo.Map(&config{}, foo)
if err == nil {
t.Error("expected type mismatch error, got nil")
} else {
if err.Error() != "type mismatch on Port field: found float64, expected uint16" {
t.Errorf("expected type mismatch error, got %q", err)
}
}
c := compatibleConfig{}
if err := mergo.Map(&c, foo); err != nil {
t.Error(err)
}
}
@@ -0,0 +1,61 @@
package mergo_test
import (
"fmt"
"testing"
"github.com/imdario/mergo"
)
func TestIssue143(t *testing.T) {
testCases := []struct {
options []func(*mergo.Config)
expected func(map[string]interface{}) error
}{
{
options: []func(*mergo.Config){mergo.WithOverride},
expected: func(m map[string]interface{}) error {
properties := m["properties"].(map[string]interface{})
if properties["field1"] != "wrong" {
return fmt.Errorf("expected %q, got %v", "wrong", properties["field1"])
}
return nil
},
},
{
options: []func(*mergo.Config){},
expected: func(m map[string]interface{}) error {
properties := m["properties"].(map[string]interface{})
if properties["field1"] == "wrong" {
return fmt.Errorf("expected a map, got %v", "wrong")
}
return nil
},
},
}
for _, tC := range testCases {
base := map[string]interface{}{
"properties": map[string]interface{}{
"field1": map[string]interface{}{
"type": "text",
},
},
}
err := mergo.Map(
&base,
map[string]interface{}{
"properties": map[string]interface{}{
"field1": "wrong",
},
},
tC.options...,
)
if err != nil {
t.Error(err)
}
if err := tC.expected(base); err != nil {
t.Error(err)
}
}
}
@@ -0,0 +1,39 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type user struct {
Name string
}
type token struct {
User *user
Token *string
}
func TestIssue149(t *testing.T) {
dest := &token{
User: &user{
Name: "destination",
},
Token: nil,
}
tokenValue := "Issue149"
src := &token{
User: nil,
Token: &tokenValue,
}
if err := mergo.Merge(dest, src, mergo.WithOverwriteWithEmptyValue); err != nil {
t.Error(err)
}
if dest.User != nil {
t.Errorf("expected nil User, got %q", dest.User)
}
if dest.Token == nil {
t.Errorf("expected not nil Token, got %q", *dest.Token)
}
}
@@ -0,0 +1,28 @@
package mergo_test
import (
"encoding/json"
"testing"
"github.com/imdario/mergo"
)
func TestIssue17MergeWithOverwrite(t *testing.T) {
var (
request = `{"timestamp":null, "name": "foo"}`
maprequest = map[string]interface{}{
"timestamp": nil,
"name": "foo",
"newStuff": "foo",
}
)
var something map[string]interface{}
if err := json.Unmarshal([]byte(request), &something); err != nil {
t.Errorf("Error while Unmarshalling maprequest: %s", err)
}
if err := mergo.MergeWithOverwrite(&something, maprequest); err != nil {
t.Errorf("Error while merging: %s", err)
}
}
@@ -0,0 +1,31 @@
package mergo_test
import (
"testing"
"time"
"github.com/imdario/mergo"
)
type document struct {
Created *time.Time
}
func TestIssue23MergeWithOverwrite(t *testing.T) {
now := time.Now()
dst := document{
&now,
}
expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
src := document{
&expected,
}
if err := mergo.MergeWithOverwrite(&dst, src); err != nil {
t.Errorf("Error while merging %s", err)
}
if !dst.Created.Equal(*src.Created) { //--> https://golang.org/pkg/time/#pkg-overview
t.Errorf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created)
}
}
@@ -0,0 +1,37 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type Foo struct {
Str string
Bslice []byte
}
func TestIssue33Merge(t *testing.T) {
dest := Foo{Str: "a"}
toMerge := Foo{
Str: "b",
Bslice: []byte{1, 2},
}
if err := mergo.Merge(&dest, toMerge); err != nil {
t.Errorf("Error while merging: %s", err)
}
// Merge doesn't overwrite an attribute if in destination it doesn't have a zero value.
// In this case, Str isn't a zero value string.
if dest.Str != "a" {
t.Errorf("dest.Str should have not been override as it has a non-zero value: dest.Str(%v) != 'a'", dest.Str)
}
// If we want to override, we must use MergeWithOverwrite or Merge using WithOverride.
if err := mergo.Merge(&dest, toMerge, mergo.WithOverride); err != nil {
t.Errorf("Error while merging: %s", err)
}
if dest.Str != toMerge.Str {
t.Errorf("dest.Str should have been override: dest.Str(%v) != toMerge.Str(%v)", dest.Str, toMerge.Str)
}
}
@@ -0,0 +1,67 @@
package mergo_test
import (
"testing"
"time"
"github.com/imdario/mergo"
)
type structWithoutTimePointer struct {
Created time.Time
}
func TestIssue38Merge(t *testing.T) {
dst := structWithoutTimePointer{
time.Now(),
}
expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
src := structWithoutTimePointer{
expected,
}
if err := mergo.Merge(&dst, src); err != nil {
t.Errorf("Error while merging %s", err)
}
if dst.Created == src.Created {
t.Errorf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created)
}
}
func TestIssue38MergeEmptyStruct(t *testing.T) {
dst := structWithoutTimePointer{}
expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
src := structWithoutTimePointer{
expected,
}
if err := mergo.Merge(&dst, src); err != nil {
t.Errorf("Error while merging %s", err)
}
if dst.Created == src.Created {
t.Errorf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created)
}
}
func TestIssue38MergeWithOverwrite(t *testing.T) {
dst := structWithoutTimePointer{
time.Now(),
}
expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
src := structWithoutTimePointer{
expected,
}
if err := mergo.MergeWithOverwrite(&dst, src); err != nil {
t.Errorf("Error while merging %s", err)
}
if dst.Created != src.Created {
t.Errorf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created)
}
}
@@ -0,0 +1,21 @@
package mergo_test
import (
"testing"
"time"
"github.com/imdario/mergo"
)
type testStruct struct {
time.Duration
}
func TestIssue50Merge(t *testing.T) {
to := testStruct{}
from := testStruct{}
if err := mergo.Merge(&to, from); err != nil {
t.Fail()
}
}
@@ -0,0 +1,113 @@
package mergo_test
import (
"reflect"
"testing"
"time"
"github.com/imdario/mergo"
)
type structWithTime struct {
Birth time.Time
}
type timeTransfomer struct {
overwrite bool
}
func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
if typ == reflect.TypeOf(time.Time{}) {
return func(dst, src reflect.Value) error {
if dst.CanSet() {
if t.overwrite {
isZero := src.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if !result[0].Bool() {
dst.Set(src)
}
} else {
isZero := dst.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if result[0].Bool() {
dst.Set(src)
}
}
}
return nil
}
}
return nil
}
func TestOverwriteZeroSrcTime(t *testing.T) {
now := time.Now()
dst := structWithTime{now}
src := structWithTime{}
if err := mergo.MergeWithOverwrite(&dst, src); err != nil {
t.FailNow()
}
if !dst.Birth.IsZero() {
t.Errorf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now)
}
}
func TestOverwriteZeroSrcTimeWithTransformer(t *testing.T) {
now := time.Now()
dst := structWithTime{now}
src := structWithTime{}
if err := mergo.MergeWithOverwrite(&dst, src, mergo.WithTransformers(timeTransfomer{true})); err != nil {
t.FailNow()
}
if dst.Birth.IsZero() {
t.Errorf("dst should not have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now)
}
}
func TestOverwriteZeroDstTime(t *testing.T) {
now := time.Now()
dst := structWithTime{}
src := structWithTime{now}
if err := mergo.MergeWithOverwrite(&dst, src); err != nil {
t.FailNow()
}
if dst.Birth.IsZero() {
t.Errorf("dst should have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{})
}
}
func TestZeroDstTime(t *testing.T) {
now := time.Now()
dst := structWithTime{}
src := structWithTime{now}
if err := mergo.Merge(&dst, src); err != nil {
t.FailNow()
}
if !dst.Birth.IsZero() {
t.Errorf("dst should not have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{})
}
}
func TestZeroDstTimeWithTransformer(t *testing.T) {
now := time.Now()
dst := structWithTime{}
src := structWithTime{now}
if err := mergo.Merge(&dst, src, mergo.WithTransformers(timeTransfomer{})); err != nil {
t.FailNow()
}
if dst.Birth.IsZero() {
t.Errorf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now)
}
}
@@ -0,0 +1,24 @@
package mergo_test
import (
"reflect"
"testing"
"github.com/imdario/mergo"
)
func TestIssue61MergeNilMap(t *testing.T) {
type T struct {
I map[string][]string
}
t1 := T{}
t2 := T{I: map[string][]string{"hi": {"there"}}}
if err := mergo.Merge(&t1, t2); err != nil {
t.Fail()
}
if !reflect.DeepEqual(t2, T{I: map[string][]string{"hi": {"there"}}}) {
t.FailNow()
}
}
@@ -0,0 +1,46 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type Student struct {
Name string
Books []string
}
type issue64TestData struct {
S1 Student
S2 Student
ExpectedSlice []string
}
func issue64Data() []issue64TestData {
return []issue64TestData{
{Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"a", "B"}},
{Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}},
{Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}},
{Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}},
}
}
func TestIssue64MergeSliceWithOverride(t *testing.T) {
for _, data := range issue64Data() {
err := mergo.Merge(&data.S2, data.S1, mergo.WithOverride)
if err != nil {
t.Errorf("Error while merging %s", err)
}
if len(data.S2.Books) != len(data.ExpectedSlice) {
t.Errorf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice))
}
for i, val := range data.S2.Books {
if val != data.ExpectedSlice[i] {
t.Errorf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val)
}
}
}
}
@@ -0,0 +1,56 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type PrivateSliceTest66 struct {
PublicStrings []string
privateStrings []string
}
func TestPrivateSlice(t *testing.T) {
p1 := PrivateSliceTest66{
PublicStrings: []string{"one", "two", "three"},
privateStrings: []string{"four", "five"},
}
p2 := PrivateSliceTest66{
PublicStrings: []string{"six", "seven"},
}
if err := mergo.Merge(&p1, p2); err != nil {
t.Errorf("Error during the merge: %v", err)
}
if len(p1.PublicStrings) != 3 {
t.Error("3 elements should be in 'PublicStrings' field, when no append")
}
if len(p1.privateStrings) != 2 {
t.Error("2 elements should be in 'privateStrings' field")
}
}
func TestPrivateSliceWithAppendSlice(t *testing.T) {
p1 := PrivateSliceTest66{
PublicStrings: []string{"one", "two", "three"},
privateStrings: []string{"four", "five"},
}
p2 := PrivateSliceTest66{
PublicStrings: []string{"six", "seven"},
}
if err := mergo.Merge(&p1, p2, mergo.WithAppendSlice); err != nil {
t.Errorf("Error during the merge: %v", err)
}
if len(p1.PublicStrings) != 5 {
t.Error("5 elements should be in 'PublicStrings' field")
}
if len(p1.privateStrings) != 2 {
t.Error("2 elements should be in 'privateStrings' field")
}
}
@@ -0,0 +1,22 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type issue83My struct {
Data []int
}
func TestIssue83(t *testing.T) {
dst := issue83My{Data: []int{1, 2, 3}}
new := issue83My{}
if err := mergo.Merge(&dst, new, mergo.WithOverwriteWithEmptyValue); err != nil {
t.Error(err)
}
if len(dst.Data) > 0 {
t.Errorf("expected empty slice, got %v", dst.Data)
}
}
@@ -0,0 +1,90 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type DstStructIssue84 struct {
A int
B int
C int
}
type DstNestedStructIssue84 struct {
A struct {
A int
B int
C int
}
B int
C int
}
func TestIssue84MergeMapWithNilValueToStructWithOverride(t *testing.T) {
p1 := DstStructIssue84{
A: 0, B: 1, C: 2,
}
p2 := map[string]interface{}{
"A": 3, "B": 4, "C": 0,
}
if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil {
t.Errorf("Error during the merge: %v", err)
}
if p1.C != 0 {
t.Error("C field should become '0'")
}
}
func TestIssue84MergeMapWithoutKeyExistsToStructWithOverride(t *testing.T) {
p1 := DstStructIssue84{
A: 0, B: 1, C: 2,
}
p2 := map[string]interface{}{
"A": 3, "B": 4,
}
if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil {
t.Errorf("Error during the merge: %v", err)
}
if p1.C != 2 {
t.Error("C field should be '2'")
}
}
func TestIssue84MergeNestedMapWithNilValueToStructWithOverride(t *testing.T) {
p1 := DstNestedStructIssue84{
A: struct {
A int
B int
C int
}{A: 1, B: 2, C: 0},
B: 0,
C: 2,
}
p2 := map[string]interface{}{
"A": map[string]interface{}{
"A": 0, "B": 0, "C": 5,
}, "B": 4, "C": 0,
}
if err := mergo.Map(&p1, p2, mergo.WithOverride); err != nil {
t.Errorf("Error during the merge: %v", err)
}
if p1.B != 4 {
t.Error("A.C field should become '4'")
}
if p1.A.C != 5 {
t.Error("A.C field should become '5'")
}
if p1.A.B != 0 || p1.A.A != 0 {
t.Error("A.A and A.B field should become '0'")
}
}
@@ -0,0 +1,57 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
func TestIssue89Boolean(t *testing.T) {
type Foo struct {
Bar bool `json:"bar"`
}
src := Foo{Bar: true}
dst := Foo{Bar: false}
if err := mergo.Merge(&dst, src); err != nil {
t.Error(err)
}
if dst.Bar == false {
t.Errorf("expected true, got false")
}
}
func TestIssue89MergeWithEmptyValue(t *testing.T) {
p1 := map[string]interface{}{
"A": 3, "B": "note", "C": true,
}
p2 := map[string]interface{}{
"B": "", "C": false,
}
if err := mergo.Merge(&p1, p2, mergo.WithOverwriteWithEmptyValue); err != nil {
t.Error(err)
}
testCases := []struct {
key string
expected interface{}
}{
{
"A",
3,
},
{
"B",
"",
},
{
"C",
false,
},
}
for _, tC := range testCases {
if p1[tC.key] != tC.expected {
t.Errorf("expected %v in p1[%q], got %v", tC.expected, tC.key, p1[tC.key])
}
}
}
@@ -0,0 +1,37 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
var testDataS = []struct {
S1 Student
S2 Student
ExpectedSlice []string
}{
{Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"1", "a", "B"}},
{Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}},
{Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}},
{Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}},
}
func TestMergeSliceWithOverrideWithAppendSlice(t *testing.T) {
for _, data := range testDataS {
err := mergo.Merge(&data.S2, data.S1, mergo.WithOverride, mergo.WithAppendSlice)
if err != nil {
t.Errorf("Error while merging %s", err)
}
if len(data.S2.Books) != len(data.ExpectedSlice) {
t.Errorf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice))
}
for i, val := range data.S2.Books {
if val != data.ExpectedSlice[i] {
t.Errorf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val)
}
}
}
}
+196
View File
@@ -0,0 +1,196 @@
// Copyright 2014 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
)
func changeInitialCase(s string, mapper func(rune) rune) string {
if s == "" {
return s
}
r, n := utf8.DecodeRuneInString(s)
return string(mapper(r)) + s[n:]
}
func isExported(field reflect.StructField) bool {
r, _ := utf8.DecodeRuneInString(field.Name)
return r >= 'A' && r <= 'Z'
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
//read note 同样是防止循环包含的处理
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return nil
}
}
// Remember, remember...
visited[h] = &visit{addr, typ, seen}
}
//read note 这边只会处理 map 、struct 、ptr
zeroValue := reflect.Value{}
switch dst.Kind() {
case reflect.Map:
dstMap := dst.Interface().(map[string]interface{})
//read note 遍历被merge的结构体的所有Field
for i, n := 0, src.NumField(); i < n; i++ {
srcType := src.Type()
field := srcType.Field(i)
if !isExported(field) {
continue
}
//read note map的key设置成小写开头
fieldName := field.Name
fieldName = changeInitialCase(fieldName, unicode.ToLower)
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
dstMap[fieldName] = src.Field(i).Interface()
}
}
//read note ptr的处理和struct类似,不过这边要转换一个Value值
case reflect.Ptr:
if dst.IsNil() {
v := reflect.New(dst.Type().Elem())
dst.Set(v)
}
dst = dst.Elem()
fallthrough
case reflect.Struct:
srcMap := src.Interface().(map[string]interface{})
//read note 遍历被merge的map
for key := range srcMap {
config.overwriteWithEmptyValue = true
srcValue := srcMap[key]
//read note map key的首字母要大写
fieldName := changeInitialCase(key, unicode.ToUpper)
dstElement := dst.FieldByName(fieldName)
//read note Field不存在的处理
if dstElement == zeroValue {
// We discard it because the field doesn't exist.
continue
}
//read note dst和src结构体Value、kind的处理
srcElement := reflect.ValueOf(srcValue)
dstKind := dstElement.Kind()
srcKind := srcElement.Kind()
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
srcElement = srcElement.Elem()
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
} else if dstKind == reflect.Ptr {
// Can this work? I guess it can't.
if srcKind != reflect.Ptr && srcElement.CanAddr() {
srcPtr := srcElement.Addr()
srcElement = reflect.ValueOf(srcPtr)
srcKind = reflect.Ptr
}
}
if !srcElement.IsValid() {
continue
}
if srcKind == dstKind {
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if srcKind == reflect.Map {
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else {
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
}
}
}
return
}
// Map sets fields' values in dst from src.
// src can be a map with string keys or a struct. dst must be the opposite:
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
// dst must be map[string]interface{}.
// It won't merge unexported (private) fields and will do recursively
// any exported field.
// If dst is a map, keys will be src fields' names in lower camel case.
// Missing key in src that doesn't match a field in dst will be skipped. This
// doesn't apply if dst is a map.
// This is separated method from Merge because it is cleaner and it keeps sane
// semantics: merging equal types, mapping different (restricted) types.
func Map(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, opts...)
}
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: Use Map(…) with WithOverride
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, append(opts, WithOverride)...)
}
func _map(dst, src interface{}, opts ...func(*Config)) error {
//read note dst和src的类型判断
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
return ErrNonPointerAgument
}
var (
vDst, vSrc reflect.Value
err error
)
config := &Config{}
for _, opt := range opts {
opt(config)
}
//read note 处理获取对应值
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
return err
}
// To be friction-less, we redirect equal-type arguments
// to deepMerge. Only because arguments can be anything.
//read note 如果类型相同则直接调用deepMerge即可
if vSrc.Kind() == vDst.Kind() {
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}
//read note map转struct 或者 struct转map,其他情况都是错误的.
switch vSrc.Kind() {
case reflect.Struct:
if vDst.Kind() != reflect.Map {
return ErrExpectedMapAsDestination
}
case reflect.Map:
if vDst.Kind() != reflect.Struct {
return ErrExpectedStructAsDestination
}
default:
return ErrNotSupported
}
//read note 对struct转map和map转struct的处理
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}
@@ -0,0 +1,420 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"fmt"
"reflect"
)
//read note 判断结构体是否有可导出字段
func hasMergeableFields(dst reflect.Value) (exported bool) {
for i, n := 0, dst.NumField(); i < n; i++ {
field := dst.Type().Field(i)
//read note 匿名字段如果是结构体需要判断该结构体中是否有可导出字段
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
exported = exported || hasMergeableFields(dst.Field(i))
} else if isExportedComponent(&field) {
exported = exported || len(field.PkgPath) == 0
}
}
return
}
//read note 判断字段是否可导出,这边通过pkgPath和字段名首字母大小写确定
func isExportedComponent(field *reflect.StructField) bool {
pkgPath := field.PkgPath
//read note 查看一下pkgPath的注释,如果为空字符串表示可导出,否则不可导出
if len(pkgPath) > 0 {
return false
}
//read note 这边也是判断字段是否可导出
c := field.Name[0]
if 'a' <= c && c <= 'z' || c == '_' {
return false
}
return true
}
//read note 配置,这边采用Option的方式进行传参设置,通过Option修改配置的字段
type Config struct {
Overwrite bool //read note 是否覆盖
AppendSlice bool //read note 是否添加到对应数组中
TypeCheck bool //read note 是否进行类型检查
Transformers Transformers //read note 自定义类型转换
overwriteWithEmptyValue bool //read note 是否覆盖空值
overwriteSliceWithEmptyValue bool //read note 是否覆盖空数组
sliceDeepCopy bool //read note 数组中的元素是否进行深度克隆
debug bool
}
type Transformers interface {
Transformer(reflect.Type) func(dst, src reflect.Value) error
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
typeCheck := config.TypeCheck
overwriteWithEmptySrc := config.overwriteWithEmptyValue
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
sliceDeepCopy := config.sliceDeepCopy
//read note 零值返回
if !src.IsValid() {
return
}
//read note 这个地方应该是根据visited判断字段是否被deepMerge
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
//read note 这边是为了避免结构体中和结构体同类型的字段赋值同样的值。如果遇到这种情况会跳过赋值,因为已经赋值过了
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return nil
}
}
// Remember, remember...
//read note 记录已经被merge的字段
visited[h] = &visit{addr, typ, seen}
}
//read note 这个转换方法只有在 结果结构体或结构体字段不为空的情况下才会处理
if config.Transformers != nil && !isEmptyValue(dst) {
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
err = fn(dst, src)
return
}
}
switch dst.Kind() {
case reflect.Struct:
//read note 这边会遍历结构体的字段,判断是否有可导出的字段,如果有,才会进行Field遍历,实现deepMerge
if hasMergeableFields(dst) {
for i, n := 0, dst.NumField(); i < n; i++ {
//read note 对每一个字段进行deepMerge操作
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
return
}
}
} else {
//read note 空值覆盖或者是有值覆盖,这个条件需要仔细阅读一下
if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
dst.Set(src)
}
}
case reflect.Map:
//read note 初始化map
if dst.IsNil() && !src.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
}
//read note 这个地方按道理如果是两个相同的结构体,字段或者结构体的类型不可能不一样,所以这边有可能进去吗?
if src.Kind() != reflect.Map {
if overwrite {
dst.Set(src)
}
return
}
//read note 遍历map的key
for _, key := range src.MapKeys() {
//read note 获取被merge的map的value
srcElement := src.MapIndex(key)
if !srcElement.IsValid() {
continue
}
//read note 获取merge结果的map的value
dstElement := dst.MapIndex(key)
//read note fallthrough表示当前的case处理完之后还会继续往下执行一个case.
switch srcElement.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
if srcElement.IsNil() {
//read note 零值覆盖
if overwrite {
dst.SetMapIndex(key, srcElement)
}
continue
}
fallthrough
default:
if !srcElement.CanInterface() {
continue
}
switch reflect.TypeOf(srcElement.Interface()).Kind() {
case reflect.Struct:
fallthrough
case reflect.Ptr:
fallthrough
case reflect.Map:
//read note map、struct、ptr的处理类似,都需要获取value对应值,然后往下进行deepMerge操作
srcMapElm := srcElement
dstMapElm := dstElement
if srcMapElm.CanInterface() {
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
if dstMapElm.IsValid() {
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
}
}
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
return
}
case reflect.Slice:
srcSlice := reflect.ValueOf(srcElement.Interface())
var dstSlice reflect.Value
//read note 如果结果字段是数组,判断是否空值进行初始化或者获取值
if !dstElement.IsValid() || dstElement.IsNil() {
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
} else {
dstSlice = reflect.ValueOf(dstElement.Interface())
}
//read note 判断直接覆盖的条件,如果满足,会把被merge的数组覆盖到原数组上
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy {
if typeCheck && srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = srcSlice
} else if config.AppendSlice {
//read note 如果标识数组append,则会对数组值进行append操作
if srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
} else if sliceDeepCopy {
//read note 如果数组需要深度克隆,则会遍历数组的所有元素,对每个元素进行deepMerge
i := 0
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
srcElement := srcSlice.Index(i)
dstElement := dstSlice.Index(i)
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
if dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
}
}
//read note 设置map的值
dst.SetMapIndex(key, dstSlice)
}
}
//read note 这边的判断是如果上面的处理进行了赋值,则跳过这个key的处理,否则再往下就是默认设值的处理了
if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
continue
}
//read note 设置转换结果为被转换的字段值,如果声明了覆盖标识(override)
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) {
if dst.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
}
dst.SetMapIndex(key, srcElement)
}
}
case reflect.Slice:
//read note 这边的slice的处理和map中对slice的处理类似,就不多赘述
if !dst.CanSet() {
break
}
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy {
dst.Set(src)
} else if config.AppendSlice {
if src.Type() != dst.Type() {
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
}
dst.Set(reflect.AppendSlice(dst, src))
} else if sliceDeepCopy {
for i := 0; i < src.Len() && i < dst.Len(); i++ {
srcElement := src.Index(i)
dstElement := dst.Index(i)
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
if dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
}
}
case reflect.Ptr:
fallthrough
case reflect.Interface:
//read note ptr和Interface的处理是一样的
//read note 空值的处理,判断是否覆盖.
if isReflectNil(src) {
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
dst.Set(src)
}
break
}
//read note 不是Interface类型,这边dst控制、ptr和结构体类型进行处理
if src.Kind() != reflect.Interface {
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
dst.Set(src)
}
} else if src.Kind() == reflect.Ptr {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
} else if dst.Elem().Type() == src.Type() {
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
return
}
} else {
return ErrDifferentArgumentsTypes
}
break
}
//read note 空值处理
if dst.IsNil() || overwrite {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
dst.Set(src)
}
break
}
//read note 类型相同,进行deepMerge处理
if dst.Elem().Kind() == src.Elem().Kind() {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
break
}
default:
//read note 默认情况下的处理,set值或者是直接赋值
mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc)
if mustSet {
if dst.CanSet() {
dst.Set(src)
} else {
dst = src
}
}
}
return
}
// Merge will fill any empty for value type attributes on the dst struct using corresponding
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
// and dst must be a pointer to struct.
// It won't merge unexported (private) fields and will do recursively any exported field.
func Merge(dst, src interface{}, opts ...func(*Config)) error {
//read note 这边的入参是被复制的结果结构体在前
return merge(dst, src, opts...)
}
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: use Merge(…) with WithOverride
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, append(opts, WithOverride)...)
}
//read note ------------------------------------
// 下面这几个就是Option进行设置Config的函数处理
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
func WithTransformers(transformers Transformers) func(*Config) {
return func(config *Config) {
config.Transformers = transformers
}
}
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
func WithOverride(config *Config) {
config.Overwrite = true
}
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
func WithOverwriteWithEmptyValue(config *Config) {
config.Overwrite = true
config.overwriteWithEmptyValue = true
}
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
func WithOverrideEmptySlice(config *Config) {
config.overwriteSliceWithEmptyValue = true
}
// WithAppendSlice will make merge append slices instead of overwriting it.
func WithAppendSlice(config *Config) {
config.AppendSlice = true
}
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
func WithTypeCheck(config *Config) {
config.TypeCheck = true
}
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
func WithSliceDeepCopy(config *Config) {
config.sliceDeepCopy = true
config.Overwrite = true
}
//read note ------------------------------------
func merge(dst, src interface{}, opts ...func(*Config)) error {
//read note 判断指针类型和结构体类型是否正确
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
return ErrNonPointerAgument
}
var (
vDst, vSrc reflect.Value
err error
)
config := &Config{}
//read note optional设置config
for _, opt := range opts {
opt(config)
}
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
return err
}
//read note 被merge的两个结构体类型必须一样,否则不能进行merge
if vDst.Type() != vSrc.Type() {
return ErrDifferentArgumentsTypes
}
//read note 进行deepMerge
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}
// IsReflectNil is the reflect value provided nil
func isReflectNil(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return v.IsNil()
default:
return false
}
}
@@ -0,0 +1,88 @@
package mergo_test
import (
"reflect"
"testing"
"github.com/imdario/mergo"
)
type transformer struct {
m map[reflect.Type]func(dst, src reflect.Value) error
}
func (s *transformer) Transformer(t reflect.Type) func(dst, src reflect.Value) error {
if fn, ok := s.m[t]; ok {
return fn
}
return nil
}
type foo struct {
s string
Bar *bar
}
type bar struct {
i int
s map[string]string
}
func TestMergeWithTransformerNilStruct(t *testing.T) {
a := foo{s: "foo"}
b := foo{Bar: &bar{i: 2, s: map[string]string{"foo": "bar"}}}
if err := mergo.Merge(&a, &b, mergo.WithOverride, mergo.WithTransformers(&transformer{
m: map[reflect.Type]func(dst, src reflect.Value) error{
reflect.TypeOf(&bar{}): func(dst, src reflect.Value) error {
// Do sthg with Elem
t.Log(dst.Elem().FieldByName("i"))
t.Log(src.Elem())
return nil
},
},
})); err != nil {
t.Error(err)
}
if a.s != "foo" {
t.Errorf("b not merged in properly: a.s.Value(%s) != expected(%s)", a.s, "foo")
}
if a.Bar == nil {
t.Errorf("b not merged in properly: a.Bar shouldn't be nil")
}
}
func TestMergeNonPointer(t *testing.T) {
dst := bar{
i: 1,
}
src := bar{
i: 2,
s: map[string]string{
"a": "1",
},
}
want := mergo.ErrNonPointerAgument
if got := mergo.Merge(dst, src); got != want {
t.Errorf("want: %s, got: %s", want, got)
}
}
func TestMapNonPointer(t *testing.T) {
dst := make(map[string]bar)
src := map[string]bar{
"a": {
i: 2,
s: map[string]string{
"a": "1",
},
},
}
want := mergo.ErrNonPointerAgument
if got := mergo.Merge(dst, src); got != want {
t.Errorf("want: %s, got: %s", want, got)
}
}
@@ -0,0 +1,79 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"errors"
"reflect"
)
// Errors reported by Mergo when it finds invalid arguments.
var (
ErrNilArguments = errors.New("src and dst must not be nil")
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
ErrNotSupported = errors.New("only structs and maps are supported")
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
ErrNonPointerAgument = errors.New("dst must be a pointer")
)
// During deepMerge, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited are stored in a map indexed by 17 * a1 + a2;
type visit struct {
ptr uintptr
typ reflect.Type
next *visit
}
// From src/pkg/encoding/json/encode.go.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if v.IsNil() {
return true
}
return isEmptyValue(v.Elem())
case reflect.Func:
return v.IsNil()
case reflect.Invalid:
return true
}
return false
}
//read note 处理返回dst和src的对应值
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
if dst == nil || src == nil {
err = ErrNilArguments
return
}
vDst = reflect.ValueOf(dst).Elem()
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
err = ErrNotSupported
return
}
vSrc = reflect.ValueOf(src)
// We check if vSrc is a pointer to dereference it.
if vSrc.Kind() == reflect.Ptr {
vSrc = vSrc.Elem()
}
return
}
@@ -0,0 +1,940 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mergo_test
import (
"io/ioutil"
"reflect"
"strings"
"testing"
"time"
"github.com/imdario/mergo"
"gopkg.in/yaml.v2"
)
type simpleTest struct {
Value int
}
type complexTest struct {
St simpleTest
sz int
ID string
}
type mapTest struct {
M map[int]int
}
type ifcTest struct {
I interface{}
}
type moreComplextText struct {
Ct complexTest
St simpleTest
Nt simpleTest
}
type pointerTest struct {
C *simpleTest
}
type sliceTest struct {
S []int
}
func TestKb(t *testing.T) {
type testStruct struct {
Name string
KeyValue map[string]interface{}
}
akv := make(map[string]interface{})
akv["Key1"] = "not value 1"
akv["Key2"] = "value2"
a := testStruct{}
a.Name = "A"
a.KeyValue = akv
bkv := make(map[string]interface{})
bkv["Key1"] = "value1"
bkv["Key3"] = "value3"
b := testStruct{}
b.Name = "B"
b.KeyValue = bkv
ekv := make(map[string]interface{})
ekv["Key1"] = "value1"
ekv["Key2"] = "value2"
ekv["Key3"] = "value3"
expected := testStruct{}
expected.Name = "B"
expected.KeyValue = ekv
if err := mergo.Merge(&b, a); err != nil {
t.Error(err)
}
if !reflect.DeepEqual(b, expected) {
t.Errorf("Actual: %#v did not match \nExpected: %#v", b, expected)
}
}
func TestNil(t *testing.T) {
if err := mergo.Merge(nil, nil); err != mergo.ErrNilArguments {
t.Fail()
}
}
func TestDifferentTypes(t *testing.T) {
a := simpleTest{42}
b := 42
if err := mergo.Merge(&a, b); err != mergo.ErrDifferentArgumentsTypes {
t.Fail()
}
}
func TestSimpleStruct(t *testing.T) {
a := simpleTest{}
b := simpleTest{42}
if err := mergo.Merge(&a, b); err != nil {
t.FailNow()
}
if a.Value != 42 {
t.Errorf("b not merged in properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value)
}
if !reflect.DeepEqual(a, b) {
t.FailNow()
}
}
func TestComplexStruct(t *testing.T) {
a := complexTest{}
a.ID = "athing"
b := complexTest{simpleTest{42}, 1, "bthing"}
if err := mergo.Merge(&a, b); err != nil {
t.FailNow()
}
if a.St.Value != 42 {
t.Errorf("b not merged in properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value)
}
if a.sz == 1 {
t.Errorf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz)
}
if a.ID == b.ID {
t.Errorf("a's field ID merged unexpectedly: a.ID(%s) == b.ID(%s)", a.ID, b.ID)
}
}
func TestComplexStructWithOverwrite(t *testing.T) {
a := complexTest{simpleTest{1}, 1, "do-not-overwrite-with-empty-value"}
b := complexTest{simpleTest{42}, 2, ""}
expect := complexTest{simpleTest{42}, 1, "do-not-overwrite-with-empty-value"}
if err := mergo.MergeWithOverwrite(&a, b); err != nil {
t.FailNow()
}
if !reflect.DeepEqual(a, expect) {
t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", a, expect)
}
}
func TestPointerStruct(t *testing.T) {
s1 := simpleTest{}
s2 := simpleTest{19}
a := pointerTest{&s1}
b := pointerTest{&s2}
if err := mergo.Merge(&a, b); err != nil {
t.FailNow()
}
if a.C.Value != b.C.Value {
t.Errorf("b not merged in properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value)
}
}
type embeddingStruct struct {
embeddedStruct
}
type embeddedStruct struct {
A string
}
func TestEmbeddedStruct(t *testing.T) {
tests := []struct {
src embeddingStruct
dst embeddingStruct
expected embeddingStruct
}{
{
src: embeddingStruct{
embeddedStruct{"foo"},
},
dst: embeddingStruct{
embeddedStruct{""},
},
expected: embeddingStruct{
embeddedStruct{"foo"},
},
},
{
src: embeddingStruct{
embeddedStruct{""},
},
dst: embeddingStruct{
embeddedStruct{"bar"},
},
expected: embeddingStruct{
embeddedStruct{"bar"},
},
},
{
src: embeddingStruct{
embeddedStruct{"foo"},
},
dst: embeddingStruct{
embeddedStruct{"bar"},
},
expected: embeddingStruct{
embeddedStruct{"bar"},
},
},
}
for _, test := range tests {
err := mergo.Merge(&test.dst, test.src)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
if !reflect.DeepEqual(test.dst, test.expected) {
t.Errorf("unexpected output\nexpected:\n%+v\nsaw:\n%+v\n", test.expected, test.dst)
}
}
}
func TestPointerStructNil(t *testing.T) {
a := pointerTest{nil}
b := pointerTest{&simpleTest{19}}
if err := mergo.Merge(&a, b); err != nil {
t.FailNow()
}
if a.C.Value != b.C.Value {
t.Errorf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value)
}
}
func testSlice(t *testing.T, a []int, b []int, e []int, opts ...func(*mergo.Config)) {
t.Helper()
bc := b
sa := sliceTest{a}
sb := sliceTest{b}
if err := mergo.Merge(&sa, sb, opts...); err != nil {
t.FailNow()
}
if !reflect.DeepEqual(sb.S, bc) {
t.Errorf("Source slice was modified %d != %d", sb.S, bc)
}
if !reflect.DeepEqual(sa.S, e) {
t.Errorf("b not merged in a proper way %d != %d", sa.S, e)
}
ma := map[string][]int{"S": a}
mb := map[string][]int{"S": b}
if err := mergo.Merge(&ma, mb, opts...); err != nil {
t.FailNow()
}
if !reflect.DeepEqual(mb["S"], bc) {
t.Errorf("map value: Source slice was modified %d != %d", mb["S"], bc)
}
if !reflect.DeepEqual(ma["S"], e) {
t.Errorf("map value: b not merged in a proper way %d != %d", ma["S"], e)
}
if a == nil {
// test case with missing dst key
ma := map[string][]int{}
mb := map[string][]int{"S": b}
if err := mergo.Merge(&ma, mb); err != nil {
t.FailNow()
}
if !reflect.DeepEqual(mb["S"], bc) {
t.Errorf("missing dst key: Source slice was modified %d != %d", mb["S"], bc)
}
if !reflect.DeepEqual(ma["S"], e) {
t.Errorf("missing dst key: b not merged in a proper way %d != %d", ma["S"], e)
}
}
if b == nil {
// test case with missing src key
ma := map[string][]int{"S": a}
mb := map[string][]int{}
if err := mergo.Merge(&ma, mb); err != nil {
t.FailNow()
}
if !reflect.DeepEqual(mb["S"], bc) {
t.Errorf("missing src key: Source slice was modified %d != %d", mb["S"], bc)
}
if !reflect.DeepEqual(ma["S"], e) {
t.Errorf("missing src key: b not merged in a proper way %d != %d", ma["S"], e)
}
}
}
func TestSlice(t *testing.T) {
testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3})
testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3})
testSlice(t, []int{1}, []int{2, 3}, []int{1})
testSlice(t, []int{1}, []int{}, []int{1})
testSlice(t, []int{1}, nil, []int{1})
testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice)
testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice)
testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice)
testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, mergo.WithAppendSlice, mergo.WithOverride)
testSlice(t, []int{1}, []int{}, []int{1}, mergo.WithAppendSlice)
testSlice(t, []int{1}, nil, []int{1}, mergo.WithAppendSlice)
}
func TestEmptyMaps(t *testing.T) {
a := mapTest{}
b := mapTest{
map[int]int{},
}
if err := mergo.Merge(&a, b); err != nil {
t.Fail()
}
if !reflect.DeepEqual(a, b) {
t.FailNow()
}
}
func TestEmptyToEmptyMaps(t *testing.T) {
a := mapTest{}
b := mapTest{}
if err := mergo.Merge(&a, b); err != nil {
t.Fail()
}
if !reflect.DeepEqual(a, b) {
t.FailNow()
}
}
func TestEmptyToNotEmptyMaps(t *testing.T) {
a := mapTest{map[int]int{
1: 2,
3: 4,
}}
aa := mapTest{map[int]int{
1: 2,
3: 4,
}}
b := mapTest{
map[int]int{},
}
if err := mergo.Merge(&a, b); err != nil {
t.Fail()
}
if !reflect.DeepEqual(a, aa) {
t.FailNow()
}
}
func TestMapsWithOverwrite(t *testing.T) {
m := map[string]simpleTest{
"a": {}, // overwritten by 16
"b": {42}, // overwritten by 0, as map Value is not addressable and it doesn't check for b is set or not set in `n`
"c": {13}, // overwritten by 12
"d": {61},
}
n := map[string]simpleTest{
"a": {16},
"b": {},
"c": {12},
"e": {14},
}
expect := map[string]simpleTest{
"a": {16},
"b": {},
"c": {12},
"d": {61},
"e": {14},
}
if err := mergo.MergeWithOverwrite(&m, n); err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(m, expect) {
t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect)
}
}
func TestMapWithEmbeddedStructPointer(t *testing.T) {
m := map[string]*simpleTest{
"a": {}, // overwritten by 16
"b": {42}, // not overwritten by empty value
"c": {13}, // overwritten by 12
"d": {61},
}
n := map[string]*simpleTest{
"a": {16},
"b": {},
"c": {12},
"e": {14},
}
expect := map[string]*simpleTest{
"a": {16},
"b": {42},
"c": {12},
"d": {61},
"e": {14},
}
if err := mergo.Merge(&m, n, mergo.WithOverride); err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(m, expect) {
t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect)
}
}
func TestMergeUsingStructAndMap(t *testing.T) {
type multiPtr struct {
Text string
Number int
}
type final struct {
Msg1 string
Msg2 string
}
type params struct {
Name string
Multi *multiPtr
Final *final
}
type config struct {
Foo string
Bar string
Params *params
}
cases := []struct {
name string
overwrite bool
changes *config
target *config
output *config
}{
{
name: "Should overwrite values in target for non-nil values in source",
overwrite: true,
changes: &config{
Bar: "from changes",
Params: &params{
Final: &final{
Msg1: "from changes",
Msg2: "from changes",
},
},
},
target: &config{
Foo: "from target",
Params: &params{
Name: "from target",
Multi: &multiPtr{
Text: "from target",
Number: 5,
},
Final: &final{
Msg1: "from target",
Msg2: "",
},
},
},
output: &config{
Foo: "from target",
Bar: "from changes",
Params: &params{
Name: "from target",
Multi: &multiPtr{
Text: "from target",
Number: 5,
},
Final: &final{
Msg1: "from changes",
Msg2: "from changes",
},
},
},
},
{
name: "Should not overwrite values in target for non-nil values in source",
overwrite: false,
changes: &config{
Bar: "from changes",
Params: &params{
Final: &final{
Msg1: "from changes",
Msg2: "from changes",
},
},
},
target: &config{
Foo: "from target",
Params: &params{
Name: "from target",
Multi: &multiPtr{
Text: "from target",
Number: 5,
},
Final: &final{
Msg1: "from target",
Msg2: "",
},
},
},
output: &config{
Foo: "from target",
Bar: "from changes",
Params: &params{
Name: "from target",
Multi: &multiPtr{
Text: "from target",
Number: 5,
},
Final: &final{
Msg1: "from target",
Msg2: "from changes",
},
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var err error
if tc.overwrite {
err = mergo.Merge(tc.target, *tc.changes, mergo.WithOverride)
} else {
err = mergo.Merge(tc.target, *tc.changes)
}
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(tc.target, tc.output) {
t.Errorf("Test failed:\ngot :\n%+v\n\nwant :\n%+v\n\n", tc.target.Params, tc.output.Params)
}
})
}
}
func TestMaps(t *testing.T) {
m := map[string]simpleTest{
"a": {},
"b": {42},
"c": {13},
"d": {61},
}
n := map[string]simpleTest{
"a": {16},
"b": {},
"c": {12},
"e": {14},
}
expect := map[string]simpleTest{
"a": {0},
"b": {42},
"c": {13},
"d": {61},
"e": {14},
}
if err := mergo.Merge(&m, n); err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(m, expect) {
t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect)
}
if m["a"].Value != 0 {
t.Errorf(`n merged in m because I solved non-addressable map values TODO: m["a"].Value(%d) != n["a"].Value(%d)`, m["a"].Value, n["a"].Value)
}
if m["b"].Value != 42 {
t.Errorf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value)
}
if m["c"].Value != 13 {
t.Errorf(`n overwritten in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value)
}
}
func TestMapsWithNilPointer(t *testing.T) {
m := map[string]*simpleTest{
"a": nil,
"b": nil,
}
n := map[string]*simpleTest{
"b": nil,
"c": nil,
}
expect := map[string]*simpleTest{
"a": nil,
"b": nil,
"c": nil,
}
if err := mergo.Merge(&m, n, mergo.WithOverride); err != nil {
t.Errorf(err.Error())
}
if !reflect.DeepEqual(m, expect) {
t.Errorf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect)
}
}
func TestYAMLMaps(t *testing.T) {
thing := loadYAML("testdata/thing.yml")
license := loadYAML("testdata/license.yml")
ft := thing["fields"].(map[interface{}]interface{})
fl := license["fields"].(map[interface{}]interface{})
// license has one extra field (site) and another already existing in thing (author) that Mergo won't override.
expectedLength := len(ft) + len(fl) - 1
if err := mergo.Merge(&license, thing); err != nil {
t.Error(err.Error())
}
currentLength := len(license["fields"].(map[interface{}]interface{}))
if currentLength != expectedLength {
t.Errorf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength)
}
fields := license["fields"].(map[interface{}]interface{})
if _, ok := fields["id"]; !ok {
t.Errorf(`thing not merged in license properly, license must have a new id field from thing`)
}
}
func TestTwoPointerValues(t *testing.T) {
a := &simpleTest{}
b := &simpleTest{42}
if err := mergo.Merge(a, b); err != nil {
t.Errorf(`Boom. You crossed the streams: %s`, err)
}
}
func TestMap(t *testing.T) {
a := complexTest{}
a.ID = "athing"
c := moreComplextText{a, simpleTest{}, simpleTest{}}
b := map[string]interface{}{
"ct": map[string]interface{}{
"st": map[string]interface{}{
"value": 42,
},
"sz": 1,
"id": "bthing",
},
"st": &simpleTest{144}, // Mapping a reference
"zt": simpleTest{299}, // Mapping a missing field (zt doesn't exist)
"nt": simpleTest{3},
}
if err := mergo.Map(&c, b); err != nil {
t.FailNow()
}
m := b["ct"].(map[string]interface{})
n := m["st"].(map[string]interface{})
o := b["st"].(*simpleTest)
p := b["nt"].(simpleTest)
if c.Ct.St.Value != 42 {
t.Errorf("b not merged in properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"])
}
if c.St.Value != 144 {
t.Errorf("b not merged in properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value)
}
if c.Nt.Value != 3 {
t.Errorf("b not merged in properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value)
}
if c.Ct.sz == 1 {
t.Errorf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"])
}
if c.Ct.ID == m["id"] {
t.Errorf("a's field ID merged unexpectedly: c.Ct.ID(%s) == b.Ct.ID(%s)", c.Ct.ID, m["id"])
}
}
func TestSimpleMap(t *testing.T) {
a := simpleTest{}
b := map[string]interface{}{
"value": 42,
}
if err := mergo.Map(&a, b); err != nil {
t.FailNow()
}
if a.Value != 42 {
t.Errorf("b not merged in properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"])
}
}
func TestIfcMap(t *testing.T) {
a := ifcTest{}
b := ifcTest{42}
if err := mergo.Map(&a, b); err != nil {
t.FailNow()
}
if a.I != 42 {
t.Errorf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I)
}
if !reflect.DeepEqual(a, b) {
t.FailNow()
}
}
func TestIfcMapNoOverwrite(t *testing.T) {
a := ifcTest{13}
b := ifcTest{42}
if err := mergo.Map(&a, b); err != nil {
t.FailNow()
}
if a.I != 13 {
t.Errorf("a not left alone: a.I(%d) == b.I(%d)", a.I, b.I)
}
}
func TestIfcMapWithOverwrite(t *testing.T) {
a := ifcTest{13}
b := ifcTest{42}
if err := mergo.MapWithOverwrite(&a, b); err != nil {
t.FailNow()
}
if a.I != 42 {
t.Errorf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I)
}
if !reflect.DeepEqual(a, b) {
t.FailNow()
}
}
type pointerMapTest struct {
A int
hidden int
B *simpleTest
}
func TestBackAndForth(t *testing.T) {
pt := pointerMapTest{42, 1, &simpleTest{66}}
m := make(map[string]interface{})
if err := mergo.Map(&m, pt); err != nil {
t.FailNow()
}
var (
v interface{}
ok bool
)
if v, ok = m["a"]; v.(int) != pt.A || !ok {
t.Errorf("pt not merged in properly: m[`a`](%d) != pt.A(%d)", v, pt.A)
}
if v, ok = m["b"]; !ok {
t.Errorf("pt not merged in properly: B is missing in m")
}
var st *simpleTest
if st = v.(*simpleTest); st.Value != 66 {
t.Errorf("something went wrong while mapping pt on m, B wasn't copied")
}
bpt := pointerMapTest{}
if err := mergo.Map(&bpt, m); err != nil {
t.Error(err)
}
if bpt.A != pt.A {
t.Errorf("pt not merged in properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A)
}
if bpt.hidden == pt.hidden {
t.Errorf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden)
}
if bpt.B.Value != pt.B.Value {
t.Errorf("pt not merged in properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value)
}
}
func TestEmbeddedPointerUnpacking(t *testing.T) {
tests := []struct{ input pointerMapTest }{
{pointerMapTest{42, 1, nil}},
{pointerMapTest{42, 1, &simpleTest{66}}},
}
newValue := 77
m := map[string]interface{}{
"b": map[string]interface{}{
"value": newValue,
},
}
for _, test := range tests {
pt := test.input
if err := mergo.MapWithOverwrite(&pt, m); err != nil {
t.FailNow()
}
if pt.B.Value != newValue {
t.Errorf("pt not mapped properly: pt.A.Value(%d) != m[`b`][`value`](%d)", pt.B.Value, newValue)
}
}
}
type structWithTimePointer struct {
Birth *time.Time
}
func TestTime(t *testing.T) {
now := time.Now()
dataStruct := structWithTimePointer{
Birth: &now,
}
dataMap := map[string]interface{}{
"Birth": &now,
}
b := structWithTimePointer{}
if err := mergo.Merge(&b, dataStruct); err != nil {
t.FailNow()
}
if b.Birth.IsZero() {
t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth)
}
if b.Birth != dataStruct.Birth {
t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth)
}
b = structWithTimePointer{}
if err := mergo.Map(&b, dataMap); err != nil {
t.FailNow()
}
if b.Birth.IsZero() {
t.Errorf("time.Time not merged in properly: b.Birth(%v) != dataMap['Birth'](%v)", b.Birth, dataMap["Birth"])
}
}
type simpleNested struct {
A int
}
type structWithNestedPtrValueMap struct {
NestedPtrValue map[string]*simpleNested
}
func TestNestedPtrValueInMap(t *testing.T) {
src := &structWithNestedPtrValueMap{
NestedPtrValue: map[string]*simpleNested{
"x": {
A: 1,
},
},
}
dst := &structWithNestedPtrValueMap{
NestedPtrValue: map[string]*simpleNested{
"x": {},
},
}
if err := mergo.Map(dst, src); err != nil {
t.FailNow()
}
if dst.NestedPtrValue["x"].A == 0 {
t.Errorf("Nested Ptr value not merged in properly: dst.NestedPtrValue[\"x\"].A(%v) != src.NestedPtrValue[\"x\"].A(%v)", dst.NestedPtrValue["x"].A, src.NestedPtrValue["x"].A)
}
}
func loadYAML(path string) (m map[string]interface{}) {
m = make(map[string]interface{})
raw, _ := ioutil.ReadFile(path)
_ = yaml.Unmarshal(raw, &m)
return
}
type structWithMap struct {
m map[string]structWithUnexportedProperty
}
type structWithUnexportedProperty struct {
s string
}
func TestUnexportedProperty(t *testing.T) {
a := structWithMap{map[string]structWithUnexportedProperty{
"key": {"hello"},
}}
b := structWithMap{map[string]structWithUnexportedProperty{
"key": {"hi"},
}}
defer func() {
if r := recover(); r != nil {
t.Errorf("Should not have panicked")
}
}()
mergo.Merge(&a, b)
}
type structWithBoolPointer struct {
C *bool
}
func TestBooleanPointer(t *testing.T) {
bt, bf := true, false
src := structWithBoolPointer{
&bt,
}
dst := structWithBoolPointer{
&bf,
}
if err := mergo.Merge(&dst, src); err != nil {
t.FailNow()
}
if dst.C == src.C {
t.Errorf("dst.C should be a different pointer than src.C")
}
if *dst.C != *src.C {
t.Errorf("dst.C should be true")
}
}
func TestMergeMapWithInnerSliceOfDifferentType(t *testing.T) {
testCases := []struct {
name string
options []func(*mergo.Config)
err string
}{
{
"With override and append slice",
[]func(*mergo.Config){mergo.WithOverride, mergo.WithAppendSlice},
"cannot append two slices with different type",
},
{
"With override and type check",
[]func(*mergo.Config){mergo.WithOverride, mergo.WithTypeCheck},
"cannot override two slices with different type",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
src := map[string]interface{}{
"foo": []string{"a", "b"},
}
dst := map[string]interface{}{
"foo": []int{1, 2},
}
if err := mergo.Merge(&src, &dst, tc.options...); err == nil || !strings.Contains(err.Error(), tc.err) {
t.Errorf("expected %q, got %q", tc.err, err)
}
})
}
}
func TestMergeSlicesIsNotSupported(t *testing.T) {
src := []string{"a", "b"}
dst := []int{1, 2}
if err := mergo.Merge(&src, &dst, mergo.WithOverride, mergo.WithAppendSlice); err != mergo.ErrNotSupported {
t.Errorf("expected %q, got %q", mergo.ErrNotSupported, err)
}
}
@@ -0,0 +1,20 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type mapInterface map[string]interface{}
func TestMergeMapsEmptyString(t *testing.T) {
a := mapInterface{"s": ""}
b := mapInterface{"s": "foo"}
if err := mergo.Merge(&a, b); err != nil {
t.Error(err)
}
if a["s"] != "foo" {
t.Errorf("b not merged in properly: a.s.Value(%s) != expected(%s)", a["s"], "foo")
}
}
@@ -0,0 +1,44 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
func TestMapInterfaceWithMultipleLayer(t *testing.T) {
m1 := map[string]interface{}{
"k1": map[string]interface{}{
"k1.1": "v1",
},
}
m2 := map[string]interface{}{
"k1": map[string]interface{}{
"k1.1": "v2",
"k1.2": "v3",
},
}
if err := mergo.Map(&m1, m2, mergo.WithOverride); err != nil {
t.Errorf("Error merging: %v", err)
}
// Check overwrite of sub map works
expected := "v2"
actual := m1["k1"].(map[string]interface{})["k1.1"].(string)
if actual != expected {
t.Errorf("Expected %v but got %v",
expected,
actual)
}
// Check new key is merged
expected = "v3"
actual = m1["k1"].(map[string]interface{})["k1.2"].(string)
if actual != expected {
t.Errorf("Expected %v but got %v",
expected,
actual)
}
}
@@ -0,0 +1,4 @@
import: ../../../../fossene/db/schema/thing.yml
fields:
site: string
author: root
@@ -0,0 +1,6 @@
fields:
id: int
name: string
parent: ref "datu:thing"
status: enum(draft, public, private)
author: updater
@@ -0,0 +1,92 @@
package mergo_test
import (
"testing"
"github.com/imdario/mergo"
)
type inner struct {
A int
}
type outer struct {
inner
B int
}
func TestV039Issue139(t *testing.T) {
dst := outer{
inner: inner{A: 1},
B: 2,
}
src := outer{
inner: inner{A: 10},
B: 20,
}
err := mergo.MergeWithOverwrite(&dst, src)
if err != nil {
panic(err.Error())
}
if dst.inner.A == 1 {
t.Errorf("expected %d, got %d", src.inner.A, dst.inner.A)
}
}
func TestV039Issue152(t *testing.T) {
dst := map[string]interface{}{
"properties": map[string]interface{}{
"field1": map[string]interface{}{
"type": "text",
},
"field2": "ohai",
},
}
src := map[string]interface{}{
"properties": map[string]interface{}{
"field1": "wrong",
},
}
if err := mergo.Map(&dst, src, mergo.WithOverride); err != nil {
t.Error(err)
}
}
type issue146Foo struct {
A string
B map[string]issue146Bar
}
type issue146Bar struct {
C *string
D *string
}
func TestV039Issue146(t *testing.T) {
var (
s1 = "asd"
s2 = "sdf"
)
dst := issue146Foo{
A: "two",
B: map[string]issue146Bar{
"foo": {
C: &s1,
},
},
}
src := issue146Foo{
A: "one",
B: map[string]issue146Bar{
"foo": {
D: &s2,
},
},
}
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
t.Error(err)
}
if dst.B["foo"].D == nil {
t.Errorf("expected %v, got nil", &s2)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

@@ -0,0 +1,5 @@
我的分析文章可参考:[Go源码解析:validator.v8](https://blog.csdn.net/qq_34326321/article/details/111030128)
流程图: ![image](2、结构体缓存获取.jpg)
流程图地址:[地址](https://www.processon.com/view/link/5fd6dabb63768906e6db0d25)
@@ -0,0 +1,29 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
*.test
*.out
*.txt
cover.html
README.html
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Dean Karn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,366 @@
Package validator
================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png">[![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-8.18.2-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v8&service=github)](https://coveralls.io/github/go-playground/validator?branch=v8)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
[![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v8?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8)
![License](https://img.shields.io/dub/l/vibe-d.svg)
Package validator implements value validations for structs and individual fields based on tags.
It has the following **unique** features:
- Cross Field and Cross Struct validations by using validation tags or custom validators.
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
- Handles type interface by determining it's underlying type prior to validation.
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError
Installation
------------
Use go get.
go get gopkg.in/go-playground/validator.v8
or to update
go get -u gopkg.in/go-playground/validator.v8
Then import the validator package into your own code.
import "gopkg.in/go-playground/validator.v8"
Error Return Value
-------
Validation functions return type error
They return type error to avoid the issue discussed in the following, where err is always != nil:
* http://stackoverflow.com/a/29138676/3158232
* https://github.com/go-playground/validator/issues/134
validator only returns nil or ValidationErrors as type error; so in you code all you need to do
is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors
like so:
```go
err := validate.Struct(mystruct)
validationErrors := err.(validator.ValidationErrors)
```
Usage and documentation
------
Please see http://godoc.org/gopkg.in/go-playground/validator.v8 for detailed usage docs.
##### Examples:
Struct & Field validation
```go
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v8"
)
// User contains user information
type User struct {
FirstName string `validate:"required"`
LastName string `validate:"required"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
}
var validate *validator.Validate
func main() {
config := &validator.Config{TagName: "validate"}
validate = validator.New(config)
validateStruct()
validateField()
}
func validateStruct() {
address := &Address{
Street: "Eavesdown Docks",
Planet: "Persphone",
Phone: "none",
}
user := &User{
FirstName: "Badger",
LastName: "Smith",
Age: 135,
Email: "Badger.Smith@gmail.com",
FavouriteColor: "#000",
Addresses: []*Address{address},
}
// returns nil or ValidationErrors ( map[string]*FieldError )
errs := validate.Struct(user)
if errs != nil {
fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
// Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
err := errs.(validator.ValidationErrors)["User.Addresses[0].City"]
fmt.Println(err.Field) // output: City
fmt.Println(err.Tag) // output: required
fmt.Println(err.Kind) // output: string
fmt.Println(err.Type) // output: string
fmt.Println(err.Param) // output:
fmt.Println(err.Value) // output:
// from here you can create your own error messages in whatever language you wish
return
}
// save user to database
}
func validateField() {
myEmail := "joeybloggs.gmail.com"
errs := validate.Field(myEmail, "required,email")
if errs != nil {
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
return
}
// email ok, move on
}
```
Custom Field Type
```go
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"gopkg.in/go-playground/validator.v8"
)
// DbBackedUser User struct
type DbBackedUser struct {
Name sql.NullString `validate:"required"`
Age sql.NullInt64 `validate:"required"`
}
func main() {
config := &validator.Config{TagName: "validate"}
validate := validator.New(config)
// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
errs := validate.Struct(x)
if len(errs.(validator.ValidationErrors)) > 0 {
fmt.Printf("Errs:\n%+v\n", errs)
}
}
// ValidateValuer implements validator.CustomTypeFunc
func ValidateValuer(field reflect.Value) interface{} {
if valuer, ok := field.Interface().(driver.Valuer); ok {
val, err := valuer.Value()
if err == nil {
return val
}
// handle the error how you want
}
return nil
}
```
Struct Level Validation
```go
package main
import (
"fmt"
"reflect"
"gopkg.in/go-playground/validator.v8"
)
// User contains user information
type User struct {
FirstName string `json:"fname"`
LastName string `json:"lname"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
}
var validate *validator.Validate
func main() {
config := &validator.Config{TagName: "validate"}
validate = validator.New(config)
validate.RegisterStructValidation(UserStructLevelValidation, User{})
validateStruct()
}
// UserStructLevelValidation contains custom struct level validations that don't always
// make sense at the field validation level. For Example this function validates that either
// FirstName or LastName exist; could have done that with a custom field validation but then
// would have had to add it to both fields duplicating the logic + overhead, this way it's
// only validated once.
//
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
// hooks right into validator and you can combine with validation tags and still have a
// common error output format.
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
user := structLevel.CurrentStruct.Interface().(User)
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
}
// plus can to more, even with different tag than "fnameorlname"
}
func validateStruct() {
address := &Address{
Street: "Eavesdown Docks",
Planet: "Persphone",
Phone: "none",
City: "Unknown",
}
user := &User{
FirstName: "",
LastName: "",
Age: 45,
Email: "Badger.Smith@gmail.com",
FavouriteColor: "#000",
Addresses: []*Address{address},
}
// returns nil or ValidationErrors ( map[string]*FieldError )
errs := validate.Struct(user)
if errs != nil {
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
err := errs.(validator.ValidationErrors)["User.FirstName"]
fmt.Println(err.Field) // output: FirstName
fmt.Println(err.Tag) // output: fnameorlname
fmt.Println(err.Kind) // output: string
fmt.Println(err.Type) // output: string
fmt.Println(err.Param) // output:
fmt.Println(err.Value) // output:
// from here you can create your own error messages in whatever language you wish
return
}
// save user to database
}
```
Benchmarks
------
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.3 darwin/amd64
```go
PASS
BenchmarkFieldSuccess-8 20000000 118 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-8 2000000 758 ns/op 432 B/op 4 allocs/op
BenchmarkFieldDiveSuccess-8 500000 2471 ns/op 464 B/op 28 allocs/op
BenchmarkFieldDiveFailure-8 500000 3172 ns/op 896 B/op 32 allocs/op
BenchmarkFieldCustomTypeSuccess-8 5000000 300 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-8 2000000 775 ns/op 432 B/op 4 allocs/op
BenchmarkFieldOrTagSuccess-8 1000000 1122 ns/op 4 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-8 1000000 1167 ns/op 448 B/op 6 allocs/op
BenchmarkStructLevelValidationSuccess-8 3000000 548 ns/op 160 B/op 5 allocs/op
BenchmarkStructLevelValidationFailure-8 3000000 558 ns/op 160 B/op 5 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-8 2000000 623 ns/op 36 B/op 3 allocs/op
BenchmarkStructSimpleCustomTypeFailure-8 1000000 1381 ns/op 640 B/op 9 allocs/op
BenchmarkStructPartialSuccess-8 1000000 1036 ns/op 272 B/op 9 allocs/op
BenchmarkStructPartialFailure-8 1000000 1734 ns/op 730 B/op 14 allocs/op
BenchmarkStructExceptSuccess-8 2000000 888 ns/op 250 B/op 7 allocs/op
BenchmarkStructExceptFailure-8 1000000 1036 ns/op 272 B/op 9 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-8 2000000 773 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossFieldFailure-8 1000000 1487 ns/op 536 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1261 ns/op 112 B/op 7 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 2055 ns/op 576 B/op 12 allocs/op
BenchmarkStructSimpleSuccess-8 3000000 519 ns/op 4 B/op 1 allocs/op
BenchmarkStructSimpleFailure-8 1000000 1429 ns/op 640 B/op 9 allocs/op
BenchmarkStructSimpleSuccessParallel-8 10000000 146 ns/op 4 B/op 1 allocs/op
BenchmarkStructSimpleFailureParallel-8 2000000 551 ns/op 640 B/op 9 allocs/op
BenchmarkStructComplexSuccess-8 500000 3269 ns/op 244 B/op 15 allocs/op
BenchmarkStructComplexFailure-8 200000 8436 ns/op 3609 B/op 60 allocs/op
BenchmarkStructComplexSuccessParallel-8 1000000 1024 ns/op 244 B/op 15 allocs/op
BenchmarkStructComplexFailureParallel-8 500000 3536 ns/op 3609 B/op 60 allocs/op
```
Complimentary Software
----------------------
Here is a list of software that compliments using this library either pre or post validation.
* [form](https://github.com/go-playground/form) - Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values. Dual Array and Full map support.
* [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags.
How to Contribute
------
There will always be a development branch for each version i.e. `v1-development`. In order to contribute,
please make your pull requests against those branches.
If the changes being proposed or requested are breaking changes, please create an issue, for discussion
or create a pull request against the highest development branch for example this package has a
v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet.
I strongly encourage everyone whom creates a custom validation function to contribute them and
help make this package even better.
License
------
Distributed under MIT License, please see license file in code for more details.
@@ -0,0 +1,285 @@
package validator
import (
"fmt"
"reflect"
"strings"
"sync"
"sync/atomic"
)
type tagType uint8
const (
typeDefault tagType = iota
typeOmitEmpty
typeNoStructLevel
typeStructOnly
typeDive
typeOr
typeExists
)
type structCache struct {
lock sync.Mutex
m atomic.Value // map[reflect.Type]*cStruct
}
func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
return
}
func (sc *structCache) Set(key reflect.Type, value *cStruct) {
m := sc.m.Load().(map[reflect.Type]*cStruct)
nm := make(map[reflect.Type]*cStruct, len(m)+1)
for k, v := range m {
nm[k] = v
}
nm[key] = value
sc.m.Store(nm)
}
type tagCache struct {
lock sync.Mutex
m atomic.Value // map[string]*cTag
}
func (tc *tagCache) Get(key string) (c *cTag, found bool) {
c, found = tc.m.Load().(map[string]*cTag)[key]
return
}
func (tc *tagCache) Set(key string, value *cTag) {
m := tc.m.Load().(map[string]*cTag)
nm := make(map[string]*cTag, len(m)+1)
for k, v := range m {
nm[k] = v
}
nm[key] = value
tc.m.Store(nm)
}
type cStruct struct {
Name string //结构体名称
fields map[int]*cField //结构体对应的字段map
fn StructLevelFunc //结构体校验器 (结构体类型->结构体校验器)
}
type cField struct {
Idx int //字段下标
Name string //字段名
AltName string //
cTags *cTag //Field对应的cTag规则,是一个链表(一串的规则).
}
type cTag struct {
tag string //标签
aliasTag string //
actualAliasTag string //
param string //如果是比较类型的标签,这里存放的是比较的值,比如说 min=10,这里存放的是【10】这个值
hasAlias bool //是否有别名校验器标签
typeof tagType //对应的tagType
hasTag bool //是否存在tag标签
fn Func //当前cTag对应的【tag标签校验器】
next *cTag //下一个cTag标签
}
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
v.structCache.lock.Lock()
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
typ := current.Type()
// could have been multiple trying to access, but once first is done this ensures struct
// isn't parsed again.
//read note 从缓存里面获取对应结构体类型的处理.
cs, ok := v.structCache.Get(typ)
if ok {
return cs
}
//read note 如果缓存里面拿不到的话,就要从结构的Field中去解析
cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]}
numFields := current.NumField()
var ctag *cTag
var fld reflect.StructField
var tag string
var customName string
//read note 进行字段的处理
for i := 0; i < numFields; i++ {
fld = typ.Field(i)
//read note 内嵌类型和小写的处理,直接pass
// 类似这种的:
/* type Derive struct {
Base ---内嵌
}
*/
if !fld.Anonymous && fld.PkgPath != blank {
continue
}
//read note 获取tag(通过之前Config设置的【tagName】属性去获取对应的tag)
tag = fld.Tag.Get(v.tagName)
//read note 如果是忽略【-】的话,跳过
if tag == skipValidationTag {
continue
}
customName = fld.Name
//read note config中【fieldNameTag】的处理,会设置到【cField】的【AltName】字段上,应该只是错误输出用的
if v.fieldNameTag != blank {
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
// dash check is for json "-" (aka skipValidationTag) means don't output in json
if name != "" && name != skipValidationTag {
customName = name
}
}
// NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
// and so only struct level caching can be used instead of combined with Field tag caching
//read note struct层的校验规则和Field层的校验规则不能共用(标签可能相同,但是别名会不一样..)
if len(tag) > 0 {
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, blank, false)
} else {
// even if field doesn't have validations need cTag for traversing to potential inner/nested
// elements of the field.
ctag = new(cTag)
}
cs.fields[i] = &cField{Idx: i, Name: fld.Name, AltName: customName, cTags: ctag}
}
v.structCache.Set(typ, cs)
return cs
}
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
var t string
var ok bool
noAlias := len(alias) == 0
//read note:这边会把tag根据【,】进行分割处理,得到对应的tag组,所以我们在写的时候可以写入,分割的标签
tags := strings.Split(tag, tagSeparator)
for i := 0; i < len(tags); i++ {
t = tags[i]
if noAlias {
alias = t
}
//read note 如果有别名校验器,则回去查找别名校验器(第一个和后面的设置不一样.调用next和没有调用next的区别)
if v.hasAliasValidators {
// check map for alias and process new tags, otherwise process as usual
if tagsVal, found := v.aliasValidators[t]; found {
if i == 0 {
//read note 对别名校验器进行解析(一个子递归的过程.)除第一个校验器外,后面的校验器是通过一个next指向的链表连接起来
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
} else {
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
current.next, current = next, curr
}
continue
}
}
//read note 设置对应的默认标签(第一个和后面的设置不一样.调用next和没有调用next的区别)
if i == 0 {
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
firstCtag = current
} else {
current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
current = current.next
}
//read note 判断用 【,】分割后的标签,前面几个是处理特殊标签的.如果不是特殊标签,则往下处理设置tag的值
switch t {
case diveTag:
current.typeof = typeDive
continue
case omitempty:
current.typeof = typeOmitEmpty
continue
case structOnlyTag:
current.typeof = typeStructOnly
continue
case noStructLevelTag:
current.typeof = typeNoStructLevel
continue
case existsTag:
current.typeof = typeExists
continue
default:
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
//read note 或的条件进行拆分.
for j := 0; j < len(orVals); j++ {
//read note 解析【=】标签
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
if noAlias {
alias = vals[0]
current.aliasTag = alias
} else {
current.actualAliasTag = t
}
//read note 如果或的标签成立,也就是说 A|B|C ABC会组装成一个链(非头节点需要往下一个节点去处理,所以这边需要把current指向它的next
if j > 0 {
current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
current = current.next
}
current.tag = vals[0]
if len(current.tag) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
//read note 查找对应的校验规则
if current.fn, ok = v.validationFuncs[current.tag]; !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
}
//read note 设置为typeOr.这个应该在后面处理对应字段的时候会拿到并且通过or的方式进行处理
if len(orVals) > 1 {
current.typeof = typeOr
}
if len(vals) > 1 {
current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
}
}
}
return
}
@@ -0,0 +1,852 @@
/*
Package validator implements value validations for structs and individual fields
based on tags.
It can also handle Cross-Field and Cross-Struct validation for nested structs
and has the ability to dive into arrays and maps of any type.
Why not a better error message?
Because this library intends for you to handle your own error messages.
Why should I handle my own errors?
Many reasons. We built an internationalized application and needed to know the
field, and what validation failed so we could provide a localized error.
if fieldErr.Field == "Name" {
switch fieldErr.ErrorTag
case "required":
return "Translated string based on field + error"
default:
return "Translated string based on field"
}
Validation Functions Return Type error
Doing things this way is actually the way the standard library does, see the
file.Open method here:
https://golang.org/pkg/os/#Open.
The authors return type "error" to avoid the issue discussed in the following,
where err is always != nil:
http://stackoverflow.com/a/29138676/3158232
https://github.com/go-playground/validator/issues/134
Validator only returns nil or ValidationErrors as type error; so, in your code
all you need to do is check if the error returned is not nil, and if it's not
type cast it to type ValidationErrors like so err.(validator.ValidationErrors).
Custom Functions
Custom functions can be added. Example:
// Structure
func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if whatever {
return false
}
return true
}
validate.RegisterValidation("custom tag name", customFunc)
// NOTES: using the same tag name as an existing function
// will overwrite the existing one
Cross-Field Validation
Cross-Field Validation can be done via the following tags:
- eqfield
- nefield
- gtfield
- gtefield
- ltfield
- ltefield
- eqcsfield
- necsfield
- gtcsfield
- ftecsfield
- ltcsfield
- ltecsfield
If, however, some custom cross-field validation is required, it can be done
using a custom validation.
Why not just have cross-fields validation tags (i.e. only eqcsfield and not
eqfield)?
The reason is efficiency. If you want to check a field within the same struct
"eqfield" only has to find the field on the same struct (1 level). But, if we
used "eqcsfield" it could be multiple levels down. Example:
type Inner struct {
StartDate time.Time
}
type Outer struct {
InnerStructField *Inner
CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"`
}
now := time.Now()
inner := &Inner{
StartDate: now,
}
outer := &Outer{
InnerStructField: inner,
CreatedAt: now,
}
errs := validate.Struct(outer)
// NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed
// into the function
// when calling validate.FieldWithValue(val, field, tag) val will be
// whatever you pass, struct, field...
// when calling validate.Field(field, tag) val will be nil
Multiple Validators
Multiple validators on a field will process in the order defined. Example:
type Test struct {
Field `validate:"max=10,min=1"`
}
// max will be checked then min
Bad Validator definitions are not handled by the library. Example:
type Test struct {
Field `validate:"min=10,max=0"`
}
// this definition of min max will never succeed
Using Validator Tags
Baked In Cross-Field validation only compares fields on the same struct.
If Cross-Field + Cross-Struct validation is needed you should implement your
own custom validator.
Comma (",") is the default separator of validation tags. If you wish to
have a comma included within the parameter (i.e. excludesall=,) you will need to
use the UTF-8 hex representation 0x2C, which is replaced in the code as a comma,
so the above will become excludesall=0x2C.
type Test struct {
Field `validate:"excludesall=,"` // BAD! Do not include a comma.
Field `validate:"excludesall=0x2C"` // GOOD! Use the UTF-8 hex representation.
}
Pipe ("|") is the default separator of validation tags. If you wish to
have a pipe included within the parameter i.e. excludesall=| you will need to
use the UTF-8 hex representation 0x7C, which is replaced in the code as a pipe,
so the above will become excludesall=0x7C
type Test struct {
Field `validate:"excludesall=|"` // BAD! Do not include a a pipe!
Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation.
}
Baked In Validators and Tags
Here is a list of the current built in validators:
Skip Field
Tells the validation to skip this struct field; this is particularly
handy in ignoring embedded structs from being validated. (Usage: -)
Usage: -
Or Operator
This is the 'or' operator allowing multiple validators to be used and
accepted. (Usage: rbg|rgba) <-- this would allow either rgb or rgba
colors to be accepted. This can also be combined with 'and' for example
( Usage: omitempty,rgb|rgba)
Usage: |
StructOnly
When a field that is a nested struct is encountered, and contains this flag
any validation on the nested struct will be run, but none of the nested
struct fields will be validated. This is usefull if inside of you program
you know the struct will be valid, but need to verify it has been assigned.
NOTE: only "required" and "omitempty" can be used on a struct itself.
Usage: structonly
NoStructLevel
Same as structonly tag except that any struct level validations will not run.
Usage: nostructlevel
Exists
Is a special tag without a validation function attached. It is used when a field
is a Pointer, Interface or Invalid and you wish to validate that it exists.
Example: want to ensure a bool exists if you define the bool as a pointer and
use exists it will ensure there is a value; couldn't use required as it would
fail when the bool was false. exists will fail is the value is a Pointer, Interface
or Invalid and is nil.
Usage: exists
Omit Empty
Allows conditional validation, for example if a field is not set with
a value (Determined by the "required" validator) then other validation
such as min or max won't run, but if a value is set validation will run.
Usage: omitempty
Dive
This tells the validator to dive into a slice, array or map and validate that
level of the slice, array or map with the validation tags that follow.
Multidimensional nesting is also supported, each level you wish to dive will
require another dive tag.
Usage: dive
Example #1
[][]string with validation tag "gt=0,dive,len=1,dive,required"
// gt=0 will be applied to []
// len=1 will be applied to []string
// required will be applied to string
Example #2
[][]string with validation tag "gt=0,dive,dive,required"
// gt=0 will be applied to []
// []string will be spared validation
// required will be applied to string
Required
This validates that the value is not the data types default zero value.
For numbers ensures value is not zero. For strings ensures value is
not "". For slices, maps, pointers, interfaces, channels and functions
ensures the value is not nil.
Usage: required
Length
For numbers, max will ensure that the value is
equal to the parameter given. For strings, it checks that
the string length is exactly that number of characters. For slices,
arrays, and maps, validates the number of items.
Usage: len=10
Maximum
For numbers, max will ensure that the value is
less than or equal to the parameter given. For strings, it checks
that the string length is at most that number of characters. For
slices, arrays, and maps, validates the number of items.
Usage: max=10
Mininum
For numbers, min will ensure that the value is
greater or equal to the parameter given. For strings, it checks that
the string length is at least that number of characters. For slices,
arrays, and maps, validates the number of items.
Usage: min=10
Equals
For strings & numbers, eq will ensure that the value is
equal to the parameter given. For slices, arrays, and maps,
validates the number of items.
Usage: eq=10
Not Equal
For strings & numbers, ne will ensure that the value is not
equal to the parameter given. For slices, arrays, and maps,
validates the number of items.
Usage: ne=10
Greater Than
For numbers, this will ensure that the value is greater than the
parameter given. For strings, it checks that the string length
is greater than that number of characters. For slices, arrays
and maps it validates the number of items.
Example #1
Usage: gt=10
Example #2 (time.Time)
For time.Time ensures the time value is greater than time.Now.UTC().
Usage: gt
Greater Than or Equal
Same as 'min' above. Kept both to make terminology with 'len' easier.
Example #1
Usage: gte=10
Example #2 (time.Time)
For time.Time ensures the time value is greater than or equal to time.Now.UTC().
Usage: gte
Less Than
For numbers, this will ensure that the value is less than the parameter given.
For strings, it checks that the string length is less than that number of
characters. For slices, arrays, and maps it validates the number of items.
Example #1
Usage: lt=10
Example #2 (time.Time)
For time.Time ensures the time value is less than time.Now.UTC().
Usage: lt
Less Than or Equal
Same as 'max' above. Kept both to make terminology with 'len' easier.
Example #1
Usage: lte=10
Example #2 (time.Time)
For time.Time ensures the time value is less than or equal to time.Now.UTC().
Usage: lte
Field Equals Another Field
This will validate the field value against another fields value either within
a struct or passed in field.
Example #1:
// Validation on Password field using:
Usage: eqfield=ConfirmPassword
Example #2:
// Validating by field:
validate.FieldWithValue(password, confirmpassword, "eqfield")
Field Equals Another Field (relative)
This does the same as eqfield except that it validates the field provided relative
to the top level struct.
Usage: eqcsfield=InnerStructField.Field)
Field Does Not Equal Another Field
This will validate the field value against another fields value either within
a struct or passed in field.
Examples:
// Confirm two colors are not the same:
//
// Validation on Color field:
Usage: nefield=Color2
// Validating by field:
validate.FieldWithValue(color1, color2, "nefield")
Field Does Not Equal Another Field (relative)
This does the same as nefield except that it validates the field provided
relative to the top level struct.
Usage: necsfield=InnerStructField.Field
Field Greater Than Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Example #1:
// Validation on End field using:
validate.Struct Usage(gtfield=Start)
Example #2:
// Validating by field:
validate.FieldWithValue(start, end, "gtfield")
Field Greater Than Another Relative Field
This does the same as gtfield except that it validates the field provided
relative to the top level struct.
Usage: gtcsfield=InnerStructField.Field
Field Greater Than or Equal To Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Example #1:
// Validation on End field using:
validate.Struct Usage(gtefield=Start)
Example #2:
// Validating by field:
validate.FieldWithValue(start, end, "gtefield")
Field Greater Than or Equal To Another Relative Field
This does the same as gtefield except that it validates the field provided relative
to the top level struct.
Usage: gtecsfield=InnerStructField.Field
Less Than Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Example #1:
// Validation on End field using:
validate.Struct Usage(ltfield=Start)
Example #2:
// Validating by field:
validate.FieldWithValue(start, end, "ltfield")
Less Than Another Relative Field
This does the same as ltfield except that it validates the field provided relative
to the top level struct.
Usage: ltcsfield=InnerStructField.Field
Less Than or Equal To Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Example #1:
// Validation on End field using:
validate.Struct Usage(ltefield=Start)
Example #2:
// Validating by field:
validate.FieldWithValue(start, end, "ltefield")
Less Than or Equal To Another Relative Field
This does the same as ltefield except that it validates the field provided relative
to the top level struct.
Usage: ltecsfield=InnerStructField.Field
Alpha Only
This validates that a string value contains alpha characters only
Usage: alpha
Alphanumeric
This validates that a string value contains alphanumeric characters only
Usage: alphanum
Numeric
This validates that a string value contains a basic numeric value.
basic excludes exponents etc...
Usage: numeric
Hexadecimal String
This validates that a string value contains a valid hexadecimal.
Usage: hexadecimal
Hexcolor String
This validates that a string value contains a valid hex color including
hashtag (#)
Usage: hexcolor
RGB String
This validates that a string value contains a valid rgb color
Usage: rgb
RGBA String
This validates that a string value contains a valid rgba color
Usage: rgba
HSL String
This validates that a string value contains a valid hsl color
Usage: hsl
HSLA String
This validates that a string value contains a valid hsla color
Usage: hsla
E-mail String
This validates that a string value contains a valid email
This may not conform to all possibilities of any rfc standard, but neither
does any email provider accept all posibilities.
Usage: email
URL String
This validates that a string value contains a valid url
This will accept any url the golang request uri accepts but must contain
a schema for example http:// or rtmp://
Usage: url
URI String
This validates that a string value contains a valid uri
This will accept any uri the golang request uri accepts
Usage: uri
Base64 String
This validates that a string value contains a valid base64 value.
Although an empty string is valid base64 this will report an empty string
as an error, if you wish to accept an empty string as valid you can use
this with the omitempty tag.
Usage: base64
Contains
This validates that a string value contains the substring value.
Usage: contains=@
Contains Any
This validates that a string value contains any Unicode code points
in the substring value.
Usage: containsany=!@#?
Contains Rune
This validates that a string value contains the supplied rune value.
Usage: containsrune=@
Excludes
This validates that a string value does not contain the substring value.
Usage: excludes=@
Excludes All
This validates that a string value does not contain any Unicode code
points in the substring value.
Usage: excludesall=!@#?
Excludes Rune
This validates that a string value does not contain the supplied rune value.
Usage: excludesrune=@
International Standard Book Number
This validates that a string value contains a valid isbn10 or isbn13 value.
Usage: isbn
International Standard Book Number 10
This validates that a string value contains a valid isbn10 value.
Usage: isbn10
International Standard Book Number 13
This validates that a string value contains a valid isbn13 value.
Usage: isbn13
Universally Unique Identifier UUID
This validates that a string value contains a valid UUID.
Usage: uuid
Universally Unique Identifier UUID v3
This validates that a string value contains a valid version 3 UUID.
Usage: uuid3
Universally Unique Identifier UUID v4
This validates that a string value contains a valid version 4 UUID.
Usage: uuid4
Universally Unique Identifier UUID v5
This validates that a string value contains a valid version 5 UUID.
Usage: uuid5
ASCII
This validates that a string value contains only ASCII characters.
NOTE: if the string is blank, this validates as true.
Usage: ascii
Printable ASCII
This validates that a string value contains only printable ASCII characters.
NOTE: if the string is blank, this validates as true.
Usage: asciiprint
Multi-Byte Characters
This validates that a string value contains one or more multibyte characters.
NOTE: if the string is blank, this validates as true.
Usage: multibyte
Data URL
This validates that a string value contains a valid DataURI.
NOTE: this will also validate that the data portion is valid base64
Usage: datauri
Latitude
This validates that a string value contains a valid latitude.
Usage: latitude
Longitude
This validates that a string value contains a valid longitude.
Usage: longitude
Social Security Number SSN
This validates that a string value contains a valid U.S. Social Security Number.
Usage: ssn
Internet Protocol Address IP
This validates that a string value contains a valid IP Adress.
Usage: ip
Internet Protocol Address IPv4
This validates that a string value contains a valid v4 IP Adress.
Usage: ipv4
Internet Protocol Address IPv6
This validates that a string value contains a valid v6 IP Adress.
Usage: ipv6
Classless Inter-Domain Routing CIDR
This validates that a string value contains a valid CIDR Adress.
Usage: cidr
Classless Inter-Domain Routing CIDRv4
This validates that a string value contains a valid v4 CIDR Adress.
Usage: cidrv4
Classless Inter-Domain Routing CIDRv6
This validates that a string value contains a valid v6 CIDR Adress.
Usage: cidrv6
Transmission Control Protocol Address TCP
This validates that a string value contains a valid resolvable TCP Adress.
Usage: tcp_addr
Transmission Control Protocol Address TCPv4
This validates that a string value contains a valid resolvable v4 TCP Adress.
Usage: tcp4_addr
Transmission Control Protocol Address TCPv6
This validates that a string value contains a valid resolvable v6 TCP Adress.
Usage: tcp6_addr
User Datagram Protocol Address UDP
This validates that a string value contains a valid resolvable UDP Adress.
Usage: udp_addr
User Datagram Protocol Address UDPv4
This validates that a string value contains a valid resolvable v4 UDP Adress.
Usage: udp4_addr
User Datagram Protocol Address UDPv6
This validates that a string value contains a valid resolvable v6 UDP Adress.
Usage: udp6_addr
Internet Protocol Address IP
This validates that a string value contains a valid resolvable IP Adress.
Usage: ip_addr
Internet Protocol Address IPv4
This validates that a string value contains a valid resolvable v4 IP Adress.
Usage: ip4_addr
Internet Protocol Address IPv6
This validates that a string value contains a valid resolvable v6 IP Adress.
Usage: ip6_addr
Unix domain socket end point Address
This validates that a string value contains a valid Unix Adress.
Usage: unix_addr
Media Access Control Address MAC
This validates that a string value contains a valid MAC Adress.
Usage: mac
Note: See Go's ParseMAC for accepted formats and types:
http://golang.org/src/net/mac.go?s=866:918#L29
Alias Validators and Tags
NOTE: When returning an error, the tag returned in "FieldError" will be
the alias tag unless the dive tag is part of the alias. Everything after the
dive tag is not reported as the alias tag. Also, the "ActualTag" in the before
case will be the actual tag within the alias that failed.
Here is a list of the current built in alias tags:
"iscolor"
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
Validator notes:
regex
a regex validator won't be added because commas and = signs can be part
of a regex which conflict with the validation definitions. Although
workarounds can be made, they take away from using pure regex's.
Furthermore it's quick and dirty but the regex's become harder to
maintain and are not reusable, so it's as much a programming philosiphy
as anything.
In place of this new validator functions should be created; a regex can
be used within the validator function and even be precompiled for better
efficiency within regexes.go.
And the best reason, you can submit a pull request and we can keep on
adding to the validation library of this package!
Panics
This package panics when bad input is provided, this is by design, bad code like
that should not make it to production.
type Test struct {
TestField string `validate:"nonexistantfunction=1"`
}
t := &Test{
TestField: "Test"
}
validate.Struct(t) // this will panic
*/
package validator
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,59 @@
package validator
import "regexp"
const (
alphaRegexString = "^[a-zA-Z]+$"
alphaNumericRegexString = "^[a-zA-Z0-9]+$"
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
numberRegexString = "^[0-9]+$"
hexadecimalRegexString = "^[0-9a-fA-F]+$"
hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
aSCIIRegexString = "^[\x00-\x7F]*$"
printableASCIIRegexString = "^[\x20-\x7E]*$"
multibyteRegexString = "[^\x00-\x7F]"
dataURIRegexString = "^data:.+\\/(.+);base64$"
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
)
var (
alphaRegex = regexp.MustCompile(alphaRegexString)
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
numericRegex = regexp.MustCompile(numericRegexString)
numberRegex = regexp.MustCompile(numberRegexString)
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
rgbRegex = regexp.MustCompile(rgbRegexString)
rgbaRegex = regexp.MustCompile(rgbaRegexString)
hslRegex = regexp.MustCompile(hslRegexString)
hslaRegex = regexp.MustCompile(hslaRegexString)
emailRegex = regexp.MustCompile(emailRegexString)
base64Regex = regexp.MustCompile(base64RegexString)
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
uUID3Regex = regexp.MustCompile(uUID3RegexString)
uUID4Regex = regexp.MustCompile(uUID4RegexString)
uUID5Regex = regexp.MustCompile(uUID5RegexString)
uUIDRegex = regexp.MustCompile(uUIDRegexString)
aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
multibyteRegex = regexp.MustCompile(multibyteRegexString)
dataURIRegex = regexp.MustCompile(dataURIRegexString)
latitudeRegex = regexp.MustCompile(latitudeRegexString)
longitudeRegex = regexp.MustCompile(longitudeRegexString)
sSNRegex = regexp.MustCompile(sSNRegexString)
)
@@ -0,0 +1,253 @@
package validator
import (
"reflect"
"strconv"
"strings"
)
const (
blank = ""
namespaceSeparator = "."
leftBracket = "["
rightBracket = "]"
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
)
var (
restrictedTags = map[string]struct{}{
diveTag: {},
existsTag: {},
structOnlyTag: {},
omitempty: {},
skipValidationTag: {},
utf8HexComma: {},
utf8Pipe: {},
noStructLevelTag: {},
}
)
// ExtractType gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the
// underlying value and it's kind.
// it is exposed for use within you Custom Functions
func (v *Validate) ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) {
val, k, _ := v.extractTypeInternal(current, false)
return val, k
}
// only exists to not break backward compatibility, needed to return the third param for a bug fix internally
func (v *Validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
switch current.Kind() {
case reflect.Ptr:
nullable = true
if current.IsNil() {
return current, reflect.Ptr, nullable
}
return v.extractTypeInternal(current.Elem(), nullable)
case reflect.Interface:
nullable = true
if current.IsNil() {
return current, reflect.Interface, nullable
}
return v.extractTypeInternal(current.Elem(), nullable)
case reflect.Invalid:
return current, reflect.Invalid, nullable
default:
if v.hasCustomFuncs {
if fn, ok := v.customTypeFuncs[current.Type()]; ok {
//read note 如果有自定义的类型校验器,则这边会先去调用一下类型校验器,然后在返回对应的结构体值,继续往回上一层,往下校验
return v.extractTypeInternal(reflect.ValueOf(fn(current)), nullable)
}
}
return current, current.Kind(), nullable
}
}
// GetStructFieldOK traverses a struct to retrieve a specific field denoted by the provided namespace and
// returns the field, field kind and whether is was successful in retrieving the field at all.
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
current, kind := v.ExtractType(current)
if kind == reflect.Invalid {
return current, kind, false
}
if namespace == blank {
return current, kind, true
}
switch kind {
case reflect.Ptr, reflect.Interface:
return current, kind, false
case reflect.Struct:
typ := current.Type()
fld := namespace
ns := namespace
if typ != timeType && typ != timePtrType {
idx := strings.Index(namespace, namespaceSeparator)
if idx != -1 {
fld = namespace[:idx]
ns = namespace[idx+1:]
} else {
ns = blank
}
bracketIdx := strings.Index(fld, leftBracket)
if bracketIdx != -1 {
fld = fld[:bracketIdx]
ns = namespace[bracketIdx:]
}
current = current.FieldByName(fld)
return v.GetStructFieldOK(current, ns)
}
case reflect.Array, reflect.Slice:
idx := strings.Index(namespace, leftBracket)
idx2 := strings.Index(namespace, rightBracket)
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
if arrIdx >= current.Len() {
return current, kind, false
}
startIdx := idx2 + 1
if startIdx < len(namespace) {
if namespace[startIdx:startIdx+1] == namespaceSeparator {
startIdx++
}
}
return v.GetStructFieldOK(current.Index(arrIdx), namespace[startIdx:])
case reflect.Map:
idx := strings.Index(namespace, leftBracket) + 1
idx2 := strings.Index(namespace, rightBracket)
endIdx := idx2
if endIdx+1 < len(namespace) {
if namespace[endIdx+1:endIdx+2] == namespaceSeparator {
endIdx++
}
}
key := namespace[idx:idx2]
switch current.Type().Key().Kind() {
case reflect.Int:
i, _ := strconv.Atoi(key)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Int8:
i, _ := strconv.ParseInt(key, 10, 8)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:])
case reflect.Int16:
i, _ := strconv.ParseInt(key, 10, 16)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:])
case reflect.Int32:
i, _ := strconv.ParseInt(key, 10, 32)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:])
case reflect.Int64:
i, _ := strconv.ParseInt(key, 10, 64)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Uint:
i, _ := strconv.ParseUint(key, 10, 0)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:])
case reflect.Uint8:
i, _ := strconv.ParseUint(key, 10, 8)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:])
case reflect.Uint16:
i, _ := strconv.ParseUint(key, 10, 16)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:])
case reflect.Uint32:
i, _ := strconv.ParseUint(key, 10, 32)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:])
case reflect.Uint64:
i, _ := strconv.ParseUint(key, 10, 64)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Float32:
f, _ := strconv.ParseFloat(key, 32)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:])
case reflect.Float64:
f, _ := strconv.ParseFloat(key, 64)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:])
case reflect.Bool:
b, _ := strconv.ParseBool(key)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:])
// reflect.Type = string
default:
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:])
}
}
// if got here there was more namespace, cannot go any deeper
panic("Invalid field namespace")
}
// asInt returns the parameter as a int64
// or panics if it can't convert
func asInt(param string) int64 {
i, err := strconv.ParseInt(param, 0, 64)
panicIf(err)
return i
}
// asUint returns the parameter as a uint64
// or panics if it can't convert
func asUint(param string) uint64 {
i, err := strconv.ParseUint(param, 0, 64)
panicIf(err)
return i
}
// asFloat returns the parameter as a float64
// or panics if it can't convert
func asFloat(param string) float64 {
i, err := strconv.ParseFloat(param, 64)
panicIf(err)
return i
}
func panicIf(err error) {
if err != nil {
panic(err.Error())
}
}
@@ -0,0 +1,829 @@
/**
* Package validator
*
* MISC:
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
*
*/
package validator
import (
"bytes"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"time"
)
const (
utf8HexComma = "0x2C" //read note 在tag里面代表【,】;需要这么书写,直接写【,】会被识别成分割符
utf8Pipe = "0x7C" //read note 在tag里面代表【|】;需要这么书写,直接写【|】会被识别成条件符
tagSeparator = "," //read note tag标签分割符
orSeparator = "|" //read note tag判断条件【或】
tagKeySeparator = "=" //read note tag里面需要等值 使用=
structOnlyTag = "structonly" //read note 结构体上有的tagType,因为结构体的特殊性,他在获取cTag的时候是从第二个开始的
_ // 也就是说第二个才是生效的,所以当只书写structonly的时候,他会略过这个tag,那么必须跟着前面的一个标签才行,比如说 `valid="require,structonly"`,这样才会生效
noStructLevelTag = "nostructlevel" // 同上
omitempty = "omitempty" //read note 如果字段未设值则忽略它
skipValidationTag = "-" //read note 忽略字段
diveTag = "dive" //read note 深入到slice, array or map 里面去校验里面的字段是否正确,每一层都需要多一个【div】来标识
_ //read note 比如说 [][]string "gt=0,dive,dive,required" gt是校验[][]string长度,第一个div是校验[]string,第二个div是校验string
existsTag = "exists" //read note 校验值存在即可(除非是nil则会报错),和require的区别是说,require对默认值也会报错
//read note 数组结构的名称组成
arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
//read note 字段校验错误信息打印
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
//read note 报错信息(非字段校验错误)
invalidValidation = "Invalid validation tag on field %s"
undefinedValidation = "Undefined validation function on field %s"
validatorNotInitialized = "Validator instance not initialized"
fieldNameRequired = "Field Name Required"
tagRequired = "Tag Required"
)
var (
timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{})
defaultCField = new(cField)
)
// StructLevel contains all of the information and helper methods
// for reporting errors during struct level validation
type StructLevel struct {
TopStruct reflect.Value
CurrentStruct reflect.Value
errPrefix string
nsPrefix string
errs ValidationErrors
v *Validate
}
// ReportValidationErrors accepts the key relative to the top level struct and validatin errors.
// Example: had a triple nested struct User, ContactInfo, Country and ran errs := validate.Struct(country)
// from within a User struct level validation would call this method like so:
// ReportValidationErrors("ContactInfo.", errs)
// NOTE: relativeKey can contain both the Field Relative and Custom name relative paths
// i.e. ReportValidationErrors("ContactInfo.|cInfo", errs) where cInfo represents say the JSON name of
// the relative path; this will be split into 2 variables in the next valiator version.
func (sl *StructLevel) ReportValidationErrors(relativeKey string, errs ValidationErrors) {
for _, e := range errs {
idx := strings.Index(relativeKey, "|")
var rel string
var cRel string
if idx != -1 {
rel = relativeKey[:idx]
cRel = relativeKey[idx+1:]
} else {
rel = relativeKey
}
key := sl.errPrefix + rel + e.Field
e.FieldNamespace = key
e.NameNamespace = sl.nsPrefix + cRel + e.Name
sl.errs[key] = e
}
}
// ReportError reports an error just by passing the field and tag information
// NOTE: tag can be an existing validation tag or just something you make up
// and precess on the flip side it's up to you.
func (sl *StructLevel) ReportError(field reflect.Value, fieldName string, customName string, tag string) {
field, kind := sl.v.ExtractType(field)
if fieldName == blank {
panic(fieldNameRequired)
}
if customName == blank {
customName = fieldName
}
if tag == blank {
panic(tagRequired)
}
ns := sl.errPrefix + fieldName
switch kind {
case reflect.Invalid:
sl.errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: sl.nsPrefix + customName,
Name: customName,
Field: fieldName,
Tag: tag,
ActualTag: tag,
Param: blank,
Kind: kind,
}
default:
sl.errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: sl.nsPrefix + customName,
Name: customName,
Field: fieldName,
Tag: tag,
ActualTag: tag,
Param: blank,
Value: field.Interface(),
Kind: kind,
Type: field.Type(),
}
}
}
// Validate contains the validator settings passed in using the Config struct
type Validate struct {
tagName string //校验起作用的tag名
fieldNameTag string //
validationFuncs map[string]Func //规则类型的校验 【tag标签】-> 校验规则
structLevelFuncs map[reflect.Type]StructLevelFunc //规则结构体的校验 【结构体类型】-> 校验规则
customTypeFuncs map[reflect.Type]CustomTypeFunc //类型校验器 【数据类型】-> 校验规则
aliasValidators map[string]string //别名校验器 【别名匹配规则组合】-> 校验规则
hasCustomFuncs bool //是否存在类型校验器
hasAliasValidators bool //是否有别名校验器
hasStructLevelFuncs bool //是否有结构体校验器
tagCache *tagCache //tag对应的【校验规则方法】的缓存
structCache *structCache //结构体对应的【校验规则方法】的缓存
errsPool *sync.Pool //校验错误奖池
}
func (v *Validate) initCheck() {
if v == nil {
panic(validatorNotInitialized)
}
}
// Config contains the options that a Validator instance will use.
// It is passed to the New() function
type Config struct {
TagName string
FieldNameTag string
}
// CustomTypeFunc allows for overriding or adding custom field type handler functions
// field = field value of the type to return a value to be validated
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
type CustomTypeFunc func(field reflect.Value) interface{}
// Func accepts all values needed for file and cross field validation
// v = validator instance, needed but some built in functions for it's custom types
// topStruct = top level struct when validating by struct otherwise nil
// currentStruct = current level struct when validating by struct otherwise optional comparison value
// field = field value for validation
// param = parameter used in validation i.e. gt=0 param would be 0
type Func func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool
// StructLevelFunc accepts all values needed for struct level validation
type StructLevelFunc func(v *Validate, structLevel *StructLevel)
// ValidationErrors is a type of map[string]*FieldError
// it exists to allow for multiple errors to be passed from this library
// and yet still subscribe to the error interface
type ValidationErrors map[string]*FieldError
// Error is intended for use in development + debugging and not intended to be a production error message.
// It allows ValidationErrors to subscribe to the Error interface.
// All information to create an error message specific to your application is contained within
// the FieldError found within the ValidationErrors map
func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString(blank)
for key, err := range ve {
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
buff.WriteString("\n")
}
return strings.TrimSpace(buff.String())
}
// FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation
type FieldError struct {
FieldNamespace string
NameNamespace string
Field string
Name string
Tag string
ActualTag string
Kind reflect.Kind
Type reflect.Type
Param string
Value interface{}
}
// New creates a new Validate instance for use.
func New(config *Config) *Validate {
//read note tag校验器缓存,初始化
tc := new(tagCache)
tc.m.Store(make(map[string]*cTag))
//read note 结构体缓存,初始化一个Map,防止后面添加的时候报错
sc := new(structCache)
sc.m.Store(make(map[reflect.Type]*cStruct))
v := &Validate{
tagName: config.TagName,
fieldNameTag: config.FieldNameTag,
tagCache: tc,
structCache: sc,
//read note 自定义一个错误池
errsPool: &sync.Pool{New: func() interface{} {
return ValidationErrors{}
}}}
//read note 设置别名规则类(一组) 可参考: baked_in.go/bakedInAliasValidators
if len(v.aliasValidators) == 0 {
// must copy alias validators for separate validations to be used in each validator instance
v.aliasValidators = map[string]string{}
for k, val := range bakedInAliasValidators {
//read note 默认别名校验器注册
v.RegisterAliasValidation(k, val)
}
}
//read note 设置默认的校验方法.可参考: baked_in.go/bakedInValidators
if len(v.validationFuncs) == 0 {
// must copy validators for separate validations to be used in each instance
v.validationFuncs = map[string]Func{}
for k, val := range bakedInValidators {
//read note 默认tag校验器注册
v.RegisterValidation(k, val)
}
}
return v
}
// RegisterStructValidation registers a StructLevelFunc against a number of types
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) {
v.initCheck()
if v.structLevelFuncs == nil {
v.structLevelFuncs = map[reflect.Type]StructLevelFunc{}
}
for _, t := range types {
v.structLevelFuncs[reflect.TypeOf(t)] = fn
fmt.Println(reflect.TypeOf(t))
}
v.hasStructLevelFuncs = true
}
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, the previous validation function will be replaced.
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterValidation(key string, fn Func) error {
v.initCheck()
if key == blank {
return errors.New("Function Key cannot be empty")
}
if fn == nil {
return errors.New("Function cannot be empty")
}
_, ok := restrictedTags[key]
if ok || strings.ContainsAny(key, restrictedTagChars) {
panic(fmt.Sprintf(restrictedTagErr, key))
}
v.validationFuncs[key] = fn
return nil
}
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
//read note 注册类型的校验器,应该在校验之前就注册完成,因为该注册方法不是线程安全的.
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
v.initCheck()
if v.customTypeFuncs == nil {
v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{}
}
for _, t := range types {
v.customTypeFuncs[reflect.TypeOf(t)] = fn
}
v.hasCustomFuncs = true
}
// RegisterAliasValidation registers a mapping of a single validationstag that
// defines a common or complex set of validation(s) to simplify adding validation
// to structs. NOTE: when returning an error the tag returned in FieldError will be
// the alias tag unless the dive tag is part of the alias; everything after the
// dive tag is not reported as the alias tag. Also the ActualTag in the before case
// will be the actual tag within the alias that failed.
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterAliasValidation(alias, tags string) {
v.initCheck()
_, ok := restrictedTags[alias]
if ok || strings.ContainsAny(alias, restrictedTagChars) {
panic(fmt.Sprintf(restrictedAliasErr, alias))
}
v.aliasValidators[alias] = tags
v.hasAliasValidators = true
}
// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
// validate Array, Slice and maps fields which may contain more than one error
//read note
func (v *Validate) Field(field interface{}, tag string) error {
v.initCheck()
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
errs := v.errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field)
//read note 从tag缓存中获取 tag标签对应的校验方法
ctag, ok := v.tagCache.Get(tag)
if !ok {
//read note 加锁
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
//read note 加锁之后在这边再获取一次,因为在上一次判断到加锁的过程中,可能有函数已经把对应方法加载进来了
ctag, ok = v.tagCache.Get(tag)
if !ok {
//read note
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
//read note 进行标签设值
v.tagCache.Set(tag, ctag)
}
}
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
//read note 判断校验错误是否存在
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
// FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
v.initCheck()
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
errs := v.errsPool.Get().(ValidationErrors)
topVal := reflect.ValueOf(val)
ctag, ok := v.tagCache.Get(tag)
if !ok {
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
v.tagCache.Set(tag, ctag)
}
}
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
// StructPartial validates the fields passed in only, ignoring all others.
// Fields may be provided in a namespaced fashion relative to the struct provided
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) StructPartial(current interface{}, fields ...string) error {
v.initCheck()
sv, _ := v.ExtractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]struct{}{}
if fields != nil {
for _, k := range fields {
flds := strings.Split(k, namespaceSeparator)
if len(flds) > 0 {
key := name + namespaceSeparator
for _, s := range flds {
idx := strings.Index(s, leftBracket)
if idx != -1 {
for idx != -1 {
key += s[:idx]
m[key] = struct{}{}
idx2 := strings.Index(s, rightBracket)
idx2++
key += s[idx:idx2]
m[key] = struct{}{}
s = s[idx2:]
idx = strings.Index(s, leftBracket)
}
} else {
key += s
m[key] = struct{}{}
}
key += namespaceSeparator
}
}
}
}
errs := v.errsPool.Get().(ValidationErrors)
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
// StructExcept validates all fields except the ones passed in.
// Fields may be provided in a namespaced fashion relative to the struct provided
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) StructExcept(current interface{}, fields ...string) error {
v.initCheck()
sv, _ := v.ExtractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]struct{}{}
for _, key := range fields {
m[name+namespaceSeparator+key] = struct{}{}
}
errs := v.errsPool.Get().(ValidationErrors)
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
// it returns nil or ValidationErrors as error.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) Struct(current interface{}) error {
v.initCheck()
errs := v.errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current)
//read note 进行结构体校验
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false)
//read note 校验之后,对校验错误进行处理.
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, isStructOnly bool) {
//read note 指针处理成结构体Value(返回其指向的实际结构体)
if current.Kind() == reflect.Ptr && !current.IsNil() {
current = current.Elem()
}
//read note 结构体类型校验.
if current.Kind() != reflect.Struct && current.Kind() != reflect.Interface {
panic("value passed for validation is not a struct")
}
//read note 校验结构体
v.tranverseStruct(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, useStructName, partial, exclude, includeExclude, nil, nil)
}
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, ct *cTag) {
var ok bool
first := len(nsPrefix) == 0
typ := current.Type()
//read note 结构体校验器_缓存
cs, ok = v.structCache.Get(typ)
if !ok {
cs = v.extractStructCache(current, typ.Name())
}
if useStructName {
errPrefix += cs.Name + namespaceSeparator
if len(v.fieldNameTag) != 0 {
nsPrefix += cs.Name + namespaceSeparator
}
}
// structonly tag present don't tranverseFields
// but must still check and run below struct level validation
// if present
//read note 【structonly】标签会忽略掉Field的校验
if first || ct == nil || ct.typeof != typeStructOnly {
for _, f := range cs.fields {
if partial {
_, ok = includeExclude[errPrefix+f.Name]
if (ok && exclude) || (!ok && !exclude) {
continue
}
}
v.traverseField(topStruct, currentStruct, current.Field(f.Idx), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, f, f.cTags)
}
}
// check if any struct level validations, after all field validations already checked.
//read note 如果结构体层的校验存在的话,需要进行调用.(这边是规则类校验)
if cs.fn != nil {
cs.fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
}
}
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, cf *cField, ct *cTag) {
//read note 处理Ptr、Interface、Invalid(validator自定义)、自定义结构体类型.
// 返回参数为:值 、 数据源类型、nullable(用来校验 【结构体】的【omiEmpty】标签...)
current, kind, nullable := v.extractTypeInternal(current, false)
var typ reflect.Type
switch kind {
//read note 处理地址、Interface、Invalid是直接往校验错误池中添加一个Field错误?
case reflect.Ptr, reflect.Interface, reflect.Invalid:
if ct == nil {
return
}
if ct.typeof == typeOmitEmpty {
return
}
if ct.hasTag {
ns := errPrefix + cf.Name
//read note 如果是Invalid,需要往校验错误池中添加一个Field错误
if kind == reflect.Invalid {
errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: ct.aliasTag,
ActualTag: ct.tag,
Param: ct.param,
Kind: kind,
}
return
}
errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: ct.aliasTag,
ActualTag: ct.tag,
Param: ct.param,
Value: current.Interface(),
Kind: kind,
Type: current.Type(),
}
return
}
case reflect.Struct:
typ = current.Type()
if typ != timeType {
//read note 对于结构体,这边有一个比较特殊的处理就是cTag是从第二个位置开始,也就是之前一直尝试structonly不成功,就是需要在structonly前面加一个tag才行。
// 前一个对于struct应该是不生效才对,第二个才会对struct生效
if ct != nil {
ct = ct.next
}
if ct != nil && ct.typeof == typeNoStructLevel {
return
}
//read note 如果上面的cTag没有生效/没有tagType为【typeNoStructLevel】的标签.就直接进入这个结构体
v.tranverseStruct(topStruct, current, current, errPrefix+cf.Name+namespaceSeparator, nsPrefix+cf.AltName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cs, ct)
return
}
}
if !ct.hasTag {
return
}
typ = current.Type()
//read note 进行字段Field的校验,这边的话是一个for的循环,在不同的标签中去找到对应的处理方式
OUTER:
for {
if ct == nil {
return
}
switch ct.typeof {
case typeExists:
ct = ct.next
continue
case typeOmitEmpty:
if !nullable && !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
return
}
ct = ct.next
continue
case typeDive:
ct = ct.next
// traverse slice or map here
// or panic ;)
switch kind {
//read note 【dive】标签处理Slice 和 Array:往数组中的每一个元素进行处理
case reflect.Slice, reflect.Array:
for i := 0; i < current.Len(); i++ {
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct)
}
//read note 【dive】标签处理 Map:往Map中的每一个元素进行处理
case reflect.Map:
for _, key := range current.MapKeys() {
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct)
}
//read note 【div】只能标注在 Slice 、 Map 、Array 上,不然就是报错.
default:
// throw error, if not a slice or map then should not have gotten here
// bad dive tag
panic("dive error! can't dive on a non slice or map")
}
return
case typeOr:
//read note 【|】的处理
errTag := blank
for {
//read note 如果结果是true的话,因为【|】标签只要有一个成立就可以,所以可以直接排除掉其他 【|】标签了
if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
// drain rest of the 'or' values, then continue or leave
//read note 排除其他 【|】标签,直到下一个Field的非【|】标签再进入处理.
for {
ct = ct.next
if ct == nil {
return
}
if ct.typeof != typeOr {
continue OUTER
}
}
}
errTag += orSeparator + ct.tag
//read note 没有下一个cTag的处理器.处理一i西安错误信息,返回.
if ct.next == nil {
// if we get here, no valid 'or' value and no more tags
ns := errPrefix + cf.Name
if ct.hasAlias {
errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: ct.aliasTag,
ActualTag: ct.actualAliasTag,
Value: current.Interface(),
Type: typ,
Kind: kind,
}
} else {
errs[errPrefix+cf.Name] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: errTag[1:],
ActualTag: errTag[1:],
Value: current.Interface(),
Type: typ,
Kind: kind,
}
}
return
}
ct = ct.next
}
default:
//read note 剩下的标签处理.类似一个循环的处理
if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
ns := errPrefix + cf.Name
errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: nsPrefix + cf.AltName,
Name: cf.AltName,
Field: cf.Name,
Tag: ct.aliasTag,
ActualTag: ct.tag,
Value: current.Interface(),
Param: ct.param,
Type: typ,
Kind: kind,
}
return
}
ct = ct.next
}
}
}
@@ -0,0 +1,33 @@
/*
* @Author : huangzj
* @Time : 2020/12/11 16:16
* @Description
*/
package testValidator
type Ower struct {
Age int `validate:"min=10"`
}
type Cat struct {
Age int `validate:"min=10"`
Ower Ower
}
type Man struct {
Name string `validate:"ipe"`
}
type Woman struct {
Name string `validate:"diy=5"`
}
type Person struct {
Name string
M map[string]int `validate:"min=10"`
H int `validate:"max=20"`
Exist bool `validate:"exists"`
Require bool `validate:"required"`
Cat Cat
}
@@ -0,0 +1,158 @@
/*
* @Author : huangzj
* @Time : 2020/12/11 16:16
* @Description
*/
package testValidator
import (
. "Go-StudyExample/SourceAnalysisAndTool/validator.v8/sourceAnalysis/gopkg.in/go-playground/validator.v8"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
)
func TestValidator(t *testing.T) {
fmt.Println("测试别名校验器")
testAlias()
fmt.Println()
fmt.Println("测试默认和自定义tag校验器")
testTag()
fmt.Println()
fmt.Println("测试字段校验器")
testField()
fmt.Println()
fmt.Println("测试结构体校验器")
testStruct()
}
func testStruct() {
//添加结构体校验,对结构体进行处理之后,validator的标签还是会起作用
config := &Config{
TagName: "validate",
}
valid := New(config)
valid.RegisterStructValidation(func(v *Validate, structLevel *StructLevel) {
c := structLevel.CurrentStruct.Interface().(Cat)
if c.Age < 100 {
fmt.Println("猫的岁数不能小于100")
}
}, Cat{})
p := Person{
Name: "192.168.0.1",
M: map[string]int{"A": 1},
H: 2000,
Exist: false,
Require: false,
Cat: Cat{
Age: 0,
Ower: Ower{},
},
}
err := valid.Struct(p)
fmt.Println(err)
}
func testField() {
//如果注册了对应的Field,则会调用自定义的方法,不会再使用validator定义的标签
//这边测试了结构体和基本类型,都是如上述描述的这么处理
config := &Config{
TagName: "validate",
}
valid := New(config)
valid.RegisterAliasValidation("ipe", "ip|ipv4|ipv6")
valid.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
d := field.Interface().(Cat)
if d.Age < 50 {
fmt.Println("狗的岁数不能小于50")
}
return field
}, Cat{})
p := Person{
Name: "192.168.0.1",
M: map[string]int{"A": 1},
H: 2000,
Exist: false,
Require: false,
Cat: Cat{
Age: 0,
Ower: Ower{},
},
}
err := valid.Struct(p)
fmt.Println(err)
valid.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
d := field.Interface().(int)
if d != 10 {
fmt.Println("测试一下")
}
return field
}, 1)
err1 := valid.Struct(p)
fmt.Println(err1)
}
func testTag() {
w := Woman{
Name: "my name is Woman",
}
w1 := Woman{
Name: "my name is wo-man",
}
config := &Config{
TagName: "validate",
}
valid := New(config)
_ = valid.RegisterValidation("diy", func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool {
s := field.Interface().(string)
i, _ := strconv.ParseInt(param, 0, 64)
if int64(len(s)) < i || strings.Contains(s, "Woman") {
fmt.Println("校验一下")
return false
}
return true
})
err := valid.Struct(w)
fmt.Println(err)
err1 := valid.Struct(w1)
fmt.Println(err1)
}
func testAlias() {
m := Man{
Name: "192.168.0.1",
}
m1 := Man{
Name: "213123123",
}
config := &Config{
TagName: "validate",
}
valid := New(config)
valid.RegisterAliasValidation("ipe", "ip|ipv4|ipv6")
err := valid.Struct(m)
fmt.Println(err)
err1 := valid.Struct(m1)
fmt.Println(err1)
}
Binary file not shown.
+6
View File
@@ -54,4 +54,10 @@ func TestCopier(t *testing.T) {
//不同的结构体数组的转换
_ = copier.Copy(&employees, &users)
fmt.Println(fmt.Sprintf("%#v\n", employee))
i := 10
var j int
err := copier.Copy(&i, &j)
fmt.Println(err, j)
}
+27
View File
@@ -0,0 +1,27 @@
/*
* @Author : huangzj
* @Time : 2020/12/29 11:24
* @Description
*/
package email
import (
"github.com/jordan-wright/email"
"log"
"net/smtp"
"testing"
)
func TestSendAnnex(t *testing.T) {
e := email.NewEmail()
e.From = "hzj <你的邮箱@qq.com>"
e.To = []string{"hzj <接受者的邮箱@qq.com>"}
e.Subject = "附件"
e.Text = []byte("附件")
_, _ = e.AttachFile("囚徒健身2翻译版 修改.pdf")
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "你的邮箱@qq.com", "你的stmp的密码", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
+55
View File
@@ -0,0 +1,55 @@
/*
* @Author : huangzj
* @Time : 2020/12/29 11:32
* @Description
*/
package email
import (
"fmt"
"github.com/jordan-wright/email"
"log"
"net/smtp"
"os"
"sync"
"testing"
"time"
)
func TestEmailPool(t *testing.T) {
ch := make(chan *email.Email, 4)
//创建2个连接的邮件池
p, err := email.NewPool("smtp.qq.com:587", 2, smtp.PlainAuth("", "你的邮箱@qq.com", "你的stmp的密码", "smtp.qq.com"))
if err != nil {
log.Fatal("failed to create pool:", err)
}
//进行邮件发送
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func() {
defer wg.Done()
for e := range ch {
err := p.Send(e, 1*time.Second)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "email:%v sent error:%v\n", e, err)
}
}
}()
}
//发送四个邮件
for i := 0; i < 4; i++ {
e := email.NewEmail()
e.From = "hzj <你的邮箱@qq.com>"
e.To = []string{"hzj <接受者的邮箱@qq.com>"}
e.Subject = "Awesome web"
e.Text = []byte(fmt.Sprintf("Awesome Web %d", i+1))
ch <- e
}
close(ch)
wg.Wait()
}
+40
View File
@@ -0,0 +1,40 @@
/*
* @Author : huangzj
* @Time : 2020/12/29 11:19
* @Description 测试发送html,包含抄送对象
*/
package email
import (
"github.com/jordan-wright/email"
"log"
"net/smtp"
"testing"
)
func TestSendHtml(t *testing.T) {
e := email.NewEmail()
e.From = "hzj <你的邮箱@qq.com>"
e.To = []string{"hzj <接受者的邮箱@qq.com>"}
//设置抄送目标
e.Cc = []string{"hzj <抄送邮箱@qq.com>"}
e.Subject = "参考:Go 每日一库"
e.HTML = []byte(`
<H1>参考:Go 每日一库 的email库</H1>
<ul>
<li><a "https://darjun.github.io/2020/01/10/godailylib/flag/">Go 每日一库之 flag</a></li>
<li><a "https://darjun.github.io/2020/01/10/godailylib/go-flags/">Go 每日一库之 go-flags</a></li>
<li><a "https://darjun.github.io/2020/01/14/godailylib/go-homedir/">Go 每日一库之 go-homedir</a></li>
<li><a "https://darjun.github.io/2020/01/15/godailylib/go-ini/">Go 每日一库之 go-ini</a></li>
<li><a "https://darjun.github.io/2020/01/17/godailylib/cobra/">Go 每日一库之 cobra</a></li>
<li><a "https://darjun.github.io/2020/01/18/godailylib/viper/">Go 每日一库之 viper</a></li>
<li><a "https://darjun.github.io/2020/01/19/godailylib/fsnotify/">Go 每日一库之 fsnotify</a></li>
<li><a "https://darjun.github.io/2020/01/20/godailylib/cast/">Go 每日一库之 cast</a></li>
</ul>
`)
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "你的邮箱@qq.com", "你的stmp的密码", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
+27
View File
@@ -0,0 +1,27 @@
/*
* @Author : huangzj
* @Time : 2020/12/29 11:10
* @Description
*/
package email
import (
"log"
"net/smtp"
"testing"
"github.com/jordan-wright/email"
)
func TestSimpleSend(t *testing.T) {
e := email.NewEmail()
e.From = "hzj <你的邮箱@qq.com>"
e.To = []string{"接受者的邮箱@qq.com"}
e.Subject = "测试"
e.Text = []byte("Text Body is, of course, supported!")
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "你的邮箱@qq.com", "你的stmp的密码", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
+13
View File
@@ -0,0 +1,13 @@
获取库命令:go get github.com/jordan-wright/email
参考地址:https://segmentfault.com/a/1190000021761747
参考地址:https://github.com/darjun/go-daily-lib
使用说明:
先要打开邮箱,通过上方的设置找到STMP服务,打开该服务,获取对应的密码
搜索对应邮箱的STMP地址和端口号,这个百度就可以找到
填写对应的发送邮箱和接受邮箱,并设置STMP密码即可
Binary file not shown.
+47
View File
@@ -0,0 +1,47 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:43
* @Description
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
//测试指向同一个结构体的对象
func TestStruct(t *testing.T) {
u1 := User{Name: "dj", Age: 18}
u2 := User{Name: "dj", Age: 18}
fmt.Println("u1 == u2?", u1 == u2)
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
u1.Contact = c1
u2.Contact = c1
fmt.Println("u1 == u2 with same pointer?", u1 == u2)
fmt.Println("u1 equals u2 with same pointer?", cmp.Equal(u1, u2))
}
//测试指向不同结构体,但是结构体值一致的对象
func TestPoint(t *testing.T) {
u1 := User{Name: "dj", Age: 18}
u2 := User{Name: "dj", Age: 18}
fmt.Println("u1 == u2?", u1 == u2)
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
c2 := &Contact{Phone: "123456789", Email: "dj@example.com"}
u1.Contact = c1
u2.Contact = c2
fmt.Println("u1 == u2 with different pointer?", u1 == u2)
fmt.Println("u1 equals u2 with different pointer?", cmp.Equal(u1, u2))
}
+34
View File
@@ -0,0 +1,34 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:03
* @Description:输出两个对象之间的差异
*/
package go_cmp
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
type Contact struct {
Phone string
Email string
}
type User struct {
Name string
Age int
Contact *Contact
}
func TestDiff(t *testing.T) {
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
c2 := &Contact{Phone: "123456879", Email: "dj2@example.com"}
u1 := User{Name: "dj", Age: 18, Contact: c1}
u2 := User{Name: "dj2", Age: 18, Contact: c2}
fmt.Println(cmp.Diff(u1, u2))
}
+50
View File
@@ -0,0 +1,50 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:57
* @Description
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
type NetAddr1 struct {
IP string
Port int
}
func compareNetAddr(a, b NetAddr1) bool {
if a.Port != b.Port {
return false
}
if a.IP != b.IP {
if a.IP == "127.0.0.1" && b.IP == "localhost" {
return true
}
if a.IP == "localhost" && b.IP == "127.0.0.1" {
return true
}
return false
}
return true
}
//自定义比较器:
// 这种方式与上面介绍的自定义Equal()方法有些类似,但更灵活。
// 有时,我们要自定义比较操作的类型定义在第三方包中,这样就无法给它定义Equal方法。
// 这时,我们就可以采用自定义Comparer的方式。
func TestDiyCompareMethod(t *testing.T) {
a1 := NetAddr1{"127.0.0.1", 5000}
a2 := NetAddr1{"localhost", 5000}
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2))
fmt.Println("a1 equals a2 with comparer?", cmp.Equal(a1, a2, cmp.Comparer(compareNetAddr)))
}
+47
View File
@@ -0,0 +1,47 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:49
* @Description:自定义Equal方法...
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
type NetAddr struct {
IP string
Port int
}
func (a NetAddr) Equal(b NetAddr) bool {
if a.Port != b.Port {
return false
}
if a.IP != b.IP {
if a.IP == "127.0.0.1" && b.IP == "localhost" {
return true
}
if a.IP == "localhost" && b.IP == "127.0.0.1" {
return true
}
return false
}
return true
}
func TestDiyEqualMethod(t *testing.T) {
a1 := NetAddr{"127.0.0.1", 5000}
a2 := NetAddr{"localhost", 5000}
a3 := NetAddr{"192.168.1.1", 5000}
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2))
fmt.Println("a1 equals a3?", cmp.Equal(a1, a3))
}
+58
View File
@@ -0,0 +1,58 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 15:11
* @Description
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"math"
"testing"
)
type FloatPair struct {
X float64
Y float64
}
func TestBasicFloat(t *testing.T) {
//特殊的浮点数NaNNot a Number),它与任何浮点数都不等,包括它自己
p1 := FloatPair{X: math.NaN()}
p2 := FloatPair{X: math.NaN()}
fmt.Println("p1 equals p2?", cmp.Equal(p1, p2))
//受限于变量的存储空间,就会存在误差
f1 := 0.1
f2 := 0.2
f3 := 0.3
p3 := FloatPair{X: f1 + f2}
p4 := FloatPair{X: f3}
fmt.Println("p3 equals p4?", cmp.Equal(p3, p4))
//Go 语言中这些字面量的运算直接是在编译器完成的,所以同样的值虽然受限于变量的存储空间,但是结果是一样的.
p5 := FloatPair{X: 0.1 + 0.2}
p6 := FloatPair{X: 0.3}
fmt.Println("p5 equals p6?", cmp.Equal(p5, p6))
}
//通过equal方法测试NaN返回结果一致
func TestNanEqual(t *testing.T) {
p1 := FloatPair{X: math.NaN()}
p2 := FloatPair{X: math.NaN()}
fmt.Println("p1 equals p2?", cmp.Equal(p1, p2, cmpopts.EquateNaNs()))
}
//测试误差在一定的范围内 具体表示是:|x-y| ≤ max(fraction*min(|x|, |y|), margin)
func TestCountError(t *testing.T) {
f1 := 0.1
f2 := 0.2
f3 := 0.3
p3 := FloatPair{X: f1 + f2}
p4 := FloatPair{X: f3}
fmt.Println(fmt.Sprintf("实际存储的值分别是:\n %v \n %v", p3.X, p4.X))
fmt.Println("p3 equals p4?", cmp.Equal(p3, p4, cmpopts.EquateApprox(0.1, 0.001)))
}
+95
View File
@@ -0,0 +1,95 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 15:21
* @Description:测试未导出字段
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"reflect"
"testing"
)
//自定义是否比较未导出字段,这个方法和上面的很类似,都是必须制定相应的结构体
func TestDiyExportMethod(t *testing.T) {
u1 := User2{"dj", 18, Address{}}
u2 := User2{"dj", 18, Address{}}
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Exporter(allowUnExportedInType)))
}
//测试只忽略当前对象的未导出属性
func TestExportOne(t *testing.T) {
c1 := Contactx{Phone: "123456789", Email: "dj@example.com"}
c2 := Contactx{Phone: "123456789", Email: "dj@example.com"}
u1 := Userx{"dj", 18, c1}
u2 := Userx{"dj", 18, c2}
//i think 通过 IgnoreUnexported 这个属性可以实现忽略具体类型的未导出字段,但是好像没办法忽略 【指针类型】 的未导出字段
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmpopts.IgnoreUnexported(Userx{})))
}
//结构体中有未导出字段的时候,通过Equal进行比较的时候,会报错,除非设置忽略
func TestExportError(t *testing.T) {
c1 := &Contact1{Phone: "123456789", Email: "dj@example.com"}
c2 := &Contact1{Phone: "123456789", Email: "dj@example.com"}
u1 := User1{"dj", 18, c1}
u2 := User1{"dj", 18, c2}
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
}
//使用 【IgnoreUnexported】,但是在结构体下层还有其他非导出的字段也会报错,这个字段没办法深入到里面
func TestExportError2(t *testing.T) {
u11 := User2{"dj", 18, Address{}}
u22 := User2{"dj", 18, Address{}}
fmt.Println("u1 equals u2?", cmp.Equal(u11, u22, cmpopts.IgnoreUnexported(User2{})))
}
func allowUnExportedInType(t reflect.Type) bool {
if t.Name() == "Address" {
return true
}
return false
}
type Contactx struct {
Phone string
Email string
}
type Userx struct {
Name string
Age int
contact Contactx //注意这边是结构体
}
type Contact1 struct {
Phone string
Email string
}
type User1 struct {
Name string
Age int
contact *Contact1
}
type Address struct {
Province string
city string
}
type User2 struct {
Name string
Age int
Address Address
}
+50
View File
@@ -0,0 +1,50 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:08
* @Descriptionmap和切片的比较方法
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"testing"
)
func TestNilEqualEmpty(t *testing.T) {
var s1 []int
var s2 = make([]int, 0)
var m1 map[int]int
var m2 = make(map[int]int)
//Go原生的" == "符号是没办法比较 切片和map的.
//fmt.Println(s1 == s2)
//fmt.Println(m1 == m2)
//这边可以通过Equal来比较 切片和map
fmt.Println("s1 equals s2?", cmp.Equal(s1, s2))
fmt.Println("m1 equals m2?", cmp.Equal(m1, m2))
//空的切片和nil如果要比较相等,需要加上对应的参数
fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.EquateEmpty()))
fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.EquateEmpty()))
}
//比较无序的切片,和本来就没有顺序的map
func TestSliceOutOfOrder(t *testing.T) {
s1 := []int{1, 2, 3, 4}
s2 := []int{4, 3, 2, 1}
fmt.Println("s1 equals s2?", cmp.Equal(s1, s2))
fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.SortSlices(func(i, j int) bool { return i < j })))
fmt.Println()
m1 := map[int]int{1: 10, 2: 20, 3: 30}
m2 := map[int]int{1: 10, 2: 20, 3: 30}
fmt.Println("m1 equals m2?", cmp.Equal(m1, m2))
fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.SortMaps(func(i, j int) bool { return i < j })))
}
+75
View File
@@ -0,0 +1,75 @@
/*
* @Author : huangzj
* @Time : 2020/12/31 14:33
* @Description: 比较的时候转换对应的属性,改变结构体对比的规则
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
type UserO struct {
Name string
Age int
}
func omitAge(u UserO) string {
return u.Name
}
type UserO2 struct {
Name string
Age int
Email string
Address string
}
func omitAge2(u UserO2) UserO2 {
return UserO2{u.Name, 0, u.Email, u.Address}
}
//对某个字段忽略比较,也就是返回字段默认值
func TestOmit(t *testing.T) {
u1 := UserO{Name: "dj", Age: 18}
u2 := UserO{Name: "dj", Age: 28}
//i think 这边omitAge只返回name属性应该是值比较UserO的name属性即可
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Transformer("omitAge", omitAge)))
u3 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"}
u4 := UserO2{Name: "dj", Age: 28, Email: "dj@example.com"}
u5 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"}
u6 := UserO2{Name: "dj1", Age: 28, Email: "dj@example.com"}
//i think 这边的omitAge2返回修改之后的UserO2,只把age设置为0,其他属性不变,说明还是会参与到比较
fmt.Println("u3 equals u4?", cmp.Equal(u3, u4, cmp.Transformer("omitAge", omitAge2)))
fmt.Println("u5 equals u5?", cmp.Equal(u5, u6, cmp.Transformer("omitAge", omitAge2)))
}
type NetAddrO struct {
IP string
Port int
}
func transformLocalhost(a NetAddrO) NetAddrO {
if a.IP == "localhost" {
return NetAddrO{IP: "127.0.0.1", Port: a.Port}
}
return a
}
//对结构体进行对比的时候,修改某字段的值
func TestTransformValue(t *testing.T) {
a1 := NetAddrO{"127.0.0.1", 5000}
a2 := NetAddrO{"localhost", 5000}
//这边是把NetAddr对应的IP进行转换,localhost == 127.0.0.1 所以直接进行转换,比较的结果就会一致
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2, cmp.Transformer("localhost", transformLocalhost)))
}
+7
View File
@@ -0,0 +1,7 @@
获取包命令:go get -u github.com/google/go-cmp
参考地址:https://segmentfault.com/a/1190000021997004
参考地址:https://github.com/google/go-cmp
参考地址:https://github.com/darjun/go-daily-lib
+127
View File
@@ -0,0 +1,127 @@
/*
* @Author : huangzj
* @Time : 2020/11/12 14:04
* @Description
*/
package linqUse
import "time"
type Book struct {
Name string
Author string
Money float64
WordsNum int
PublishTime time.Time
}
func MakeBook() []Book {
bookList := make([]Book, 0)
bookList = append(bookList, Book{
Name: "Go语言",
Author: "Go",
Money: 100,
WordsNum: 1000,
PublishTime: time.Date(2020, 1, 1, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Effective Java",
Author: "Java",
Money: 78,
WordsNum: 9000,
PublishTime: time.Date(2020, 2, 15, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Java语言",
Author: "Java",
Money: 50,
WordsNum: 3000,
PublishTime: time.Date(2020, 2, 1, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Lua语言",
Author: "Lua",
Money: 75,
WordsNum: 45000,
PublishTime: time.Date(2020, 1, 10, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "React语言",
Author: "React",
Money: 99,
WordsNum: 14500,
PublishTime: time.Date(2020, 7, 1, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Red语言",
Author: "Red",
Money: 28,
WordsNum: 880,
PublishTime: time.Date(2019, 4, 1, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "JavaScript语言",
Author: "JavaScript",
Money: 81,
WordsNum: 3776,
PublishTime: time.Date(2019, 5, 17, 10, 0, 0, 0, time.Local),
})
return bookList
}
func MakeBook1() []Book {
bookList := make([]Book, 0)
bookList = append(bookList, Book{
Name: "Go语言",
Author: "Go",
Money: 100,
WordsNum: 1000,
PublishTime: time.Date(2020, 1, 1, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Effective Java",
Author: "Java",
Money: 78,
WordsNum: 9000,
PublishTime: time.Date(2020, 2, 15, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Go语言(第二版)",
Author: "Go",
Money: 50,
WordsNum: 3000,
PublishTime: time.Date(2020, 2, 1, 10, 0, 0, 0, time.Local),
})
return bookList
}
func MakeBook2() []Book {
bookList := make([]Book, 0)
bookList = append(bookList, Book{
Name: "Go语言",
Author: "Go",
Money: 100,
WordsNum: 1000,
PublishTime: time.Date(2020, 1, 1, 10, 0, 0, 0, time.Local),
})
return bookList
}
func MakeBook3() []Book {
bookList := make([]Book, 0)
bookList = append(bookList, Book{
Name: "Go语言",
Author: "Go",
Money: 100,
WordsNum: 1000,
PublishTime: time.Date(2020, 1, 1, 10, 0, 0, 0, time.Local),
})
bookList = append(bookList, Book{
Name: "Go语言(第三版)",
Author: "Go",
Money: 50,
WordsNum: 3000,
PublishTime: time.Date(2020, 2, 1, 10, 0, 0, 0, time.Local),
})
return bookList
}
+57
View File
@@ -0,0 +1,57 @@
/*
* @Author : huangzj
* @Time : 2020/11/24 16:17
* @Description
*/
package linqUse
type Person struct {
Name string //拥有者的名字
}
type Pet struct {
Name string //动物的名字
OwnerName string //拥有者的名字
}
type Man struct {
Name string //拥有者的名字
Pets []Pets //宠物们
}
type Pets struct {
Name string //动物的名字
}
func MakeInnerData() []Man {
man := make([]Man, 0)
man = append(man, Man{
Name: "康康",
Pets: []Pets{{Name: "康康的狗"}, {Name: "康康的猫"}},
})
man = append(man, Man{
Name: "老施",
Pets: []Pets{{Name: "老施的🐟"}, {Name: "老施的鸟"}},
})
man = append(man, Man{
Name: "小明",
Pets: []Pets{{Name: "小明的🐖"}, {Name: "小明的狗"}},
})
return man
}
func MakeJoinData() ([]Person, []Pet) {
kangkang := Person{Name: "爱吃合合乐的康康"}
laoshi := Person{Name: "老施"}
xiaoming := Person{Name: "不要催-小明"}
expect := Person{Name: "我是没有宠物的人"}
dog := Pet{Name: "康康的狗", OwnerName: kangkang.Name}
cat := Pet{Name: "康康的猫", OwnerName: kangkang.Name}
fish := Pet{Name: "老施的🐟", OwnerName: laoshi.Name}
pig := Pet{Name: "小明的🐖", OwnerName: xiaoming.Name}
return []Person{kangkang, laoshi, xiaoming, expect}, []Pet{dog, cat, fish, pig}
}

Some files were not shown because too many files have changed in this diff Show More