Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 85effbb

Browse files
committed
IntOrString for use in JSON/YAML
Specifying an API type as IntOrString will allow JSON and YAML to accept either ints or strings with the same name. For example, port names or numbers.
1 parent 0748ac3 commit 85effbb

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

pkg/util/util.go

+63
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,66 @@ func MakeJSONString(o interface{}) string {
6464
data, _ := json.Marshal(o)
6565
return string(data)
6666
}
67+
68+
// IntOrString is a type that can hold an int or a string. When used in
69+
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
70+
// inner type. This allows you to have, for example, a JSON field that can
71+
// accept a name or number.
72+
type IntOrString struct {
73+
Kind IntstrKind
74+
IntVal int
75+
StrVal string
76+
}
77+
78+
type IntstrKind int
79+
80+
const (
81+
IntstrInt IntstrKind = iota
82+
IntstrString
83+
)
84+
85+
func (intstr *IntOrString) SetYAML(tag string, value interface{}) bool {
86+
if intVal, ok := value.(int); ok {
87+
intstr.Kind = IntstrInt
88+
intstr.IntVal = intVal
89+
return true
90+
}
91+
if strVal, ok := value.(string); ok {
92+
intstr.Kind = IntstrString
93+
intstr.StrVal = strVal
94+
return true
95+
}
96+
return false
97+
}
98+
99+
func (intstr IntOrString) GetYAML() (tag string, value interface{}) {
100+
switch intstr.Kind {
101+
case IntstrInt:
102+
value = intstr.IntVal
103+
case IntstrString:
104+
value = intstr.StrVal
105+
default:
106+
panic("impossible IntOrString.Kind")
107+
}
108+
return
109+
}
110+
111+
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
112+
if value[0] == '"' {
113+
intstr.Kind = IntstrString
114+
return json.Unmarshal(value, &intstr.StrVal)
115+
}
116+
intstr.Kind = IntstrInt
117+
return json.Unmarshal(value, &intstr.IntVal)
118+
}
119+
120+
func (intstr IntOrString) MarshalJSON() ([]byte, error) {
121+
switch intstr.Kind {
122+
case IntstrInt:
123+
return json.Marshal(intstr.IntVal)
124+
case IntstrString:
125+
return json.Marshal(intstr.StrVal)
126+
default:
127+
panic("impossible IntOrString.Kind")
128+
}
129+
}

pkg/util/util_test.go

+126
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package util
1919
import (
2020
"encoding/json"
2121
"testing"
22+
23+
"gopkg.in/v1/yaml"
2224
)
2325

2426
type FakeJSONBase struct {
@@ -65,3 +67,127 @@ func TestHandleCrash(t *testing.T) {
6567
t.Errorf("Expected %d iterations, found %d", expect, count)
6668
}
6769
}
70+
71+
type IntOrStringHolder struct {
72+
IOrS IntOrString `json:"val" yaml:"val"`
73+
}
74+
75+
func TestIntOrStringUnmarshalYAML(t *testing.T) {
76+
{
77+
yaml_code_int := "val: 123\n"
78+
79+
var result IntOrStringHolder
80+
if err := yaml.Unmarshal([]byte(yaml_code_int), &result); err != nil {
81+
t.Errorf("Failed to unmarshal: %v", err)
82+
}
83+
if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 {
84+
t.Errorf("Failed to unmarshal int-typed IntOrString: %v", result)
85+
}
86+
}
87+
88+
{
89+
yaml_code_str := "val: \"123\"\n"
90+
91+
var result IntOrStringHolder
92+
if err := yaml.Unmarshal([]byte(yaml_code_str), &result); err != nil {
93+
t.Errorf("Failed to unmarshal: %v", err)
94+
}
95+
if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" {
96+
t.Errorf("Failed to unmarshal string-typed IntOrString: %v", result)
97+
}
98+
}
99+
}
100+
101+
func TestIntOrStringMarshalYAML(t *testing.T) {
102+
{
103+
input := IntOrStringHolder{
104+
IOrS: IntOrString{
105+
Kind: IntstrInt,
106+
IntVal: 123,
107+
},
108+
}
109+
result, err := yaml.Marshal(&input)
110+
if err != nil {
111+
t.Errorf("Failed to marshal: %v", err)
112+
}
113+
if string(result) != "val: 123\n" {
114+
t.Errorf("Failed to marshal int-typed IntOrString: %q", string(result))
115+
}
116+
}
117+
118+
{
119+
input := IntOrStringHolder{
120+
IOrS: IntOrString{
121+
Kind: IntstrString,
122+
StrVal: "123",
123+
},
124+
}
125+
result, err := yaml.Marshal(&input)
126+
if err != nil {
127+
t.Errorf("Failed to marshal: %v", err)
128+
}
129+
if string(result) != "val: \"123\"\n" {
130+
t.Errorf("Failed to marshal string-typed IntOrString: %q", string(result))
131+
}
132+
}
133+
}
134+
135+
func TestIntOrStringUnmarshalJSON(t *testing.T) {
136+
{
137+
json_code_int := "{\"val\": 123}"
138+
139+
var result IntOrStringHolder
140+
if err := json.Unmarshal([]byte(json_code_int), &result); err != nil {
141+
t.Errorf("Failed to unmarshal: %v", err)
142+
}
143+
if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 {
144+
t.Errorf("Failed to unmarshal int-typed IntOrString: %v", result)
145+
}
146+
}
147+
148+
{
149+
json_code_str := "{\"val\": \"123\"}"
150+
151+
var result IntOrStringHolder
152+
if err := json.Unmarshal([]byte(json_code_str), &result); err != nil {
153+
t.Errorf("Failed to unmarshal: %v", err)
154+
}
155+
if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" {
156+
t.Errorf("Failed to unmarshal string-typed IntOrString: %v", result)
157+
}
158+
}
159+
}
160+
161+
func TestIntOrStringMarshalJSON(t *testing.T) {
162+
{
163+
input := IntOrStringHolder{
164+
IOrS: IntOrString{
165+
Kind: IntstrInt,
166+
IntVal: 123,
167+
},
168+
}
169+
result, err := json.Marshal(&input)
170+
if err != nil {
171+
t.Errorf("Failed to marshal: %v", err)
172+
}
173+
if string(result) != "{\"val\":123}" {
174+
t.Errorf("Failed to marshal int-typed IntOrString: %q", string(result))
175+
}
176+
}
177+
178+
{
179+
input := IntOrStringHolder{
180+
IOrS: IntOrString{
181+
Kind: IntstrString,
182+
StrVal: "123",
183+
},
184+
}
185+
result, err := json.Marshal(&input)
186+
if err != nil {
187+
t.Errorf("Failed to marshal: %v", err)
188+
}
189+
if string(result) != "{\"val\":\"123\"}" {
190+
t.Errorf("Failed to marshal string-typed IntOrString: %q", string(result))
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)