You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
2.8 KiB
130 lines
2.8 KiB
package yaml |
|
|
|
import ( |
|
"reflect" |
|
"strings" |
|
|
|
"golang.org/x/xerrors" |
|
) |
|
|
|
const ( |
|
// StructTagName tag keyword for Marshal/Unmarshal |
|
StructTagName = "yaml" |
|
) |
|
|
|
// StructField information for each the field in structure |
|
type StructField struct { |
|
FieldName string |
|
RenderName string |
|
AnchorName string |
|
AliasName string |
|
IsAutoAnchor bool |
|
IsAutoAlias bool |
|
IsOmitEmpty bool |
|
IsFlow bool |
|
IsInline bool |
|
} |
|
|
|
func getTag(field reflect.StructField) string { |
|
// If struct tag `yaml` exist, use that. If no `yaml` |
|
// exists, but `json` does, use that and try the best to |
|
// adhere to its rules |
|
tag := field.Tag.Get(StructTagName) |
|
if tag == "" { |
|
tag = field.Tag.Get(`json`) |
|
} |
|
return tag |
|
} |
|
|
|
func structField(field reflect.StructField) *StructField { |
|
tag := getTag(field) |
|
fieldName := strings.ToLower(field.Name) |
|
options := strings.Split(tag, ",") |
|
if len(options) > 0 { |
|
if options[0] != "" { |
|
fieldName = options[0] |
|
} |
|
} |
|
structField := &StructField{ |
|
FieldName: field.Name, |
|
RenderName: fieldName, |
|
} |
|
if len(options) > 1 { |
|
for _, opt := range options[1:] { |
|
switch { |
|
case opt == "omitempty": |
|
structField.IsOmitEmpty = true |
|
case opt == "flow": |
|
structField.IsFlow = true |
|
case opt == "inline": |
|
structField.IsInline = true |
|
case strings.HasPrefix(opt, "anchor"): |
|
anchor := strings.Split(opt, "=") |
|
if len(anchor) > 1 { |
|
structField.AnchorName = anchor[1] |
|
} else { |
|
structField.IsAutoAnchor = true |
|
} |
|
case strings.HasPrefix(opt, "alias"): |
|
alias := strings.Split(opt, "=") |
|
if len(alias) > 1 { |
|
structField.AliasName = alias[1] |
|
} else { |
|
structField.IsAutoAlias = true |
|
} |
|
default: |
|
} |
|
} |
|
} |
|
return structField |
|
} |
|
|
|
func isIgnoredStructField(field reflect.StructField) bool { |
|
if field.PkgPath != "" && !field.Anonymous { |
|
// private field |
|
return true |
|
} |
|
tag := getTag(field) |
|
if tag == "-" { |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
type StructFieldMap map[string]*StructField |
|
|
|
func (m StructFieldMap) isIncludedRenderName(name string) bool { |
|
for _, v := range m { |
|
if v.RenderName == name { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func (m StructFieldMap) hasMergeProperty() bool { |
|
for _, v := range m { |
|
if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func structFieldMap(structType reflect.Type) (StructFieldMap, error) { |
|
structFieldMap := StructFieldMap{} |
|
renderNameMap := map[string]struct{}{} |
|
for i := 0; i < structType.NumField(); i++ { |
|
field := structType.Field(i) |
|
if isIgnoredStructField(field) { |
|
continue |
|
} |
|
structField := structField(field) |
|
if _, exists := renderNameMap[structField.RenderName]; exists { |
|
return nil, xerrors.Errorf("duplicated struct field name %s", structField.RenderName) |
|
} |
|
structFieldMap[structField.FieldName] = structField |
|
renderNameMap[structField.RenderName] = struct{}{} |
|
} |
|
return structFieldMap, nil |
|
}
|
|
|