diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2022-08-13 15:11:17 -0600 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2022-08-13 15:18:11 -0600 |
commit | 234e0836f1040f7724251b4120a2351bcbf64131 (patch) | |
tree | 201b30fcc99eed470ae345a9bbe594f7ee7a1178 /struct.go | |
parent | f2769bd863521cf316ec9237a498bfa4ecaa115f (diff) |
set up as a separate repo
Diffstat (limited to 'struct.go')
-rw-r--r-- | struct.go | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/struct.go b/struct.go new file mode 100644 index 0000000..ad142d6 --- /dev/null +++ b/struct.go @@ -0,0 +1,164 @@ +// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com> +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "reflect" +) + +type structField struct { + Name string + Path []int + Tagged bool + OmitEmpty bool + Quote bool +} + +type structIndex struct { + byPos []structField + byName map[string]int +} + +func indexStruct(typ reflect.Type) structIndex { + byName := make(map[string][]structField) + var byPos []string + + indexStructInner(typ, nil, byName, &byPos) + + ret := structIndex{ + byName: make(map[string]int), + } + + for _, name := range byPos { + fields := byName[name] + delete(byName, name) + switch len(fields) { + case 0: + // do nothing + case 1: + ret.byName[name] = len(ret.byPos) + ret.byPos = append(ret.byPos, fields[0]) + default: + // To quote the encoding/json docs (version 1.18.4): + // + // If there are multiple fields at the same level, and that level is the + // least nested (and would therefore be the nesting level selected by the + // usual Go rules), the following extra rules apply: + // + // 1) Of those fields, if any are JSON-tagged, only tagged fields are + // considered, even if there are multiple untagged fields that would + // otherwise conflict. + // + // 2) If there is exactly one field (tagged or not according to the first + // rule), that is selected. + // + // 3) Otherwise there are multiple fields, and all are ignored; no error + // occurs. + leastLevel := len(fields[0].Path) + for _, field := range fields[1:] { + if len(field.Path) < leastLevel { + leastLevel = len(field.Path) + } + } + var numUntagged, numTagged int + var untaggedIdx, taggedIdx int + for i, field := range fields { + if len(field.Path) != leastLevel { + continue + } + if field.Tagged { + numTagged++ + taggedIdx = i + if numTagged > 1 { + break // optimization + } + } else { + numUntagged++ + untaggedIdx = i + } + } + switch numTagged { + case 0: + switch numUntagged { + case 0: + // do nothing + case 1: + ret.byName[name] = len(ret.byPos) + ret.byPos = append(ret.byPos, fields[untaggedIdx]) + } + case 1: + ret.byName[name] = len(ret.byPos) + ret.byPos = append(ret.byPos, fields[taggedIdx]) + } + } + } + + return ret +} + +func indexStructInner(typ reflect.Type, prefix []int, byName map[string][]structField, byPos *[]string) { + n := typ.NumField() + for i := 0; i < n; i++ { + path := append(append([]int(nil), prefix...), i) + + fTyp := typ.Field(i) + var embed bool + if fTyp.Anonymous { + t := fTyp.Type + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + if !fTyp.IsExported() && t.Kind() != reflect.Struct { + continue + } + embed = t.Kind() == reflect.Struct + } else if !fTyp.IsExported() { + continue + } + tag := fTyp.Tag.Get("json") + if tag == "-" { + continue + } + tagName, opts := parseTag(tag) + name := tagName + if name == "" { + name = fTyp.Name + } + + if embed { + t := fTyp.Type + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + indexStructInner(t, path, byName, byPos) + } else { + byName[name] = append(byName[name], structField{ + Name: name, + Path: path, + Tagged: tagName != "", + OmitEmpty: opts.Contains("omitempty"), + Quote: opts.Contains("string") && isQuotable(fTyp.Type), + }) + *byPos = append(*byPos, name) + } + } +} + +func isQuotable(typ reflect.Type) bool { + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } + switch typ.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: + return true + default: + return false + } +} |