Skip to content

Commit 1a7ee26

Browse files
authored
Merge pull request #273 from Yamashou/remove-enableInputJsonOmitemptyTag
Remove EnableInputJsonOmitemptyTag and modernize
2 parents 12e7129 + 6cad43b commit 1a7ee26

File tree

3 files changed

+71
-181
lines changed

3 files changed

+71
-181
lines changed

clientv2/client.go

Lines changed: 36 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"mime/multipart"
1313
"net/http"
1414
"reflect"
15+
"slices"
1516
"strconv"
1617
"strings"
1718

@@ -462,52 +463,13 @@ func (c *Client) unmarshal(data []byte, res any) error {
462463
return err
463464
}
464465

465-
// contextKey is a type for context keys
466-
type contextKey string
467-
468-
const (
469-
// EnableInputJsonOmitemptyTagKey is a context key for EnableInputJsonOmitemptyTag
470-
EnableInputJsonOmitemptyTagKey contextKey = "enable_input_json_omitempty_tag"
471-
)
472-
473-
// WithEnableInputJsonOmitemptyTag returns a new context with EnableInputJsonOmitemptyTag value
474-
func WithEnableInputJsonOmitemptyTag(ctx context.Context, enable bool) context.Context {
475-
return context.WithValue(ctx, EnableInputJsonOmitemptyTagKey, enable)
476-
}
477-
478-
// getEnableInputJsonOmitemptyTagFromContext retrieves the EnableInputJsonOmitemptyTag value from context
479-
func getEnableInputJsonOmitemptyTagFromContext(ctx context.Context) bool {
480-
enableClientJsonOmitemptyTag := true
481-
if ctx != nil {
482-
enable, ok := ctx.Value(EnableInputJsonOmitemptyTagKey).(bool)
483-
if ok {
484-
enableClientJsonOmitemptyTag = enable
485-
}
486-
}
487-
return enableClientJsonOmitemptyTag
488-
}
489-
490-
func MarshalJSON(ctx context.Context, v any) ([]byte, error) {
491-
if v == nil {
492-
return []byte("null"), nil
493-
}
494-
495-
val := reflect.ValueOf(v)
496-
if !val.IsValid() || (val.Kind() == reflect.Ptr && val.IsNil()) {
497-
return []byte("null"), nil
498-
}
499-
500-
encoder := &Encoder{
501-
EnableInputJsonOmitemptyTag: getEnableInputJsonOmitemptyTagFromContext(ctx),
502-
}
503-
504-
return encoder.Encode(val)
466+
func MarshalJSON(_ context.Context, v any) ([]byte, error) {
467+
encoder := &Encoder{}
468+
return encoder.Encode(reflect.ValueOf(v))
505469
}
506470

507471
// Encoder is a struct for encoding GraphQL requests to JSON
508-
type Encoder struct {
509-
EnableInputJsonOmitemptyTag bool
510-
}
472+
type Encoder struct{}
511473

512474
// fieldInfo holds field information of a struct
513475
type fieldInfo struct {
@@ -620,17 +582,17 @@ func (e *Encoder) encodeBool(v reflect.Value) ([]byte, error) {
620582

621583
// encodeInt encodes an integer value
622584
func (e *Encoder) encodeInt(v reflect.Value) ([]byte, error) {
623-
return []byte(fmt.Sprintf("%d", v.Int())), nil
585+
return fmt.Appendf(nil, "%d", v.Int()), nil
624586
}
625587

626588
// encodeUint encodes an unsigned integer value
627589
func (e *Encoder) encodeUint(v reflect.Value) ([]byte, error) {
628-
return []byte(fmt.Sprintf("%d", v.Uint())), nil
590+
return fmt.Appendf(nil, "%d", v.Uint()), nil
629591
}
630592

631593
// encodeFloat encodes a floating-point value
632594
func (e *Encoder) encodeFloat(v reflect.Value) ([]byte, error) {
633-
return []byte(fmt.Sprintf("%f", v.Float())), nil
595+
return fmt.Appendf(nil, "%f", v.Float()), nil
634596
}
635597

636598
// encodeString encodes a string value
@@ -650,24 +612,32 @@ func (e *Encoder) trimQuotes(s string) string {
650612
return s
651613
}
652614

653-
func (e *Encoder) isSkipOmitemptyField(v reflect.Value, field fieldInfo) bool {
654-
if !e.EnableInputJsonOmitemptyTag {
655-
return false
656-
}
657-
658-
if !field.omitempty {
659-
return false
660-
}
661-
615+
func isSkipField(omitempty bool, v reflect.Value) bool {
662616
if !v.IsValid() {
663617
return true
664618
}
665619

666-
if v.Kind() == reflect.Ptr && v.IsNil() {
667-
return true
620+
var skipByOmitEmpty bool
621+
if omitempty {
622+
skipByOmitEmpty = isEmptyValue(v)
668623
}
669624

670-
return v.IsZero()
625+
return skipByOmitEmpty
626+
}
627+
628+
// https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/encoding/json/encode.go;l=318-330
629+
func isEmptyValue(v reflect.Value) bool {
630+
switch v.Kind() {
631+
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
632+
return v.Len() == 0
633+
case reflect.Bool,
634+
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
635+
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
636+
reflect.Float32, reflect.Float64,
637+
reflect.Interface, reflect.Pointer:
638+
return v.IsZero()
639+
}
640+
return false
671641
}
672642

673643
// encodeStruct encodes a struct value
@@ -676,7 +646,7 @@ func (e *Encoder) encodeStruct(v reflect.Value) ([]byte, error) {
676646
result := make(map[string]json.RawMessage)
677647
for _, field := range fields {
678648
fieldValue := v.FieldByName(field.name)
679-
if e.isSkipOmitemptyField(fieldValue, field) {
649+
if isSkipField(field.omitempty, fieldValue) {
680650
continue
681651
}
682652

@@ -773,21 +743,17 @@ func (e *Encoder) prepareFields(t reflect.Type) []fieldInfo {
773743
if jsonTag == "-" {
774744
continue
775745
}
776-
777-
jsonName := f.Name
778-
if jsonTag != "" {
779-
parts := strings.Split(jsonTag, ",")
780-
jsonName = parts[0]
781-
}
782-
783746
fi := fieldInfo{
784747
name: f.Name,
785-
jsonName: jsonName,
748+
jsonName: f.Name,
786749
typ: f.Type,
787750
}
788-
789-
if strings.Contains(jsonTag, "omitempty") {
790-
fi.omitempty = true
751+
if jsonTag != "" {
752+
parts := strings.Split(jsonTag, ",")
753+
fi.jsonName = parts[0]
754+
if len(parts) > 1 {
755+
fi.omitempty = slices.Contains(parts[1:], "omitempty")
756+
}
791757
}
792758

793759
fields = append(fields, fi)

clientv2/client_test.go

Lines changed: 33 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,11 +1038,10 @@ func TestEncoder_encodeStruct(t *testing.T) {
10381038
email := "test@example.com"
10391039

10401040
tests := []struct {
1041-
name string
1042-
input Person
1043-
enableOmitemptyTag bool
1044-
want map[string]interface{}
1045-
wantErr bool
1041+
name string
1042+
input Person
1043+
want map[string]any
1044+
wantErr bool
10461045
}{
10471046
{
10481047
name: "all fields filled",
@@ -1055,7 +1054,6 @@ func TestEncoder_encodeStruct(t *testing.T) {
10551054
Nickname: "Johnny",
10561055
Hobbies: []string{"reading", "swimming"},
10571056
},
1058-
enableOmitemptyTag: true,
10591057
want: map[string]any{
10601058
"name": "John",
10611059
"age": int64(30),
@@ -1073,54 +1071,13 @@ func TestEncoder_encodeStruct(t *testing.T) {
10731071
Name: "John",
10741072
Address: Address{City: "Tokyo"},
10751073
},
1076-
enableOmitemptyTag: true,
10771074
want: map[string]any{
10781075
"name": "John",
10791076
"email2": nil,
10801077
"address": map[string]any{"city": "Tokyo"},
10811078
"hobbies": nil,
10821079
},
10831080
},
1084-
{
1085-
name: "omitempty disabled",
1086-
input: Person{
1087-
Name: "John",
1088-
Address: Address{City: "Tokyo"},
1089-
},
1090-
enableOmitemptyTag: false,
1091-
want: map[string]any{
1092-
"name": "John",
1093-
"age": int64(0),
1094-
"email": nil,
1095-
"email2": nil,
1096-
"address": map[string]any{"city": "Tokyo", "country": "", "zip": nil},
1097-
"tags": nil,
1098-
"nickname": "",
1099-
"hobbies": nil,
1100-
},
1101-
},
1102-
{
1103-
name: "nil slice set to null on omitempty disabled",
1104-
input: Person{
1105-
Name: "John",
1106-
Address: Address{City: "Tokyo"},
1107-
},
1108-
enableOmitemptyTag: false,
1109-
want: map[string]any{
1110-
"name": "John",
1111-
"age": int64(0),
1112-
"email": nil, // omitempty is ignored
1113-
"email2": nil,
1114-
"tags": nil, // omitempty is ignored
1115-
"hobbies": nil,
1116-
"address": map[string]any{
1117-
"city": "Tokyo",
1118-
"country": "", // omitempty is ignored
1119-
"zip": nil,
1120-
},
1121-
"nickname": "", // omitempty is ignored
1122-
},
1123-
},
11241081
{
11251082
name: "zero value of slice (i.e. nil slice) dropped on omitempty enabled",
11261083
input: Person{
@@ -1129,7 +1086,6 @@ func TestEncoder_encodeStruct(t *testing.T) {
11291086
Hobbies: nil,
11301087
Tags: nil, // will be dropped as omitempty is enabled
11311088
},
1132-
enableOmitemptyTag: true,
11331089
want: map[string]any{
11341090
"name": "John",
11351091
"email2": nil,
@@ -1140,14 +1096,12 @@ func TestEncoder_encodeStruct(t *testing.T) {
11401096
{
11411097
name: "nil slice set to null",
11421098
input: Person{
1143-
Tags: []string{}, // this is not zero value, so omitempty will not be applied
1099+
Tags: []string{}, // empty slice is empty value but not zero value
11441100
Hobbies: nil, // will continue to be nil
11451101
},
1146-
enableOmitemptyTag: true,
11471102
want: map[string]any{
11481103
"name": "",
11491104
"email2": nil,
1150-
"tags": []any{},
11511105
"hobbies": nil,
11521106
"address": map[string]any{"city": ""},
11531107
},
@@ -1156,10 +1110,7 @@ func TestEncoder_encodeStruct(t *testing.T) {
11561110

11571111
for _, tt := range tests {
11581112
t.Run(tt.name, func(t *testing.T) {
1159-
encoder := &Encoder{
1160-
EnableInputJsonOmitemptyTag: tt.enableOmitemptyTag,
1161-
}
1162-
1113+
encoder := &Encoder{}
11631114
got, err := encoder.encodeStruct(reflect.ValueOf(tt.input))
11641115
if (err != nil) != tt.wantErr {
11651116
t.Errorf("encodeStruct() error = %v, wantErr %v", err, tt.wantErr)
@@ -1175,78 +1126,56 @@ func TestEncoder_encodeStruct(t *testing.T) {
11751126

11761127
// JSONの文字列として比較
11771128
if string(got) != string(want) {
1178-
t.Errorf("encodeStruct() = %s, want %s", got, want)
1129+
t.Errorf("encodeStruct()\n got: %s\nwant: %s", got, want)
11791130
}
11801131
})
11811132
}
11821133
}
11831134

11841135
func TestEncoder_isSkipOmitemptyField(t *testing.T) {
1185-
type testStruct struct {
1186-
Required string `json:"required"`
1187-
Optional string `json:"optional,omitempty"`
1188-
Ptr *string `json:"ptr,omitempty"`
1189-
}
1190-
11911136
str := "test"
11921137
tests := []struct {
1193-
name string
1194-
value reflect.Value
1195-
field fieldInfo
1196-
enableOmitemptyTag bool
1197-
want bool
1138+
name string
1139+
value reflect.Value
1140+
field fieldInfo
1141+
want bool
11981142
}{
11991143
{
1200-
name: "non-empty value with omitempty",
1201-
value: reflect.ValueOf("test"),
1202-
field: fieldInfo{omitempty: true},
1203-
enableOmitemptyTag: true,
1204-
want: false,
1144+
name: "non-empty value with omitempty",
1145+
value: reflect.ValueOf("test"),
1146+
field: fieldInfo{omitempty: true},
1147+
want: false,
12051148
},
12061149
{
1207-
name: "empty value with omitempty",
1208-
value: reflect.ValueOf(""),
1209-
field: fieldInfo{omitempty: true},
1210-
enableOmitemptyTag: true,
1211-
want: true,
1150+
name: "empty value with omitempty",
1151+
value: reflect.ValueOf(""),
1152+
field: fieldInfo{omitempty: true},
1153+
want: true,
12121154
},
12131155
{
1214-
name: "nil pointer with omitempty",
1215-
value: reflect.ValueOf((*string)(nil)),
1216-
field: fieldInfo{omitempty: true},
1217-
enableOmitemptyTag: true,
1218-
want: true,
1156+
name: "nil pointer with omitempty",
1157+
value: reflect.ValueOf((*string)(nil)),
1158+
field: fieldInfo{omitempty: true},
1159+
want: true,
12191160
},
12201161
{
1221-
name: "non-nil pointer with omitempty",
1222-
value: reflect.ValueOf(&str),
1223-
field: fieldInfo{omitempty: true},
1224-
enableOmitemptyTag: true,
1225-
want: false,
1162+
name: "non-nil pointer with omitempty",
1163+
value: reflect.ValueOf(&str),
1164+
field: fieldInfo{omitempty: true},
1165+
want: false,
12261166
},
12271167
{
1228-
name: "empty value without omitempty",
1229-
value: reflect.ValueOf(""),
1230-
field: fieldInfo{omitempty: false},
1231-
enableOmitemptyTag: true,
1232-
want: false,
1233-
},
1234-
{
1235-
name: "omitempty tag disabled",
1236-
value: reflect.ValueOf(""),
1237-
field: fieldInfo{omitempty: true},
1238-
enableOmitemptyTag: false,
1239-
want: false,
1168+
name: "empty value without omitempty",
1169+
value: reflect.ValueOf(""),
1170+
field: fieldInfo{omitempty: false},
1171+
want: false,
12401172
},
12411173
}
12421174

12431175
for _, tt := range tests {
12441176
t.Run(tt.name, func(t *testing.T) {
1245-
encoder := &Encoder{
1246-
EnableInputJsonOmitemptyTag: tt.enableOmitemptyTag,
1247-
}
1248-
if got := encoder.isSkipOmitemptyField(tt.value, tt.field); got != tt.want {
1249-
t.Errorf("isSkipOmitemptyField() = %v, want %v", got, tt.want)
1177+
if got := isSkipField(tt.field.omitempty, tt.value); got != tt.want {
1178+
t.Errorf("isSkipField() = %v, want %v", got, tt.want)
12501179
}
12511180
})
12521181
}

0 commit comments

Comments
 (0)