// ------------------------------------------------------------------------ // Project atila // Active Thing (activething.com) git.activething.com/go // // File name rRFields_type.go // Created by DEV // Modified 26/01/2024 // // Copyright 2024 activething.com // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ------------------------------------------------------------------------ package reflects type ( RStruct struct { Fields RFields NameIndex map[string]int } ) /* 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 for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[reflect.Type]int{} for _, f := range current { if visited[f.RType] { continue } visited[f.RType] = true // Scan f.RType for Fields to include. for i := 0; i < f.RType.NumField(); i++ { var flags RFieldFlags sf := f.RType.Field(i) if sf.Anonymous { t := sf.Type if t.Kind() == reflect.Pointer { t = t.Elem() } if !sf.IsExported() && t.Kind() != reflect.Struct { // Ignore embedded Fields of unexported non-struct types. 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 = "" } 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() } // Only strings, floats, integers, and booleans can be quoted. if opts.Contains("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 } } // 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 } rf.BName = []byte(rf.Name) //rf.EqualFold = foldFunc(rf.BName) // Build nameEscHTML and nameNonEsc ahead of time. nameEscBuf.Reset() nameEscBuf.WriteString(`"`) //HTMLEscape(&nameEscBuf, rf.BName) nameEscBuf.WriteString(`":`) rf.HName = nameEscBuf.String() //rf. = `"` + rf.Name + `":` 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]) } 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}) } } } } 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, } } 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 } */