// 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) } }