|
|
|
|
@ -23,7 +23,11 @@
|
|
|
|
|
|
|
|
|
|
package reflects
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"activething.com/go/gogo/core/slices"
|
|
|
|
|
"bytes"
|
|
|
|
|
"reflect"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type (
|
|
|
|
|
|
|
|
|
|
@ -35,224 +39,119 @@ type (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func NewRStruct (rtype reflect.Type) *RStruct {
|
|
|
|
|
res := &RStruct{
|
|
|
|
|
Fields : make(RFields,0,rtype.NumField()),
|
|
|
|
|
NameIndex: make(map[string]int),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur := RFields{}
|
|
|
|
|
nxt := RFields{{ RType: rtype }}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
func NewRStruct (t reflect.Type) *RStruct {
|
|
|
|
|
// Anonymous Fields to explore at the current level and the next.
|
|
|
|
|
current := RFields{}
|
|
|
|
|
next := []RField{{RType: t}}
|
|
|
|
|
|
|
|
|
|
// Count of queued names for current level and the next.
|
|
|
|
|
var count, nextCount map[reflect.Type]int
|
|
|
|
|
|
|
|
|
|
// Types already visited at an earlier level.
|
|
|
|
|
visited := map[reflect.Type]bool{}
|
|
|
|
|
|
|
|
|
|
// Fields found.
|
|
|
|
|
var fs []RField
|
|
|
|
|
|
|
|
|
|
// Buffer to run HTMLEscape on RField names.
|
|
|
|
|
var nameEscBuf bytes.Buffer
|
|
|
|
|
var (
|
|
|
|
|
cnt,nxc map[reflect.Type]int
|
|
|
|
|
vis map[reflect.Type]struct{}
|
|
|
|
|
fls RFields
|
|
|
|
|
nms bytes.Buffer
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for len(next) > 0 {
|
|
|
|
|
current, next = next, current[:0]
|
|
|
|
|
count, nextCount = nextCount, map[reflect.Type]int{}
|
|
|
|
|
for len(nxt) > 0 {
|
|
|
|
|
cur, nxt = nxt, cur[:0]
|
|
|
|
|
cnt, nxc = nxc, map[reflect.Type]int{}
|
|
|
|
|
|
|
|
|
|
for _, f := range current {
|
|
|
|
|
if visited[f.RType] {
|
|
|
|
|
for _,f := range cur {
|
|
|
|
|
if _,h := vis[f.RType]; h {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
visited[f.RType] = true
|
|
|
|
|
vis[f.RType]=struct{}{}
|
|
|
|
|
|
|
|
|
|
// Scan f.RType for Fields to include.
|
|
|
|
|
for i := 0; i < f.RType.NumField(); i++ {
|
|
|
|
|
var flags RFieldFlags
|
|
|
|
|
for i :=0; i<f.RType.NumField(); i++ {
|
|
|
|
|
sf := f.RType.Field(i)
|
|
|
|
|
if sf.Anonymous {
|
|
|
|
|
t := sf.Type
|
|
|
|
|
if t.Kind() == reflect.Pointer {
|
|
|
|
|
t = t.Elem()
|
|
|
|
|
st := sf.Type
|
|
|
|
|
if st.Kind() == reflect.Pointer {
|
|
|
|
|
st = st.Elem()
|
|
|
|
|
}
|
|
|
|
|
if !sf.IsExported() && t.Kind() != reflect.Struct {
|
|
|
|
|
// Ignore embedded Fields of unexported non-struct types.
|
|
|
|
|
if !sf.IsExported() && st.Kind() != reflect.Struct {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// Do not ignore embedded Fields of unexported struct types
|
|
|
|
|
// since they may have exported Fields.
|
|
|
|
|
} else if !sf.IsExported() {
|
|
|
|
|
// Ignore unexported non-embedded Fields.
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
tag := sf.Tag.Get("json")
|
|
|
|
|
if tag == "-" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
name, opts := parseTag(tag)
|
|
|
|
|
if !isValidTag(name) {
|
|
|
|
|
name = ""
|
|
|
|
|
|
|
|
|
|
nm := false
|
|
|
|
|
tags,ok := parseTag(sf,"json",nil)
|
|
|
|
|
if ok {
|
|
|
|
|
if tags.Has("-") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if isValidTag(tags[0]) {
|
|
|
|
|
nm=true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
index := make([]int, len(f.Index)+1)
|
|
|
|
|
copy(index, f.Index)
|
|
|
|
|
index[len(f.Index)] = i
|
|
|
|
|
|
|
|
|
|
ft := sf.Type
|
|
|
|
|
if ft.Name() == "" && ft.Kind() == reflect.Pointer {
|
|
|
|
|
// Follow pointer.
|
|
|
|
|
ft = ft.Elem()
|
|
|
|
|
ft=ft.Elem()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only strings, floats, integers, and booleans can be quoted.
|
|
|
|
|
if opts.Contains("string") {
|
|
|
|
|
qt := false
|
|
|
|
|
if tags.Has("string") {
|
|
|
|
|
switch ft.Kind() {
|
|
|
|
|
case reflect.Bool,
|
|
|
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
|
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
|
|
|
|
reflect.Float32, reflect.Float64,
|
|
|
|
|
reflect.String:
|
|
|
|
|
flags|=RFieldQuoted
|
|
|
|
|
qt = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record found RField and index sequence.
|
|
|
|
|
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
|
|
|
|
if name != "" {
|
|
|
|
|
flags |= RFieldFlagTagged
|
|
|
|
|
}
|
|
|
|
|
if name == "" {
|
|
|
|
|
name = sf.Name
|
|
|
|
|
}
|
|
|
|
|
rf := RField{
|
|
|
|
|
Name : name,
|
|
|
|
|
Index: index,
|
|
|
|
|
RType: ft,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opts.Contains("omitempty") {
|
|
|
|
|
rf.Flags|=RFieldOmitEmpty
|
|
|
|
|
}
|
|
|
|
|
index := make([]int, len(f.Index)+1)
|
|
|
|
|
copy(index, f.Index)
|
|
|
|
|
index[len(f.Index)] = i
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rf.BName = []byte(rf.Name)
|
|
|
|
|
//rf.EqualFold = foldFunc(rf.BName)
|
|
|
|
|
var name string
|
|
|
|
|
if nm || sf.Anonymous || ft.Kind() != reflect.Struct {
|
|
|
|
|
if nm {
|
|
|
|
|
name = tags[0]
|
|
|
|
|
} else {
|
|
|
|
|
name = sf.Name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build nameEscHTML and nameNonEsc ahead of time.
|
|
|
|
|
nameEscBuf.Reset()
|
|
|
|
|
nameEscBuf.WriteString(`"`)
|
|
|
|
|
//HTMLEscape(&nameEscBuf, rf.BName)
|
|
|
|
|
nameEscBuf.WriteString(`":`)
|
|
|
|
|
rf.HName = nameEscBuf.String()
|
|
|
|
|
//rf. = `"` + rf.Name + `":`
|
|
|
|
|
rf := RField{
|
|
|
|
|
RField: sf,
|
|
|
|
|
RType: ft,
|
|
|
|
|
Name: name,
|
|
|
|
|
BName: []byte(name),
|
|
|
|
|
Tags: map[string]slices.String{"json": append(slices.String(nil), tags...)},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs = append(fs, rf)
|
|
|
|
|
if count[f.RType] > 1 {
|
|
|
|
|
// If there were multiple instances, add a second,
|
|
|
|
|
// so that the annihilation code will see a duplicate.
|
|
|
|
|
// It only cares about the distinction between 1 or 2,
|
|
|
|
|
// so don't bother generating any more copies.
|
|
|
|
|
fs = append(fs, fs[len(fs)-1])
|
|
|
|
|
res.Fields = append(res.Fields, rf)
|
|
|
|
|
if cnt[f.RType] > 1 {
|
|
|
|
|
res.Fields = append(res.Fields, res.Fields[len(res.Fields)-1])
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record new anonymous struct to explore in next round.
|
|
|
|
|
nextCount[ft]++
|
|
|
|
|
if nextCount[ft] == 1 {
|
|
|
|
|
next = append(next, RField{Name: ft.Name(), Index: index, RType: ft})
|
|
|
|
|
nxc[ft]++
|
|
|
|
|
if nxc[ft] == 1 {
|
|
|
|
|
nxt = append(nxt,RField{
|
|
|
|
|
RType: ft,
|
|
|
|
|
Name: ft.Name(),
|
|
|
|
|
Index: index,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sort.Slice(fs, func(i, j int) bool {
|
|
|
|
|
x := fs
|
|
|
|
|
// sort RField by name, breaking ties with depth, then
|
|
|
|
|
// breaking ties with "name came from json tag", then
|
|
|
|
|
// breaking ties with index sequence.
|
|
|
|
|
if x[i].Name != x[j].Name {
|
|
|
|
|
return x[i].Name < x[j].Name
|
|
|
|
|
}
|
|
|
|
|
if len(x[i].Index) != len(x[j].Index) {
|
|
|
|
|
return len(x[i].Index) < len(x[j].Index)
|
|
|
|
|
}
|
|
|
|
|
if x[i].Tagged() != x[j].Tagged() {
|
|
|
|
|
return x[i].Tagged()
|
|
|
|
|
}
|
|
|
|
|
return RFieldsByIndex(x).Less(i, j)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Delete all Fields that are hidden by the Go rules for embedded Fields,
|
|
|
|
|
// except that Fields with JSON tags are promoted.
|
|
|
|
|
|
|
|
|
|
// The Fields are sorted in primary order of name, secondary order
|
|
|
|
|
// of RField index length. Loop over names; for each name, delete
|
|
|
|
|
// hidden Fields by choosing the one dominant RField that survives.
|
|
|
|
|
out := fs[:0]
|
|
|
|
|
for advance, i := 0, 0; i < len(fs); i += advance {
|
|
|
|
|
// One iteration per name.
|
|
|
|
|
// Find the sequence of Fields with the name of this first RField.
|
|
|
|
|
fi := fs[i]
|
|
|
|
|
name := fi.Name
|
|
|
|
|
for advance = 1; i+advance < len(fs); advance++ {
|
|
|
|
|
fj := fs[i+advance]
|
|
|
|
|
if fj.Name != name {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if advance == 1 { // Only one RField with this name
|
|
|
|
|
out = append(out, fi)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
dominant, ok := RFields(fs[i : i+advance]).Dominant()
|
|
|
|
|
if ok {
|
|
|
|
|
out = append(out, dominant)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs = out
|
|
|
|
|
sort.Sort(RFieldsByIndex(fs))
|
|
|
|
|
|
|
|
|
|
for i := range fs {
|
|
|
|
|
//f := &fs[i]
|
|
|
|
|
//f.encoder = typeEncoder(typeByIndex(t, f.index))
|
|
|
|
|
}
|
|
|
|
|
nameIndex := make(map[string]int, len(fs))
|
|
|
|
|
for i, f := range fs {
|
|
|
|
|
nameIndex[f.Name] = i
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &RStruct{
|
|
|
|
|
Fields: fs,
|
|
|
|
|
FieldsIndex: nameIndex,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
func parseTag(tag string) (string, tagOptions) {
|
|
|
|
|
tag, opt, _ := strings.Cut(tag, ",")
|
|
|
|
|
return tag, tagOptions(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func isValidTag(s string) bool {
|
|
|
|
|
if s == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, c := range s {
|
|
|
|
|
switch {
|
|
|
|
|
case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
|
|
|
|
|
// Backslash and quote chars are reserved, but
|
|
|
|
|
// otherwise any punctuation chars are allowed
|
|
|
|
|
// in a tag name.
|
|
|
|
|
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*/
|