Skip to content

Commit 76a39f4

Browse files
committedJan 30, 2025
fix: fix apijson.Port for embedded structs (#3844)
1 parent c4efc0a commit 76a39f4

File tree

2 files changed

+100
-13
lines changed

2 files changed

+100
-13
lines changed
 

‎internal/apijson/port.go

+24-13
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,33 @@ func Port(from any, to any) error {
3434
fromJSON := fromVal.FieldByName("JSON")
3535
toJSON := toVal.FieldByName("JSON")
3636

37-
// First, iterate through the from fields and load all the "normal" fields in the struct to the map of
38-
// string to reflect.Value, as well as their raw .JSON.Foo counterpart.
39-
for i := 0; i < fromType.NumField(); i++ {
40-
field := fromType.Field(i)
41-
ptag, ok := parseJSONStructTag(field)
42-
if !ok {
43-
continue
44-
}
45-
if ptag.name == "-" {
46-
continue
37+
// Iterate through the fields of v and load all the "normal" fields in the struct to the map of
38+
// string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j.
39+
var getFields func(t reflect.Type, v, j reflect.Value)
40+
getFields = func(t reflect.Type, v, j reflect.Value) {
41+
// Recurse into anonymous fields first, since the fields on the object should win over the fields in the
42+
// embedded object.
43+
for i := 0; i < t.NumField(); i++ {
44+
field := t.Field(i)
45+
if field.Anonymous {
46+
getFields(field.Type, v.Field(i), v.FieldByName("JSON"))
47+
continue
48+
}
4749
}
48-
values[ptag.name] = fromVal.Field(i)
49-
if fromJSON.IsValid() {
50-
fields[ptag.name] = fromJSON.FieldByName(field.Name)
50+
51+
for i := 0; i < t.NumField(); i++ {
52+
field := t.Field(i)
53+
ptag, ok := parseJSONStructTag(field)
54+
if !ok || ptag.name == "-" {
55+
continue
56+
}
57+
values[ptag.name] = v.Field(i)
58+
if j.IsValid() {
59+
fields[ptag.name] = j.FieldByName(field.Name)
60+
}
5161
}
5262
}
63+
getFields(fromType, fromVal, fromJSON)
5364

5465
// Use the values from the previous step to populate the 'to' struct.
5566
for i := 0; i < toType.NumField(); i++ {

‎internal/apijson/port_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,37 @@ type CardMastercardData struct {
9494
Bar int64 `json:"bar"`
9595
}
9696

97+
type CommonFields struct {
98+
Metadata Metadata `json:"metadata"`
99+
Value string `json:"value"`
100+
}
101+
102+
type commonFieldsJSON struct {
103+
Metadata Field
104+
Value Field
105+
ExtraFields map[string]Field
106+
}
107+
108+
type CardEmbedded struct {
109+
CommonFields
110+
Processor CardVisaProcessor `json:"processor"`
111+
Data CardVisaData `json:"data"`
112+
IsFoo bool `json:"is_foo"`
113+
114+
JSON cardEmbeddedJSON
115+
}
116+
117+
type cardEmbeddedJSON struct {
118+
commonFieldsJSON
119+
Processor Field
120+
Data Field
121+
IsFoo Field
122+
ExtraFields map[string]Field
123+
raw string
124+
}
125+
126+
func (r cardEmbeddedJSON) RawJSON() string { return r.raw }
127+
97128
var portTests = map[string]struct {
98129
from any
99130
to any
@@ -158,6 +189,51 @@ var portTests = map[string]struct {
158189
Value: false,
159190
},
160191
},
192+
"embedded to card": {
193+
CardEmbedded{
194+
CommonFields: CommonFields{
195+
Metadata: Metadata{
196+
CreatedAt: "Mar 29 2024",
197+
},
198+
Value: "embedded_value",
199+
},
200+
Processor: "visa",
201+
IsFoo: true,
202+
Data: CardVisaData{
203+
Foo: "embedded_foo",
204+
},
205+
JSON: cardEmbeddedJSON{
206+
commonFieldsJSON: commonFieldsJSON{
207+
Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: valid},
208+
Value: Field{raw: `"embedded_value"`, status: valid},
209+
},
210+
raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"}}`,
211+
Processor: Field{raw: `"visa"`, status: valid},
212+
IsFoo: Field{raw: `true`, status: valid},
213+
Data: Field{raw: `{"foo":"embedded_foo"}`, status: valid},
214+
},
215+
},
216+
Card{
217+
Processor: "visa",
218+
IsFoo: true,
219+
IsBar: false,
220+
Data: CardVisaData{
221+
Foo: "embedded_foo",
222+
},
223+
Metadata: Metadata{
224+
CreatedAt: "Mar 29 2024",
225+
},
226+
Value: "embedded_value",
227+
JSON: cardJSON{
228+
raw: "{\"processor\":\"visa\",\"is_foo\":true,\"data\":{\"foo\":\"embedded_foo\"}}",
229+
Processor: Field{raw: `"visa"`, status: 0x3},
230+
IsFoo: Field{raw: "true", status: 0x3},
231+
Data: Field{raw: `{"foo":"embedded_foo"}`, status: 0x3},
232+
Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: 0x3},
233+
Value: Field{raw: `"embedded_value"`, status: 0x3},
234+
},
235+
},
236+
},
161237
}
162238

163239
func TestPort(t *testing.T) {

0 commit comments

Comments
 (0)
Please sign in to comment.