Last active
December 5, 2020 13:57
-
-
Save kerma/ea726055540da482810147df272375a5 to your computer and use it in GitHub Desktop.
Some tests for checking encoding/json behaviour
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://kerma.codes/posts/go-json/ | |
package main | |
import ( | |
"encoding/json" | |
"reflect" | |
"testing" | |
"time" | |
) | |
// BasicTypes covers all json data types | |
type BasicTypes struct { | |
String string | |
Int int | |
Number float64 | |
Bool bool | |
Object map[string]string | |
Array []string | |
} | |
// NestedObject uses structs and interface | |
type NestedObject struct { | |
Object Dummy | |
PointerObject *Dummy | |
AnyType interface{} | |
} | |
type Dummy struct { | |
Key string | |
} | |
type Output struct { | |
Capitalized string | |
CamelCase []string `json:"camelCase"` | |
Optional string `json:",omitempty"` | |
} | |
type NestedOutput struct { | |
Object Dummy `json:",omitempty"` | |
OptionalObject *Dummy `json:",omitempty"` | |
PointerObject *Dummy | |
AnyType interface{} | |
} | |
type OptionalString struct { | |
Key string | |
Val string `json:",omitempty"` | |
} | |
type AnyType struct { | |
Any interface{} | |
} | |
func TestBasicTypes(t *testing.T) { | |
t.Run("interface map", func(t *testing.T) { | |
j := ` | |
{ | |
"string": "string", | |
"int": 42, | |
"number": 6.66, | |
"bool": true, | |
"object": { | |
"key": "value" | |
}, | |
"array": ["item1", "item2"] | |
}` | |
result := make(map[string]interface{}) | |
checkErr(t, json.Unmarshal([]byte(j), &result)) | |
assertEqual(t, result["string"], "string") | |
assertEqual(t, result["int"], float64(42)) | |
assertEqual(t, result["number"], 6.66) | |
assertEqual(t, result["bool"], true) | |
object := result["object"].(map[string]interface{}) | |
assertEqual(t, object["key"].(string), "value") | |
array := result["array"].([]interface{}) | |
assertEqual(t, array[1].(string), "item2") | |
}) | |
t.Run("struct", func(t *testing.T) { | |
j := ` | |
{ | |
"string": "string", | |
"int": 42, | |
"number": 6.66, | |
"bool": true, | |
"object": { | |
"key": "value" | |
}, | |
"array": ["item1", "item2"] | |
}` | |
result := &BasicTypes{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.String, "string") | |
assertEqual(t, result.Int, 42) | |
assertEqual(t, result.Number, 6.66) | |
assertEqual(t, result.Bool, true) | |
assertEqual(t, result.Object["key"], "value") | |
assertEqual(t, result.Array[1], "item2") | |
}) | |
t.Run("Ignore case", func(t *testing.T) { | |
j := ` | |
{ | |
"String": "string", | |
"iNt": 42, | |
"NumbeR": 6.66, | |
"BOOL": true, | |
"OBJECT": { | |
"KEY": "value" | |
}, | |
"aRRay": ["item1", "item2"] | |
}` | |
result := &BasicTypes{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.String, "string") | |
assertEqual(t, result.Int, 42) | |
assertEqual(t, result.Number, 6.66) | |
assertEqual(t, result.Bool, true) | |
assertEqual(t, result.Object["KEY"], "value") | |
assertEqual(t, result.Array[1], "item2") | |
}) | |
t.Run("Default values", func(t *testing.T) { | |
j := `{"valid": "json"}` | |
result := &BasicTypes{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.String, "") | |
assertEqual(t, result.Int, 0) | |
assertEqual(t, result.Number, float64(0)) | |
assertEqual(t, result.Bool, false) | |
assertEqual(t, len(result.Object), 0) | |
assertEqual(t, len(result.Array), 0) | |
}) | |
t.Run("null values", func(t *testing.T) { | |
j := ` | |
{ | |
"string": null, | |
"number": null, | |
"bool": null, | |
"object": null, | |
"array": null | |
}` | |
result := &BasicTypes{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.String, "") | |
assertEqual(t, result.Int, 0) | |
assertEqual(t, result.Number, float64(0)) | |
assertEqual(t, result.Bool, false) | |
assertEqual(t, len(result.Object), 0) | |
assertEqual(t, len(result.Array), 0) | |
}) | |
t.Run("null values with pointers", func(t *testing.T) { | |
j := ` | |
{ | |
"object": null, | |
"array": null | |
}` | |
var result = struct { | |
Object *Dummy | |
Array *[]string | |
}{} | |
checkErr(t, json.Unmarshal([]byte(j), &result)) | |
if result.Object != nil { | |
t.Fatalf("%v != nil", result.Object) | |
} | |
if result.Array != nil { | |
t.Fatalf("%v != nil", result.Array) | |
} | |
}) | |
t.Run("empty collections", func(t *testing.T) { | |
j := ` | |
{ | |
"object": {}, | |
"array": [] | |
}` | |
result := &BasicTypes{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, len(result.Object), 0) | |
assertEqual(t, len(result.Array), 0) | |
}) | |
t.Run("empty collections with pointers", func(t *testing.T) { | |
j := ` | |
{ | |
"object": {}, | |
"array": [] | |
}` | |
var result = struct { | |
Object *Dummy | |
Array *[]string | |
}{} | |
checkErr(t, json.Unmarshal([]byte(j), &result)) | |
assertEqual(t, result.Object.Key, "") | |
assertEqual(t, len(*result.Array), 0) | |
}) | |
} | |
func TestNestedObject(t *testing.T) { | |
t.Run("Happy", func(t *testing.T) { | |
j := ` | |
{ | |
"object": { | |
"key": "value" | |
} | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.Object.Key, "value") | |
}) | |
t.Run("Missing object", func(t *testing.T) { | |
j := ` | |
{ | |
"name": "string" | |
}` | |
result := &NestedObject{} | |
object := Dummy{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.Object, object) | |
if result.PointerObject != nil { | |
t.Fatalf("%v != nil", result.PointerObject) | |
} | |
}) | |
t.Run("null value object", func(t *testing.T) { | |
j := ` | |
{ | |
"pointerObject": null | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
if result.PointerObject != nil { | |
t.Fatalf("%v != nil", result.PointerObject) | |
} | |
}) | |
} | |
func TestAnyType(t *testing.T) { | |
t.Run("number to float", func(t *testing.T) { | |
j := ` | |
{ | |
"anyType": 1 | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.AnyType, float64(1)) | |
}) | |
t.Run("string", func(t *testing.T) { | |
j := ` | |
{ | |
"anyType": "string" | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
assertEqual(t, result.AnyType, "string") | |
}) | |
t.Run("array", func(t *testing.T) { | |
j := ` | |
{ | |
"anyType": ["item1", "item2"] | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
var slice = result.AnyType.([]interface{}) | |
assertEqual(t, slice[1], "item2") | |
}) | |
t.Run("object", func(t *testing.T) { | |
j := ` | |
{ | |
"anyType": {"key": "val"} | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
var mp = result.AnyType.(map[string]interface{}) | |
assertEqual(t, mp["key"], "val") | |
}) | |
t.Run("nested object", func(t *testing.T) { | |
j := ` | |
{ | |
"anyType": {"key": {"inner": "val"}} | |
}` | |
result := &NestedObject{} | |
checkErr(t, json.Unmarshal([]byte(j), result)) | |
var mp = result.AnyType.(map[string]interface{}) | |
var inner = mp["key"].(map[string]interface{}) | |
assertEqual(t, inner["inner"], "val") | |
}) | |
} | |
func TestSerialize(t *testing.T) { | |
t.Run("Capitalized default", func(t *testing.T) { | |
var b = struct { | |
Capitalized string | |
}{ | |
"v", | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Capitalized":"v"}`) | |
}) | |
t.Run("uppercase", func(t *testing.T) { | |
var b = struct { | |
UPPER string | |
}{ | |
"v", | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"UPPER":"v"}`) | |
}) | |
t.Run("omitempty", func(t *testing.T) { | |
var b = struct { | |
Key string | |
Optional string `json:",omitempty"` | |
}{} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Key":""}`) | |
}) | |
t.Run("empty array", func(t *testing.T) { | |
var b = struct { | |
Arr []string | |
}{ | |
[]string{}, | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Arr":[]}`) | |
}) | |
t.Run("empty array null", func(t *testing.T) { | |
output := struct { | |
Arr []string | |
}{} | |
out, err := json.Marshal(output) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Arr":null}`) | |
}) | |
t.Run("empty array with nil pointer", func(t *testing.T) { | |
var arr []string | |
output := struct { | |
Array *[]string `json:"array"` | |
}{ | |
&arr, | |
} | |
out, err := json.Marshal(output) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"array":null}`) | |
}) | |
t.Run("empty array with make", func(t *testing.T) { | |
output := struct { | |
Array []string `json:"array"` | |
}{ | |
make([]string, 0), | |
} | |
out, err := json.Marshal(output) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"array":[]}`) | |
}) | |
t.Run("empty object", func(t *testing.T) { | |
type inner struct { | |
Key string `json:"key,omitempty"` | |
} | |
output := struct { | |
Inner inner `json:"inner"` | |
}{ | |
inner{}, | |
} | |
out, err := json.Marshal(output) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"inner":{}}`) | |
}) | |
} | |
func TestNestedSerialize(t *testing.T) { | |
t.Run("Empty defaults", func(t *testing.T) { | |
var b = NestedObject{ | |
Object: Dummy{}, | |
PointerObject: nil, | |
AnyType: nil, | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Object":{"Key":""},"PointerObject":null,"AnyType":null}`) | |
}) | |
t.Run("Empty defaults with omitempty", func(t *testing.T) { | |
var b = NestedOutput{ | |
Object: Dummy{}, | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Object":{"Key":""},"PointerObject":null,"AnyType":null}`) | |
}) | |
t.Run("Explicit defaults with omitempty", func(t *testing.T) { | |
var b = OptionalString{ | |
Key: "key", | |
Val: "", | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"Key":"key"}`) | |
}) | |
} | |
type Date struct { | |
time.Time | |
} | |
func (d Date) MarshalJSON() ([]byte, error) { | |
return []byte(d.Format("2006-01-02")), nil | |
} | |
func (t *Date) UnmarshalJSON(data []byte) error { | |
var s string | |
if err := json.Unmarshal(data, &s); err != nil { | |
return err | |
} | |
p, err := time.Parse("2006-01-02", s) | |
if err != nil { | |
return err | |
} | |
t.Time = p | |
return nil | |
} | |
type UnixTime struct { | |
time.Time | |
} | |
func (t UnixTime) MarshalJSON() ([]byte, error) { | |
return json.Marshal(t.Unix()) | |
} | |
func (t *UnixTime) UnmarshalJSON(data []byte) error { | |
var i int64 | |
if err := json.Unmarshal(data, &i); err != nil { | |
return err | |
} | |
t.Time = time.Unix(i, 0) | |
return nil | |
} | |
func TestDateTime(t *testing.T) { | |
t.Run("Encode time to string", func(t *testing.T) { | |
d, _ := time.Parse("2006-01-02", "2020-11-23") | |
var b = struct { | |
Time time.Time `json:"time"` | |
}{ | |
d, | |
} | |
out, err := json.Marshal(b) | |
checkErr(t, err) | |
assertEqual(t, string(out), `{"time":"2020-11-23T00:00:00Z"}`) | |
}) | |
t.Run("Decode string to time", func(t *testing.T) { | |
var b = struct { | |
Time time.Time `json:"time"` | |
}{} | |
inp := `{"time":"2020-11-23T00:00:00Z"}` | |
err := json.Unmarshal([]byte(inp), &b) | |
checkErr(t, err) | |
d, _ := time.Parse("2006-01-02", "2020-11-23") | |
assertEqual(t, d, b.Time) | |
}) | |
t.Run("Decode date string to time", func(t *testing.T) { | |
expect, _ := time.Parse("2006-01-02", "2020-11-23") | |
var b = struct { | |
Time Date `json:"time"` | |
}{} | |
inp := `{"time":"2020-11-23"}` | |
err := json.Unmarshal([]byte(inp), &b) | |
if err != nil { | |
t.Fatalf("%v\n", err) | |
} | |
got := b.Time | |
if expect.Year() != got.Year() || expect.Month() != got.Month() || expect.Day() != got.Day() { | |
t.Fatalf("Expected %s != %s", expect, got) | |
} | |
}) | |
t.Run("Encode time to unix timestamp", func(t *testing.T) { | |
var expect = `{"time":1606089600}` | |
d, _ := time.Parse("2006-01-02", "2020-11-23") | |
var ut = UnixTime{d} | |
var b = struct { | |
Time UnixTime `json:"time"` | |
}{ | |
ut, | |
} | |
out, err := json.Marshal(b) | |
if err != nil { | |
t.Fatalf("%v\n", err) | |
} | |
got := string(out) | |
if reflect.DeepEqual(expect, got) != true { | |
t.Fatalf("Expected %s != %s", expect, got) | |
} | |
}) | |
t.Run("Decode unix timestamp to time", func(t *testing.T) { | |
var expect = int64(1606089600) | |
var b = struct { | |
Time UnixTime `json:"time"` | |
}{} | |
err := json.Unmarshal([]byte(`{"time":1606089600}`), &b) | |
if err != nil { | |
t.Fatalf("%v\n", err) | |
} | |
got := b.Time.Unix() | |
if expect != got { | |
t.Fatalf("Expected %d != %d", expect, got) | |
} | |
}) | |
} | |
func assertEqual(t *testing.T, a interface{}, b interface{}) { | |
switch a.(type) { | |
case interface{}: | |
if reflect.DeepEqual(a, b) != true { | |
t.Fatalf("%s != %s", a, b) | |
} | |
default: | |
t.Fatalf("%s != %s", a, b) | |
} | |
} | |
func checkErr(t *testing.T, err error) { | |
if err != nil { | |
t.Fatalf("%v\n", err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment