@@ -90,25 +90,32 @@ func Get(v any, path *structpath.PathNode) (any, error) {
9090func accessKey (v reflect.Value , key string , path * structpath.PathNode ) (reflect.Value , error ) {
9191 switch v .Kind () {
9292 case reflect .Struct :
93- fv , sf , owner , ok := findStructFieldByKey (v , key )
93+ // Precalculate ForceSendFields mappings for this struct hierarchy
94+ forceSendFieldsMap := getForceSendFieldsForFromTyped (v )
95+
96+ fv , sf , embeddedIndex , ok := findStructFieldByKey (v , key )
9497 if ! ok {
9598 return reflect.Value {}, fmt .Errorf ("%s: field %q not found in %s" , path .String (), key , v .Type ())
9699 }
97- // Evaluate ForceSendFields on both the current struct and the declaring owner
98- force := containsForceSendField (v , sf .Name ) || containsForceSendField (owner , sf .Name )
99100
100- // Honor omitempty: if present and value is zero and not forced, treat as omitted (nil).
101+ // Check ForceSendFields using precalculated map
102+ var force bool
103+ if fields , exists := forceSendFieldsMap [embeddedIndex ]; exists {
104+ force = containsString (fields , sf .Name )
105+ }
106+
107+ // Honor omitempty: if present and value is empty and not forced, treat as omitted (nil).
101108 jsonTag := structtag .JSONTag (sf .Tag .Get ("json" ))
102109 if jsonTag .OmitEmpty () && ! force {
103110 if fv .Kind () == reflect .Pointer {
104111 if fv .IsNil () {
105112 return reflect.Value {}, nil
106113 }
107- // Non-nil pointer: check the element zero-ness for pointers to scalars/structs.
108- if fv .Elem (). IsZero ( ) {
114+ // Non-nil pointer: check if the pointed-to value is empty for omitempty
115+ if isEmptyForOmitEmpty ( fv .Elem ()) {
109116 return reflect.Value {}, nil
110117 }
111- } else if fv . IsZero ( ) {
118+ } else if isEmptyForOmitEmpty ( fv ) {
112119 return reflect.Value {}, nil
113120 }
114121 }
@@ -132,18 +139,18 @@ func accessKey(v reflect.Value, key string, path *structpath.PathNode) (reflect.
132139 }
133140}
134141
135- // findStructFieldByKey searches exported fields of struct v for a field matching key.
136- // It matches json tag name (when present and not "-") only.
137- // It also searches embedded anonymous structs (pointer or value) recursively.
138- func findStructFieldByKey (v reflect.Value , key string ) (reflect.Value , reflect.StructField , reflect.Value , bool ) {
142+ // findFieldInStruct searches for a field by JSON key in a single struct (no embedding).
143+ // Returns: fieldValue, structField, found
144+ func findFieldInStruct (v reflect.Value , key string ) (reflect.Value , reflect.StructField , bool ) {
139145 t := v .Type ()
140-
141- // First pass: direct fields
142146 for i := range t .NumField () {
143147 sf := t .Field (i )
144148 if sf .PkgPath != "" { // unexported
145149 continue
146150 }
151+ if sf .Anonymous { // skip embedded fields
152+ continue
153+ }
147154
148155 // Read JSON tag using structtag helper
149156 name := structtag .JSONTag (sf .Tag .Get ("json" )).Name ()
@@ -157,11 +164,26 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S
157164 if btag .Internal () || btag .ReadOnly () {
158165 continue
159166 }
160- return v .Field (i ), sf , v , true
167+ return v .Field (i ), sf , true
161168 }
162169 }
170+ return reflect.Value {}, reflect.StructField {}, false
171+ }
172+
173+ // findStructFieldByKey searches exported fields of struct v for a field matching key.
174+ // It matches json tag name (when present and not "-") only.
175+ // It also searches embedded anonymous structs (flattening semantics).
176+ // Returns: fieldValue, structField, embeddedIndex, found
177+ // embeddedIndex is -1 for direct fields, or the index of the embedded struct containing the field.
178+ func findStructFieldByKey (v reflect.Value , key string ) (reflect.Value , reflect.StructField , int , bool ) {
179+ t := v .Type ()
180+
181+ // First pass: direct fields
182+ if fv , sf , found := findFieldInStruct (v , key ); found {
183+ return fv , sf , - 1 , true
184+ }
163185
164- // Second pass: search embedded anonymous structs recursively (flattening semantics)
186+ // Second pass: search embedded anonymous structs (flattening semantics)
165187 for i := range t .NumField () {
166188 sf := t .Field (i )
167189 if ! sf .Anonymous {
@@ -179,38 +201,88 @@ func findStructFieldByKey(v reflect.Value, key string) (reflect.Value, reflect.S
179201 if fv .Kind () != reflect .Struct {
180202 continue
181203 }
182- if out , osf , owner , ok := findStructFieldByKey (fv , key ); ok {
183- // Skip fields marked as internal or readonly via bundle tag
184- btag := structtag .BundleTag (osf .Tag .Get ("bundle" ))
185- if btag .Internal () || btag .ReadOnly () {
186- // Treat as not found and continue searching other anonymous fields
187- continue
204+ if out , osf , found := findFieldInStruct (fv , key ); found {
205+ return out , osf , i , true
206+ }
207+ }
208+
209+ return reflect.Value {}, reflect.StructField {}, - 1 , false
210+ }
211+
212+ // getForceSendFieldsForFromTyped collects ForceSendFields values for FromTyped operations
213+ // Returns map[structKey][]fieldName where structKey is -1 for direct fields, embedded index for embedded fields
214+ func getForceSendFieldsForFromTyped (v reflect.Value ) map [int ][]string {
215+ if ! v .IsValid () || v .Type ().Kind () != reflect .Struct {
216+ return make (map [int ][]string )
217+ }
218+
219+ result := make (map [int ][]string )
220+
221+ for i := range v .Type ().NumField () {
222+ field := v .Type ().Field (i )
223+ fieldValue := v .Field (i )
224+
225+ if field .Name == "ForceSendFields" && ! field .Anonymous {
226+ // Direct ForceSendFields (structKey = -1)
227+ if fields , ok := fieldValue .Interface ().([]string ); ok {
228+ result [- 1 ] = fields
229+ }
230+ } else if field .Anonymous {
231+ // Embedded struct - check for ForceSendFields inside it
232+ if embeddedStruct := getEmbeddedStructForReading (fieldValue ); embeddedStruct .IsValid () {
233+ if forceSendField := embeddedStruct .FieldByName ("ForceSendFields" ); forceSendField .IsValid () {
234+ if fields , ok := forceSendField .Interface ().([]string ); ok {
235+ result [i ] = fields
236+ }
237+ }
188238 }
189- return out , osf , owner , true
190239 }
191240 }
192241
193- return reflect. Value {}, reflect. StructField {}, reflect. Value {}, false
242+ return result
194243}
195244
196- // containsForceSendField reports whether struct v has a ForceSendFields slice containing goFieldName.
197- func containsForceSendField (v reflect.Value , goFieldName string ) bool {
198- if ! v .IsValid () || v .Kind () != reflect .Struct {
199- return false
245+ // Helper function for reading - doesn't create nil pointers
246+ func getEmbeddedStructForReading (fieldValue reflect.Value ) reflect.Value {
247+ if fieldValue .Kind () == reflect .Pointer {
248+ if fieldValue .IsNil () {
249+ return reflect.Value {} // Don't create, just return invalid
250+ }
251+ fieldValue = fieldValue .Elem ()
200252 }
201- fsField := v .FieldByName ("ForceSendFields" )
202- if ! fsField .IsValid () || fsField .Kind () != reflect .Slice {
203- return false
253+ if fieldValue .Kind () == reflect .Struct {
254+ return fieldValue
204255 }
205- for i := range fsField .Len () {
206- el := fsField .Index (i )
207- if el .Kind () == reflect .String && el .String () == goFieldName {
256+ return reflect.Value {}
257+ }
258+
259+ // containsString checks if a slice contains a specific string
260+ func containsString (slice []string , str string ) bool {
261+ for _ , s := range slice {
262+ if s == str {
208263 return true
209264 }
210265 }
211266 return false
212267}
213268
269+ // isEmptyForOmitEmpty returns true if the value should be omitted by JSON omitempty.
270+ // This matches JSON encoder behavior, which is different from reflect.IsZero() for slices/maps.
271+ func isEmptyForOmitEmpty (v reflect.Value ) bool {
272+ switch v .Kind () {
273+ case reflect .Slice , reflect .Map , reflect .Array :
274+ return v .Len () == 0
275+ case reflect .Interface , reflect .Pointer :
276+ return v .IsNil ()
277+ case reflect .Struct :
278+ // Pointers to structs are not considered empty if pointer != nil
279+ // Structs as values are never empty and omitempty on them has no effect.
280+ return false
281+ default :
282+ return v .IsZero ()
283+ }
284+ }
285+
214286// deref dereferences pointers and interfaces until it reaches a non-pointer, non-interface value.
215287// Returns ok=false if it encounters a nil pointer/interface.
216288func deref (v reflect.Value ) (reflect.Value , bool ) {
0 commit comments