Skip to content

Commit c0f2c8f

Browse files
committed
imp
1 parent b2295f9 commit c0f2c8f

4 files changed

Lines changed: 364 additions & 123 deletions

File tree

internal/common/common.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ type Common struct {
1111

1212
// FieldInfo holds information about a struct field including its path for embedded structs
1313
type FieldInfo struct {
14-
Path []int // path to reach this field (indices for embedded structs)
15-
Name string // field name or tag
16-
Omit bool // omitempty flag
14+
Path []int // path to reach this field (indices for embedded structs)
15+
Name string // field name or tag
16+
Omit bool // omitempty flag
17+
Tagged bool // tag name explicitly set
18+
OmitPaths [][]int // paths to embedded fields with omitempty
1719
}
1820

1921
// CollectFields collects all fields from a struct, expanding embedded structs
2022
// following the same rules as encoding/json
2123
func (c *Common) CollectFields(t reflect.Type, path []int) []FieldInfo {
24+
return c.collectFields(t, path, nil)
25+
}
26+
27+
func (c *Common) collectFields(t reflect.Type, path []int, omitPaths [][]int) []FieldInfo {
2228
var fields []FieldInfo
2329
var embedded []FieldInfo // embedded fields to process later (lower priority)
2430

@@ -44,6 +50,7 @@ func (c *Common) CollectFields(t reflect.Type, path []int) []FieldInfo {
4450

4551
// Check if this is an embedded struct
4652
isEmbedded := field.Anonymous && (tag == "" || tagName == "")
53+
tagged := tagName != ""
4754

4855
if isEmbedded {
4956
// Get the actual type (dereference pointer if needed)
@@ -55,7 +62,11 @@ func (c *Common) CollectFields(t reflect.Type, path []int) []FieldInfo {
5562
// If it's a struct, expand its fields
5663
if fieldType.Kind() == reflect.Struct {
5764
newPath := append(append([]int{}, path...), i)
58-
embeddedFields := c.CollectFields(fieldType, newPath)
65+
nextOmitPaths := omitPaths
66+
if omit {
67+
nextOmitPaths = appendOmitPath(omitPaths, newPath)
68+
}
69+
embeddedFields := c.collectFields(fieldType, newPath, nextOmitPaths)
5970
embedded = append(embedded, embeddedFields...)
6071
continue
6172
}
@@ -64,9 +75,11 @@ func (c *Common) CollectFields(t reflect.Type, path []int) []FieldInfo {
6475
// Regular field or embedded non-struct
6576
newPath := append(append([]int{}, path...), i)
6677
fields = append(fields, FieldInfo{
67-
Path: newPath,
68-
Name: name,
69-
Omit: omit,
78+
Path: newPath,
79+
Name: name,
80+
Omit: omit,
81+
Tagged: tagged,
82+
OmitPaths: omitPaths,
7083
})
7184
}
7285

@@ -77,6 +90,16 @@ func (c *Common) CollectFields(t reflect.Type, path []int) []FieldInfo {
7790
return c.deduplicateFields(fields)
7891
}
7992

93+
func appendOmitPath(paths [][]int, path []int) [][]int {
94+
if len(paths) == 0 {
95+
return [][]int{path}
96+
}
97+
newPaths := make([][]int, len(paths)+1)
98+
copy(newPaths, paths)
99+
newPaths[len(paths)] = path
100+
return newPaths
101+
}
102+
80103
// deduplicateFields removes duplicate fields and handles ambiguous fields
81104
// following encoding/json behavior
82105
func (c *Common) deduplicateFields(fields []FieldInfo) []FieldInfo {
@@ -119,9 +142,20 @@ func (c *Common) deduplicateFields(fields []FieldInfo) []FieldInfo {
119142
}
120143

121144
// If there's exactly one field at minimum depth, use it
122-
// If there are multiple fields at the same minimum depth, it's ambiguous - skip it
123145
if len(fieldsAtMinDepth) == 1 {
124146
result = append(result, fieldsAtMinDepth[0])
147+
continue
148+
}
149+
150+
// Prefer the tagged field if exactly one is tagged at minimum depth
151+
var taggedFields []FieldInfo
152+
for _, f := range fieldsAtMinDepth {
153+
if f.Tagged {
154+
taggedFields = append(taggedFields, f)
155+
}
156+
}
157+
if len(taggedFields) == 1 {
158+
result = append(result, taggedFields[0])
125159
}
126160
// else: ambiguous field, skip it (following encoding/json behavior)
127161
}

internal/encoding/struct.go

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import (
1010
)
1111

1212
type structCache struct {
13-
indexes [][]int // field path (support for embedded structs)
14-
names []string
15-
omits []bool
16-
noOmit bool
13+
indexes [][]int // field path (support for embedded structs)
14+
names []string
15+
omits []bool
16+
omitPaths [][][]int // embedded omitempty parent paths
17+
noOmit bool
1718
common.Common
1819
}
1920

@@ -22,20 +23,31 @@ var cachemap = sync.Map{}
2223
type structCalcFunc func(rv reflect.Value) (int, error)
2324
type structWriteFunc func(rv reflect.Value, offset int) int
2425

25-
// getFieldByPath returns the field value by following the path of indices
26-
func getFieldByPath(rv reflect.Value, path []int) reflect.Value {
26+
// getFieldByPath returns the field value by following the path of indices.
27+
// The bool indicates whether the path was reachable (no nil pointer in the path).
28+
func getFieldByPath(rv reflect.Value, path []int) (reflect.Value, bool) {
2729
for _, idx := range path {
2830
// Handle pointer indirection if needed
2931
if rv.Kind() == reflect.Ptr {
3032
if rv.IsNil() {
31-
// Return zero value if pointer is nil
32-
return reflect.Value{}
33+
// Return invalid value if pointer is nil
34+
return reflect.Value{}, false
3335
}
3436
rv = rv.Elem()
3537
}
3638
rv = rv.Field(idx)
3739
}
38-
return rv
40+
return rv, true
41+
}
42+
43+
func shouldOmitByParent(rv reflect.Value, omitPaths [][]int) bool {
44+
for _, path := range omitPaths {
45+
parentValue, ok := getFieldByPath(rv, path)
46+
if !ok || parentValue.IsZero() {
47+
return true
48+
}
49+
}
50+
return false
3951
}
4052

4153
func (e *encoder) getStructCalc(typ reflect.Type) structCalcFunc {
@@ -81,18 +93,10 @@ func (e *encoder) calcStructArray(rv reflect.Value) (int, error) {
8193
fields := e.CollectFields(t, nil)
8294
omitCount := 0
8395
for _, field := range fields {
84-
fieldValue := getFieldByPath(rv, field.Path)
85-
if !fieldValue.IsValid() {
86-
continue
87-
}
88-
size, err := e.calcSize(fieldValue)
89-
if err != nil {
90-
return 0, err
91-
}
92-
ret += size
9396
c.indexes = append(c.indexes, field.Path)
9497
c.names = append(c.names, field.Name)
9598
c.omits = append(c.omits, field.Omit)
99+
c.omitPaths = append(c.omitPaths, field.OmitPaths)
96100
if field.Omit {
97101
omitCount++
98102
}
@@ -101,17 +105,17 @@ func (e *encoder) calcStructArray(rv reflect.Value) (int, error) {
101105
cachemap.Store(t, c)
102106
} else {
103107
c = cache.(*structCache)
104-
for i := 0; i < len(c.indexes); i++ {
105-
fieldValue := getFieldByPath(rv, c.indexes[i])
106-
if !fieldValue.IsValid() {
107-
continue
108-
}
109-
size, err := e.calcSize(fieldValue)
110-
if err != nil {
111-
return 0, err
112-
}
113-
ret += size
108+
}
109+
for i := 0; i < len(c.indexes); i++ {
110+
fieldValue, ok := getFieldByPath(rv, c.indexes[i])
111+
if shouldOmitByParent(rv, c.omitPaths[i]) || !ok {
112+
fieldValue = reflect.Value{}
113+
}
114+
size, err := e.calcSize(fieldValue)
115+
if err != nil {
116+
return 0, err
114117
}
118+
ret += size
115119
}
116120

117121
// format size
@@ -128,53 +132,42 @@ func (e *encoder) calcStructMap(rv reflect.Value) (int, error) {
128132
t := rv.Type()
129133
cache, find := cachemap.Load(t)
130134
var c *structCache
131-
var l int
132135
if !find {
133136
c = &structCache{}
134137
fields := e.CollectFields(t, nil)
135138
omitCount := 0
136139
for _, field := range fields {
137-
fieldValue := getFieldByPath(rv, field.Path)
138-
if !fieldValue.IsValid() {
139-
continue
140-
}
141-
size, err := e.calcSizeWithOmitEmpty(fieldValue, field.Name, field.Omit)
142-
if err != nil {
143-
return 0, err
144-
}
145-
ret += size
146140
c.indexes = append(c.indexes, field.Path)
147141
c.names = append(c.names, field.Name)
148142
c.omits = append(c.omits, field.Omit)
143+
c.omitPaths = append(c.omitPaths, field.OmitPaths)
149144
if field.Omit {
150145
omitCount++
151146
}
152-
if size > 0 {
153-
l++
154-
}
155147
}
156148
c.noOmit = omitCount == 0
157149
cachemap.Store(t, c)
158150
} else {
159151
c = cache.(*structCache)
160-
for i := 0; i < len(c.indexes); i++ {
161-
fieldValue := getFieldByPath(rv, c.indexes[i])
162-
if !fieldValue.IsValid() {
163-
continue
164-
}
165-
size, err := e.calcSizeWithOmitEmpty(fieldValue, c.names[i], c.omits[i])
166-
if err != nil {
167-
return 0, err
168-
}
169-
ret += size
170-
if size > 0 {
171-
l++
172-
}
152+
}
153+
l := 0
154+
for i := 0; i < len(c.indexes); i++ {
155+
fieldValue, ok := getFieldByPath(rv, c.indexes[i])
156+
if shouldOmitByParent(rv, c.omitPaths[i]) || !ok {
157+
continue
158+
}
159+
size, err := e.calcSizeWithOmitEmpty(fieldValue, c.names[i], c.omits[i])
160+
if err != nil {
161+
return 0, err
162+
}
163+
ret += size
164+
if size > 0 {
165+
l++
173166
}
174167
}
175168

176169
// format size
177-
size, err := e.calcLength(len(c.indexes))
170+
size, err := e.calcLength(l)
178171
if err != nil {
179172
return 0, err
180173
}
@@ -249,7 +242,10 @@ func (e *encoder) writeStructArray(rv reflect.Value, offset int) int {
249242
}
250243

251244
for i := 0; i < num; i++ {
252-
fieldValue := getFieldByPath(rv, c.indexes[i])
245+
fieldValue, ok := getFieldByPath(rv, c.indexes[i])
246+
if shouldOmitByParent(rv, c.omitPaths[i]) || !ok {
247+
fieldValue = reflect.Value{}
248+
}
253249
offset = e.create(fieldValue, offset)
254250
}
255251
return offset
@@ -263,14 +259,13 @@ func (e *encoder) writeStructMap(rv reflect.Value, offset int) int {
263259
// format size
264260
num := len(c.indexes)
265261
l := 0
266-
if c.noOmit {
267-
l = num
268-
} else {
269-
for i := 0; i < num; i++ {
270-
fieldValue := getFieldByPath(rv, c.indexes[i])
271-
if !c.omits[i] || !fieldValue.IsZero() {
272-
l++
273-
}
262+
for i := 0; i < num; i++ {
263+
fieldValue, ok := getFieldByPath(rv, c.indexes[i])
264+
if shouldOmitByParent(rv, c.omitPaths[i]) || !ok {
265+
continue
266+
}
267+
if c.noOmit || !c.omits[i] || !fieldValue.IsZero() {
268+
l++
274269
}
275270
}
276271

@@ -285,8 +280,11 @@ func (e *encoder) writeStructMap(rv reflect.Value, offset int) int {
285280
}
286281

287282
for i := 0; i < num; i++ {
288-
fieldValue := getFieldByPath(rv, c.indexes[i])
289-
if !c.omits[i] || !fieldValue.IsZero() {
283+
fieldValue, ok := getFieldByPath(rv, c.indexes[i])
284+
if shouldOmitByParent(rv, c.omitPaths[i]) || !ok {
285+
continue
286+
}
287+
if c.noOmit || !c.omits[i] || !fieldValue.IsZero() {
290288
offset = e.writeString(c.names[i], offset)
291289
offset = e.create(fieldValue, offset)
292290
}

0 commit comments

Comments
 (0)