summaryrefslogtreecommitdiff
path: root/vendor/github.com/pquerna/ffjson/inception/reflect.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pquerna/ffjson/inception/reflect.go')
-rw-r--r--vendor/github.com/pquerna/ffjson/inception/reflect.go290
1 files changed, 290 insertions, 0 deletions
diff --git a/vendor/github.com/pquerna/ffjson/inception/reflect.go b/vendor/github.com/pquerna/ffjson/inception/reflect.go
new file mode 100644
index 000000000..8fb0bd5cb
--- /dev/null
+++ b/vendor/github.com/pquerna/ffjson/inception/reflect.go
@@ -0,0 +1,290 @@
+/**
+ * Copyright 2014 Paul Querna
+ *
+ * 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 ffjsoninception
+
+import (
+ fflib "github.com/pquerna/ffjson/fflib/v1"
+ "github.com/pquerna/ffjson/shared"
+
+ "bytes"
+ "encoding/json"
+ "reflect"
+ "unicode/utf8"
+)
+
+type StructField struct {
+ Name string
+ JsonName string
+ FoldFuncName string
+ Typ reflect.Type
+ OmitEmpty bool
+ ForceString bool
+ HasMarshalJSON bool
+ HasUnmarshalJSON bool
+ Pointer bool
+ Tagged bool
+}
+
+type FieldByJsonName []*StructField
+
+func (a FieldByJsonName) Len() int { return len(a) }
+func (a FieldByJsonName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a FieldByJsonName) Less(i, j int) bool { return a[i].JsonName < a[j].JsonName }
+
+type StructInfo struct {
+ Name string
+ Obj interface{}
+ Typ reflect.Type
+ Fields []*StructField
+ Options shared.StructOptions
+}
+
+func NewStructInfo(obj shared.InceptionType) *StructInfo {
+ t := reflect.TypeOf(obj.Obj)
+ return &StructInfo{
+ Obj: obj.Obj,
+ Name: t.Name(),
+ Typ: t,
+ Fields: extractFields(obj.Obj),
+ Options: obj.Options,
+ }
+}
+
+func (si *StructInfo) FieldsByFirstByte() map[string][]*StructField {
+ rv := make(map[string][]*StructField)
+ for _, f := range si.Fields {
+ b := string(f.JsonName[1])
+ rv[b] = append(rv[b], f)
+ }
+ return rv
+}
+
+func (si *StructInfo) ReverseFields() []*StructField {
+ var i int
+ rv := make([]*StructField, 0)
+ for i = len(si.Fields) - 1; i >= 0; i-- {
+ rv = append(rv, si.Fields[i])
+ }
+ return rv
+}
+
+const (
+ caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
+)
+
+func foldFunc(key []byte) string {
+ nonLetter := false
+ special := false // special letter
+ for _, b := range key {
+ if b >= utf8.RuneSelf {
+ return "bytes.EqualFold"
+ }
+ upper := b & caseMask
+ if upper < 'A' || upper > 'Z' {
+ nonLetter = true
+ } else if upper == 'K' || upper == 'S' {
+ // See above for why these letters are special.
+ special = true
+ }
+ }
+ if special {
+ return "fflib.EqualFoldRight"
+ }
+ if nonLetter {
+ return "fflib.AsciiEqualFold"
+ }
+ return "fflib.SimpleLetterEqualFold"
+}
+
+type MarshalerFaster interface {
+ MarshalJSONBuf(buf fflib.EncodingBuffer) error
+}
+
+type UnmarshalFaster interface {
+ UnmarshalJSONFFLexer(l *fflib.FFLexer, state fflib.FFParseState) error
+}
+
+var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem()
+var marshalerFasterType = reflect.TypeOf(new(MarshalerFaster)).Elem()
+var unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
+var unmarshalFasterType = reflect.TypeOf(new(UnmarshalFaster)).Elem()
+
+// extractFields returns a list of fields that JSON should recognize for the given type.
+// The algorithm is breadth-first search over the set of structs to include - the top struct
+// and then any reachable anonymous structs.
+func extractFields(obj interface{}) []*StructField {
+ t := reflect.TypeOf(obj)
+ // Anonymous fields to explore at the current level and the next.
+ current := []StructField{}
+ next := []StructField{{Typ: t}}
+
+ // Count of queued names for current level and the next.
+ count := map[reflect.Type]int{}
+ nextCount := map[reflect.Type]int{}
+
+ // Types already visited at an earlier level.
+ visited := map[reflect.Type]bool{}
+
+ // Fields found.
+ var fields []*StructField
+
+ for len(next) > 0 {
+ current, next = next, current[:0]
+ count, nextCount = nextCount, map[reflect.Type]int{}
+
+ for _, f := range current {
+ if visited[f.Typ] {
+ continue
+ }
+ visited[f.Typ] = true
+
+ // Scan f.typ for fields to include.
+ for i := 0; i < f.Typ.NumField(); i++ {
+ sf := f.Typ.Field(i)
+ if sf.PkgPath != "" { // unexported
+ continue
+ }
+ tag := sf.Tag.Get("json")
+ if tag == "-" {
+ continue
+ }
+ name, opts := parseTag(tag)
+ if !isValidTag(name) {
+ name = ""
+ }
+
+ ft := sf.Type
+ ptr := false
+ if ft.Kind() == reflect.Ptr {
+ ptr = true
+ }
+
+ if ft.Name() == "" && ft.Kind() == reflect.Ptr {
+ // Follow pointer.
+ ft = ft.Elem()
+ }
+
+ // Record found field and index sequence.
+ if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
+ tagged := name != ""
+ if name == "" {
+ name = sf.Name
+ }
+
+ var buf bytes.Buffer
+ fflib.WriteJsonString(&buf, name)
+
+ field := &StructField{
+ Name: sf.Name,
+ JsonName: string(buf.Bytes()),
+ FoldFuncName: foldFunc([]byte(name)),
+ Typ: ft,
+ HasMarshalJSON: ft.Implements(marshalerType),
+ HasUnmarshalJSON: ft.Implements(unmarshalerType),
+ OmitEmpty: opts.Contains("omitempty"),
+ ForceString: opts.Contains("string"),
+ Pointer: ptr,
+ Tagged: tagged,
+ }
+
+ fields = append(fields, field)
+
+ if count[f.Typ] > 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.
+ fields = append(fields, fields[len(fields)-1])
+ }
+ continue
+ }
+
+ // Record new anonymous struct to explore in next round.
+ nextCount[ft]++
+ if nextCount[ft] == 1 {
+ next = append(next, StructField{
+ Name: ft.Name(),
+ Typ: ft,
+ })
+ }
+ }
+ }
+ }
+
+ // 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 field index length. Loop over names; for each name, delete
+ // hidden fields by choosing the one dominant field that survives.
+ out := fields[:0]
+ for advance, i := 0, 0; i < len(fields); i += advance {
+ // One iteration per name.
+ // Find the sequence of fields with the name of this first field.
+ fi := fields[i]
+ name := fi.JsonName
+ for advance = 1; i+advance < len(fields); advance++ {
+ fj := fields[i+advance]
+ if fj.JsonName != name {
+ break
+ }
+ }
+ if advance == 1 { // Only one field with this name
+ out = append(out, fi)
+ continue
+ }
+ dominant, ok := dominantField(fields[i : i+advance])
+ if ok {
+ out = append(out, dominant)
+ }
+ }
+
+ fields = out
+
+ return fields
+}
+
+// dominantField looks through the fields, all of which are known to
+// have the same name, to find the single field that dominates the
+// others using Go's embedding rules, modified by the presence of
+// JSON tags. If there are multiple top-level fields, the boolean
+// will be false: This condition is an error in Go and we skip all
+// the fields.
+func dominantField(fields []*StructField) (*StructField, bool) {
+ tagged := -1 // Index of first tagged field.
+ for i, f := range fields {
+ if f.Tagged {
+ if tagged >= 0 {
+ // Multiple tagged fields at the same level: conflict.
+ // Return no field.
+ return nil, false
+ }
+ tagged = i
+ }
+ }
+ if tagged >= 0 {
+ return fields[tagged], true
+ }
+ // All remaining fields have the same length. If there's more than one,
+ // we have a conflict (two fields named "X" at the same level) and we
+ // return no field.
+ if len(fields) > 1 {
+ return nil, false
+ }
+ return fields[0], true
+}