Skip to content

Commit

Permalink
IntOrString for use in JSON/YAML
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
thockin committed Jul 10, 2014
1 parent 0748ac3 commit 85effbb
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 0 deletions.
63 changes: 63 additions & 0 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,66 @@ func MakeJSONString(o interface{}) string {
data, _ := json.Marshal(o)
return string(data)
}

// IntOrString is a type that can hold an int or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
type IntOrString struct {
Kind IntstrKind
IntVal int
StrVal string
}

type IntstrKind int

const (
IntstrInt IntstrKind = iota
IntstrString
)

func (intstr *IntOrString) SetYAML(tag string, value interface{}) bool {
if intVal, ok := value.(int); ok {
intstr.Kind = IntstrInt
intstr.IntVal = intVal
return true
}
if strVal, ok := value.(string); ok {
intstr.Kind = IntstrString
intstr.StrVal = strVal
return true
}
return false
}

func (intstr IntOrString) GetYAML() (tag string, value interface{}) {
switch intstr.Kind {
case IntstrInt:
value = intstr.IntVal
case IntstrString:
value = intstr.StrVal
default:
panic("impossible IntOrString.Kind")
}
return
}

func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {
intstr.Kind = IntstrString
return json.Unmarshal(value, &intstr.StrVal)
}
intstr.Kind = IntstrInt
return json.Unmarshal(value, &intstr.IntVal)
}

func (intstr IntOrString) MarshalJSON() ([]byte, error) {
switch intstr.Kind {
case IntstrInt:
return json.Marshal(intstr.IntVal)
case IntstrString:
return json.Marshal(intstr.StrVal)
default:
panic("impossible IntOrString.Kind")
}
}
126 changes: 126 additions & 0 deletions pkg/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package util
import (
"encoding/json"
"testing"

"gopkg.in/v1/yaml"
)

type FakeJSONBase struct {
Expand Down Expand Up @@ -65,3 +67,127 @@ func TestHandleCrash(t *testing.T) {
t.Errorf("Expected %d iterations, found %d", expect, count)
}
}

type IntOrStringHolder struct {
IOrS IntOrString `json:"val" yaml:"val"`
}

func TestIntOrStringUnmarshalYAML(t *testing.T) {
{
yaml_code_int := "val: 123\n"

var result IntOrStringHolder
if err := yaml.Unmarshal([]byte(yaml_code_int), &result); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 {
t.Errorf("Failed to unmarshal int-typed IntOrString: %v", result)
}
}

{
yaml_code_str := "val: \"123\"\n"

var result IntOrStringHolder
if err := yaml.Unmarshal([]byte(yaml_code_str), &result); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" {
t.Errorf("Failed to unmarshal string-typed IntOrString: %v", result)
}
}
}

func TestIntOrStringMarshalYAML(t *testing.T) {
{
input := IntOrStringHolder{
IOrS: IntOrString{
Kind: IntstrInt,
IntVal: 123,
},
}
result, err := yaml.Marshal(&input)
if err != nil {
t.Errorf("Failed to marshal: %v", err)
}
if string(result) != "val: 123\n" {
t.Errorf("Failed to marshal int-typed IntOrString: %q", string(result))
}
}

{
input := IntOrStringHolder{
IOrS: IntOrString{
Kind: IntstrString,
StrVal: "123",
},
}
result, err := yaml.Marshal(&input)
if err != nil {
t.Errorf("Failed to marshal: %v", err)
}
if string(result) != "val: \"123\"\n" {
t.Errorf("Failed to marshal string-typed IntOrString: %q", string(result))
}
}
}

func TestIntOrStringUnmarshalJSON(t *testing.T) {
{
json_code_int := "{\"val\": 123}"

var result IntOrStringHolder
if err := json.Unmarshal([]byte(json_code_int), &result); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if result.IOrS.Kind != IntstrInt || result.IOrS.IntVal != 123 {
t.Errorf("Failed to unmarshal int-typed IntOrString: %v", result)
}
}

{
json_code_str := "{\"val\": \"123\"}"

var result IntOrStringHolder
if err := json.Unmarshal([]byte(json_code_str), &result); err != nil {
t.Errorf("Failed to unmarshal: %v", err)
}
if result.IOrS.Kind != IntstrString || result.IOrS.StrVal != "123" {
t.Errorf("Failed to unmarshal string-typed IntOrString: %v", result)
}
}
}

func TestIntOrStringMarshalJSON(t *testing.T) {
{
input := IntOrStringHolder{
IOrS: IntOrString{
Kind: IntstrInt,
IntVal: 123,
},
}
result, err := json.Marshal(&input)
if err != nil {
t.Errorf("Failed to marshal: %v", err)
}
if string(result) != "{\"val\":123}" {
t.Errorf("Failed to marshal int-typed IntOrString: %q", string(result))
}
}

{
input := IntOrStringHolder{
IOrS: IntOrString{
Kind: IntstrString,
StrVal: "123",
},
}
result, err := json.Marshal(&input)
if err != nil {
t.Errorf("Failed to marshal: %v", err)
}
if string(result) != "{\"val\":\"123\"}" {
t.Errorf("Failed to marshal string-typed IntOrString: %q", string(result))
}
}
}

0 comments on commit 85effbb

Please sign in to comment.