Skip to content

Commit 44b8d15

Browse files
authored
Merge pull request #273 from jpbetz/json-tags
Add JSON Tag parse utility
2 parents 3b05ca7 + 2e3eea7 commit 44b8d15

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed

v2/parser/tags/json.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// This is based on the highly efficient approach from https://golang.org/src/encoding/json/encode.go
18+
19+
package tags
20+
21+
import (
22+
"reflect"
23+
"strings"
24+
25+
"k8s.io/gengo/v2/types"
26+
)
27+
28+
// JSON represents a go json field tag.
29+
type JSON struct {
30+
Name string
31+
Omit bool
32+
Inline bool
33+
Omitempty bool
34+
}
35+
36+
func (t JSON) String() string {
37+
var tag string
38+
if !t.Inline {
39+
tag += t.Name
40+
}
41+
if t.Omitempty {
42+
tag += ",omitempty"
43+
}
44+
if t.Inline {
45+
tag += ",inline"
46+
}
47+
return tag
48+
}
49+
50+
func LookupJSON(m types.Member) (JSON, bool) {
51+
tag := reflect.StructTag(m.Tags).Get("json")
52+
if tag == "-" {
53+
return JSON{Omit: true}, true
54+
}
55+
name, opts := parse(tag)
56+
inline := opts.Contains("inline")
57+
omitempty := opts.Contains("omitempty")
58+
if !inline && name == "" {
59+
name = m.Name
60+
}
61+
return JSON{
62+
Name: name,
63+
Omit: false,
64+
Inline: inline,
65+
Omitempty: omitempty,
66+
}, true
67+
}
68+
69+
type options string
70+
71+
// parse splits a struct field's json tag into its Name and
72+
// comma-separated options.
73+
func parse(tag string) (string, options) {
74+
if idx := strings.Index(tag, ","); idx != -1 {
75+
return tag[:idx], options(tag[idx+1:])
76+
}
77+
return tag, ""
78+
}
79+
80+
// Contains reports whether a comma-separated listAlias of options
81+
// contains a particular substr flag. substr must be surrounded by a
82+
// string boundary or commas.
83+
func (o options) Contains(optionName string) bool {
84+
if len(o) == 0 {
85+
return false
86+
}
87+
s := string(o)
88+
for s != "" {
89+
var next string
90+
i := strings.Index(s, ",")
91+
if i >= 0 {
92+
s, next = s[:i], s[i+1:]
93+
}
94+
if s == optionName {
95+
return true
96+
}
97+
s = next
98+
}
99+
return false
100+
}

v2/parser/tags/json_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package tags
18+
19+
import (
20+
"testing"
21+
22+
"k8s.io/gengo/v2/parser"
23+
"k8s.io/gengo/v2/types"
24+
)
25+
26+
func TestJSON(t *testing.T) {
27+
p := parser.New()
28+
u := types.Universe{}
29+
// Proper packages with deps.
30+
pkgs, err := p.LoadPackagesTo(&u, "./testdata/tags")
31+
if err != nil {
32+
t.Errorf("unexpected error: %v", err)
33+
}
34+
35+
member := func(typeName, memberName string) types.Member {
36+
typ := pkgs[0].Type(typeName)
37+
for _, m := range typ.Members {
38+
if m.Name == memberName {
39+
return m
40+
}
41+
}
42+
t.Fatalf("member %s not found", memberName)
43+
return types.Member{}
44+
}
45+
46+
tests := []struct {
47+
name string
48+
member types.Member
49+
expected JSON
50+
}{
51+
{
52+
name: "name",
53+
member: member("T1", "A"),
54+
expected: JSON{Name: "a"},
55+
},
56+
{
57+
name: "omitempty",
58+
member: member("T1", "B"),
59+
expected: JSON{Name: "b", Omitempty: true},
60+
},
61+
{
62+
name: "inline",
63+
member: member("T1", "C"),
64+
expected: JSON{Name: "", Inline: true},
65+
},
66+
{
67+
name: "omit",
68+
member: member("T1", "D"),
69+
expected: JSON{Name: "", Omit: true},
70+
},
71+
{
72+
name: "empty",
73+
member: member("T1", "E"),
74+
expected: JSON{Name: "E"},
75+
},
76+
}
77+
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
tags, ok := LookupJSON(tt.member)
81+
if !ok {
82+
t.Errorf("failed to lookup tags")
83+
}
84+
if tags != tt.expected {
85+
t.Errorf("expected %#+v, got %#+v", tt.expected, tags)
86+
}
87+
})
88+
}
89+
}
90+
91+
type t1 struct {
92+
Name string `json:"name"`
93+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package tags
2+
3+
type T1 struct {
4+
A string `json:"a"`
5+
B string `json:"b,omitempty"`
6+
C string `json:",inline"`
7+
D string `json:"-"`
8+
E string `json:""`
9+
}

0 commit comments

Comments
 (0)