diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2023-02-07 12:45:46 -0700 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2023-02-07 14:05:37 -0700 |
commit | 643cbc4d6e37d07619bec05039da1abb411d28d4 (patch) | |
tree | 68f771d5103d0243ed49b21ff896f01e49a81a72 /struct.go | |
parent | 2b9473f5e8816eeea76b2fdada184532be00d3a2 (diff) |
Move struct-handling to internal/jsonstruct
Diffstat (limited to 'struct.go')
-rw-r--r-- | struct.go | 205 |
1 files changed, 0 insertions, 205 deletions
diff --git a/struct.go b/struct.go deleted file mode 100644 index 5ccb62f..0000000 --- a/struct.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (C) 2022-2023 Luke Shumaker <lukeshu@lukeshu.com> -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "reflect" - - "git.lukeshu.com/go/typedsync" - - "git.lukeshu.com/go/lowmemjson/internal/jsontags" -) - -type structField struct { - Name string - Path []int - Tagged bool - OmitEmpty bool - Quote bool -} - -// A structIndex is used by Decoder.Decode() and Encoder.Encode() when -// decoding-to or encoding-from a struct. -type structIndex struct { - byPos []structField - byName map[string]int -} - -var structIndexCache typedsync.CacheMap[reflect.Type, structIndex] - -// indexStruct takes a struct Type, and indexes its fields for use by -// Decoder.Decode() and Encoder.Encode(). indexStruct caches its -// results. -func indexStruct(typ reflect.Type) structIndex { - ret, _ := structIndexCache.LoadOrCompute(typ, indexStructReal) - return ret -} - -// indexStructReal is like indexStruct, but is the real indexer, -// bypassing the cache. -func indexStructReal(typ reflect.Type) structIndex { - var byPos []structField - byName := make(map[string][]int) - - indexStructInner(typ, &byPos, byName, nil, map[reflect.Type]struct{}{}) - - ret := structIndex{ - byName: make(map[string]int), - } - - for curPos, _field := range byPos { - name := _field.Name - fieldPoss := byName[name] - switch len(fieldPoss) { - case 0: - // do nothing - case 1: - ret.byName[name] = len(ret.byPos) - ret.byPos = append(ret.byPos, _field) - 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(byPos[fieldPoss[0]].Path) - for _, fieldPos := range fieldPoss[1:] { - field := byPos[fieldPos] - if len(field.Path) < leastLevel { - leastLevel = len(field.Path) - } - } - var numUntagged, numTagged int - var untaggedPos, taggedPos int - for _, fieldPos := range fieldPoss { - field := byPos[fieldPos] - if len(field.Path) != leastLevel { - continue - } - if field.Tagged { - numTagged++ - taggedPos = fieldPos - if numTagged > 1 { - break // optimization - } - } else { - numUntagged++ - untaggedPos = fieldPos - } - } - switch numTagged { - case 0: - switch numUntagged { - case 0: - // do nothing - case 1: - if curPos == untaggedPos { - ret.byName[name] = len(ret.byPos) - ret.byPos = append(ret.byPos, byPos[curPos]) - } - } - case 1: - if curPos == taggedPos { - ret.byName[name] = len(ret.byPos) - ret.byPos = append(ret.byPos, byPos[curPos]) - } - } - } - } - - return ret -} - -// indexStructInner crawls the struct `typ`, storing information on -// all struct fields found in to `byPos` and `byName`. If `typ` -// contains other structs as fields, indexStructInner will recurse and -// call itself; keeping track of stack information with `stackPath` -// (which identifies where we are in the parent struct) and -// `stackSeen` (which is used for detecting loops). -func indexStructInner(typ reflect.Type, byPos *[]structField, byName map[string][]int, stackPath []int, stackSeen map[reflect.Type]struct{}) { - if _, ok := stackSeen[typ]; ok { - return - } - stackSeen[typ] = struct{}{} - defer delete(stackSeen, typ) - - n := typ.NumField() - for i := 0; i < n; i++ { - stackPath := append(stackPath, 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 := jsontags.ParseTag(tag) - name := tagName - if !isValidTag(name) { - name = "" - } - if name == "" { - name = fTyp.Name - } - - if embed && tagName == "" { - t := fTyp.Type - if t.Kind() == reflect.Pointer { - t = t.Elem() - } - indexStructInner(t, byPos, byName, stackPath, stackSeen) - } else { - byName[name] = append(byName[name], len(*byPos)) - *byPos = append(*byPos, structField{ - Name: name, - Path: append([]int(nil), stackPath...), - Tagged: tagName != "", - OmitEmpty: opts.Contains("omitempty"), - Quote: opts.Contains("string") && isQuotable(fTyp.Type), - }) - } - } -} - -// isQuotable returns whether a type is eligible for `json:,string` -// quoting. -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 - } -} |