Skip to content

Instantly share code, notes, and snippets.

@kerma
Last active December 5, 2020 13:57
Show Gist options
  • Save kerma/ea726055540da482810147df272375a5 to your computer and use it in GitHub Desktop.
Save kerma/ea726055540da482810147df272375a5 to your computer and use it in GitHub Desktop.
Some tests for checking encoding/json behaviour
// 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