func (o *Obj) Field(fieldName string) *ObjField {
	if o.fieldsValue.IsValid() {
		if metadata, found := o.fields[fieldName]; found {
			return newObjField(o, metadata)
		}
	}
	return newObjField(o, ObjFieldMetadata{name: fieldName, valid: false, fieldKind: reflect.Invalid})
}
Field get a field wrapper.
Note that the field name can be invalid.
You can check the field validity using ObjField.IsValid().
package reflector
import (
	"fmt"
	"reflect"
	"strings"
	"sync"
)
type fieldListingType int
const (
	fieldsAll fieldListingType = iota
	fieldsAnonymous
	fieldsFlattenAnonymous
	fieldsNoFlattenAnonymous
)
var (
	metadataCache      map[reflect.Type]ObjMetadata
	metadataCacheMutex sync.RWMutex
)
func init() {
	metadataCache = map[reflect.Type]ObjMetadata{}
}
func updateCache(ty reflect.Type, o *Obj) {
	metadataCacheMutex.Lock()
	defer metadataCacheMutex.Unlock()
	metadataCache[ty] = o.ObjMetadata
}
type ObjMetadata struct {
	isStruct      bool
	isPtrToStruct bool
	underlyingType reflect.Type
	objType reflect.Type
	objKind reflect.Kind
	fields map[string]ObjFieldMetadata
	fieldNamesAll                []string
	fieldNamesAnonymous          []string
	fieldNamesFlattenAnonymous   []string
	fieldNamesNoFlattenAnonymous []string
	methods     map[string]ObjMethodMetadata
	methodNames []string
}
func newObjMetadata(ty reflect.Type) *ObjMetadata {
	res := new(ObjMetadata)
	if ty == nil {
		res.objKind = reflect.Invalid
		return res
	}
	res.objType = ty
	res.objKind = res.objType.Kind()
	if ty.Kind() == reflect.Struct {
		res.isStruct = true
	}
	if ty.Kind() == reflect.Ptr && ty.Elem().Kind() == reflect.Struct {
		ty = ty.Elem()
		res.isPtrToStruct = true
	}
	res.underlyingType = ty
	allFields := res.getFields(res.objType, fieldsAll)
	res.fieldNamesAll = allFields
	res.fieldNamesAnonymous = res.getFields(res.objType, fieldsAnonymous)
	res.fieldNamesFlattenAnonymous = res.getFields(res.objType, fieldsFlattenAnonymous)
	res.fieldNamesNoFlattenAnonymous = res.getFields(res.objType, fieldsNoFlattenAnonymous)
	res.methods = map[string]ObjMethodMetadata{}
	res.methodNames = []string{}
	if res.objKind != reflect.Invalid {
		res.fields = map[string]ObjFieldMetadata{}
		for _, fieldName := range allFields {
			res.fields[fieldName] = *newObjFieldMetadata(res.objType, fieldName, res)
		}
		for i := 0; i < res.objType.NumMethod(); i++ {
			method := res.objType.Method(i)
			res.methodNames = append(res.methodNames, method.Name)
			res.methods[method.Name] = *newObjMethodMetadata(res.objType, method.Name, res)
		}
	}
	return res
}
func (om *ObjMetadata) IsStructOrPtrToStruct() bool {
	return om.isStruct || om.isPtrToStruct
}
func (om *ObjMetadata) appendFields(fields []string, field reflect.StructField, listingType fieldListingType) []string {
	k := field.Type.Kind()
	if listingType == fieldsAnonymous {
		if field.Anonymous {
			fields = append(fields, field.Name)
		}
	} else if listingType == fieldsAll {
		fields = append(fields, field.Name)
		if k == reflect.Struct && field.Anonymous {
			fields = append(fields, om.getFields(field.Type, listingType)...)
		}
	} else {
		if listingType == fieldsFlattenAnonymous && k == reflect.Struct && field.Anonymous {
			fields = append(fields, om.getFields(field.Type, listingType)...)
		} else {
			fields = append(fields, field.Name)
		}
	}
	return fields
}
func (om *ObjMetadata) getFields(ty reflect.Type, listingType fieldListingType) []string {
	var fields []string
	if ty.Kind() == reflect.Ptr {
		ty = ty.Elem()
	}
	if ty.Kind() != reflect.Struct {
		return fields  
	}
	for i := 0; i < ty.NumField(); i++ {
		f := ty.Field(i)
		fields = om.appendFields(fields, f, listingType)
	}
	return fields
}
type ObjFieldMetadata struct {
	name string
	structField reflect.StructField
	valid bool
	fieldKind reflect.Kind
	fieldType reflect.Type
}
func newObjFieldMetadata(ty reflect.Type, name string, objMetadata *ObjMetadata) *ObjFieldMetadata {
	res := &ObjFieldMetadata{}
	res.fieldKind = reflect.Invalid
	res.name = name
	if objMetadata.IsStructOrPtrToStruct() {
		var found bool
		var structField reflect.StructField
		if objMetadata.isPtrToStruct {
			structField, found = objMetadata.objType.Elem().FieldByName(res.name)
		} else {
			structField, found = objMetadata.objType.FieldByName(res.name)
		}
		res.structField = structField
		res.fieldType = structField.Type
		if res.fieldType == nil {
			res.valid = false
		} else {
			res.fieldKind = structField.Type.Kind()
			res.valid = found
		}
	}
	return res
}
type ObjMethodMetadata struct {
	name   string
	method reflect.Method
	valid  bool
}
func newObjMethodMetadata(ty reflect.Type, name string, objMetadata *ObjMetadata) *ObjMethodMetadata {
	res := &ObjMethodMetadata{name: name}
	if objMetadata.objKind == reflect.Invalid {
		res.valid = false
	} else {
		if method, found := objMetadata.objType.MethodByName(name); found {
			res.method = method
			res.valid = res.method.Func.IsValid()
		} else {
			res.valid = false
		}
	}
	return res
}
type Obj struct {
	iface interface{}
	fieldsValue reflect.Value
	ObjMetadata
}
func NewFromType(ty reflect.Type) *Obj {
	if ty == nil {
		return New(nil)
	}
	return New(reflect.New(ty).Interface())
}
func New(obj interface{}) *Obj {
	o := &Obj{iface: obj}
	ty := reflect.TypeOf(obj)
	metadataCacheMutex.RLock()
	metadata, found := metadataCache[ty]
	metadataCacheMutex.RUnlock()
	if found {
		o.ObjMetadata = metadata
	} else {
		o.ObjMetadata = *newObjMetadata(reflect.TypeOf(obj))
		updateCache(ty, o)
	}
	o.fieldsValue = reflect.Indirect(reflect.ValueOf(obj))
	return o
}
func (o *Obj) IsValid() bool {
	return o.objKind != reflect.Invalid
}
func (o *Obj) Len() int {
	switch o.fieldsValue.Kind() {
	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
		return o.fieldsValue.Len()
	}
	return 0
}
func (o *Obj) IsGettableByIndex() bool {
	switch o.fieldsValue.Kind() {
	case reflect.Array, reflect.Slice, reflect.String:
		return true
	default:
		return false
	}
}
func (o *Obj) IsSettableByIndex() bool {
	switch o.fieldsValue.Kind() {
	case reflect.Array, reflect.Slice:
		return true
	default:
		return false
	}
}
func (o *Obj) IsMap() bool {
	switch o.fieldsValue.Kind() {
	case reflect.Map:
		return true
	default:
		return false
	}
}
func (o *Obj) GetByIndex(index int) (value interface{}, found bool) {
	defer func() {
		if err := recover(); err != nil {
			value = nil
			found = false
		}
	}()
	if o.IsGettableByIndex() {
		if 0 <= index && index < o.fieldsValue.Len() {
			value = o.fieldsValue.Index(index).Interface()
			found = true
			return
		}
	}
	return
}
func (o *Obj) SetByIndex(index int, val interface{}) error {
	if index < 0 || o.Len() <= index {
		return fmt.Errorf("cannot set element %d", index)
	}
	if o.IsSettableByIndex() {
		elem := o.fieldsValue.Index(index)
		elem.Set(reflect.ValueOf(val))
		return nil
	}
	return fmt.Errorf("cannot set element %d of %s", index, o.fieldsValue.String())
}
func (o *Obj) Keys() ([]interface{}, error) {
	if o.IsMap() {
		keys := o.fieldsValue.MapKeys()
		res := make([]interface{}, len(keys))
		for n := range keys {
			res[n] = keys[n].Interface()
		}
		return res, nil
	}
	return nil, fmt.Errorf("invalid type %s", o.Type().String())
}
func (o *Obj) SetByKey(key interface{}, val interface{}) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("cannot set key %s: %v", key, e)
		}
	}()
	if o.IsMap() {
		o.fieldsValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(val))
		return
	}
	err = fmt.Errorf("cannot set key %s: %w", key, err)
	return
}
func (o *Obj) GetByKey(key interface{}) (value interface{}, found bool) {
	defer func() {
		if err := recover(); err != nil {
			value = nil
			found = false
		}
	}()
	if o.IsMap() {
		v := o.fieldsValue.MapIndex(reflect.ValueOf(key))
		if !v.IsValid() {
			found = false
			return
		}
		value = v.Interface()
		found = true
		return
	}
	return
}
func (o *Obj) Fields() []ObjField {
	return o.getFields(fieldsNoFlattenAnonymous)
}
func (o Obj) FieldsFlattened() []ObjField {
	return o.getFields(fieldsFlattenAnonymous)
}
func (o Obj) FieldsAll() []ObjField {
	return o.getFields(fieldsAll)
}
func (o Obj) FieldsAnonymous() []ObjField {
	return o.getFields(fieldsAnonymous)
}
func (o *Obj) getFields(listingType fieldListingType) []ObjField {
	var fieldNames []string
	switch listingType {
	case fieldsAll:
		fieldNames = o.fieldNamesAll
	case fieldsAnonymous:
		fieldNames = o.fieldNamesAnonymous
	case fieldsFlattenAnonymous:
		fieldNames = o.fieldNamesFlattenAnonymous
	case fieldsNoFlattenAnonymous:
		fieldNames = o.fieldNamesNoFlattenAnonymous
	default:
		panic(fmt.Sprintf("Invalid field listing type %d", listingType))
	}
	res := make([]ObjField, len(fieldNames))
	for n, fieldName := range fieldNames {
		res[n] = *o.Field(fieldName)
	}
	return res
}
func (o Obj) FindDoubleFields() []string {
	fields := map[string]int{}
	res := []string{}
	for _, f := range o.FieldsAll() {
		counter := fields[f.name]
		if counter == 1 {
			res = append(res, f.name)
		}
		fields[f.name] = counter + 1
	}
	return res
}
func (o Obj) IsPtr() bool {
	return o.objKind == reflect.Ptr
}