func (n *Node) TimeString(timeFmt string) string {
	if !n.IsTime() {
		panic(fmt.Sprintf("field '%v' must be a 'time.Time' value type but is '%v' instead",
			n.FullName(),
			n.ValueType(),
		))
	}
	timeFmt = NormTimeFormat(timeFmt)
	tv := n.FieldValue.Interface().(time.Time)
	if tv.IsZero() {
		return ""
	}
	return tv.Format(timeFmt)
}
TimeString is a special case method for handling "time.Time" field types.

It's a separate method because it needs the timeFmt format information
in order to know the string format.

TimeString panics if the value type is not "time.Time".

timeFmt can be an exact go time format definition or the format name
as it appears in the common time package formats. For example if the value
of timeFmt == "RFC3339" then time.RFC3339 format will be used.
package node
import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
	"github.com/pcelvng/go-config/util"
)
type Nodes struct {
	nodesMap   map[string]*Node  
	nodesSlice []*Node           
	v          interface{}       
}
func (ns *Nodes) Map() map[string]*Node {
	return ns.nodesMap
}
func (ns *Nodes) List() []*Node {
	return ns.nodesSlice
}
func (ns *Nodes) StructPtr() interface{} {
	return ns.v
}
func (ns *Nodes) SetTag(fieldName, key, value string) error {
	n, ok := ns.nodesMap[fieldName]
	if !ok {
		return errors.New("unable to set field tag: no field found by name '" + fieldName + "'")
	}
	n.SetTag(key, value)
	return nil
}
type Node struct {
	Prefix string  
	FieldValue reflect.Value
	Field      reflect.StructField
	Index int
	tag map[string]string
	meta map[string]string
}
func (n *Node) FieldName() string {
	return n.Field.Name
}
func (n *Node) FullName() string {
	if n.Prefix == "" {
		return n.FieldName()
	}
	return n.Prefix + "." + n.FieldName()
}
func (n *Node) ParentName() string {
	return n.Prefix
}
func (n *Node) ParentsNames() []string {
	names := make([]string, 0)
	if n.ParentName() == "" {
		return names
	}
	lineage := strings.Split(n.ParentName(), ".")
	for i := 0; i < len(lineage); i++ {
		names = append(names, strings.Join(lineage[:i+1], "."))
	}
	return names
}
func (n *Node) ValueType() string {
	return n.FieldValue.Type().String()
}
func (n *Node) Kind() reflect.Kind {
	return n.FieldValue.Kind()
}
func (n *Node) SetTag(key, value string) {
	if value == "" || key == "" {
		return
	}
	n.tag[key] = value
}
func (n *Node) GetTag(key string) string {
	if v, ok := n.tag[key]; ok {
		return v
	}
	return n.Field.Tag.Get(key)
}
func (n *Node) SetMeta(key, value string) {
	if value == "" || key == "" {
		return
	}
	n.meta[key] = value
}
func (n *Node) GetMeta(key string) string {
	return n.meta[key]
}
func (n *Node) GetBoolTag(key string) bool {
	bv, _ := strconv.ParseBool(n.GetTag(key))
	return bv
}
func (n *Node) SetBoolTag(key string, value bool) {
	n.SetTag(key, strconv.FormatBool(value))
}
func (n *Node) IsStruct() bool {
	return n.Kind() == reflect.Struct
}
func (n *Node) IsString() bool {
	return n.Kind() == reflect.String
}
func (n *Node) IsDuration() bool {
	return n.ValueType() == "time.Duration"
}
func (n *Node) IsTime() bool {
	if n == nil {
		return false
	}
	return n.ValueType() == "time.Time"
}
func (n *Node) IsSlice() bool {
	return n.Kind() == reflect.Slice
}
func (n *Node) IsStringSlice() bool {
	if n.Kind() == reflect.Slice {
		baseType := reflect.TypeOf(n.FieldValue.Interface()).Elem()
		return baseType.Kind() == reflect.String
	}
	return false
}
func (n *Node) IsBool() bool {
	return n.Kind() == reflect.Bool
}
func fieldString(value reflect.Value) string {
	switch value.Kind() {
	case reflect.String:
		return value.String()
	case reflect.Bool:
		return strconv.FormatBool(value.Bool())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if value.Type().String() == "time.Duration" {
			return value.Interface().(time.Duration).String()
		}
		return strconv.FormatInt(value.Int(), 10)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return strconv.FormatUint(value.Uint(), 10)
	case reflect.Float32, reflect.Float64:
		f := fmt.Sprintf("%f", value.Float())  
		f = strings.TrimRight(f, "0")
		return strings.TrimRight(f, ".")  
	default:
		panic(fmt.Sprintf("unexpected king to convert to string '%v'", value.Kind().String()))
	}
}
func (n *Node) String() string {
	return fieldString(n.FieldValue)
}
func (n *Node) SliceString() []string {
	if !n.IsSlice() {
		panic(fmt.Sprintf("field value '%v' must be a slice and instead was '%v'",
			n.FullName(),
			n.FieldValue.Kind().String()),
		)
	}
	items := make([]string, 0)
	for i := 0; i < n.FieldValue.Len(); i++ {
		itemValue := n.FieldValue.Index(i)
		if itemValue.Kind() == reflect.Ptr {
			itemValue = itemValue.Elem()
		}
		items = append(items, fieldString(itemValue))
	}
	return items
}