package common import ( "math" "testing" "time" ) // ───────────────────────────────────────────── // 辅助:检查 Error 是否携带错误 // ───────────────────────────────────────────── func hasErr(e *Error) bool { return e.GetError() != nil } func newErr() *Error { return &Error{} } // ───────────────────────────────────────────── // ObjToMap // ───────────────────────────────────────────── func TestObjToMap(t *testing.T) { t.Run("nil returns nil with error", func(t *testing.T) { e := newErr() v := ObjToMap(nil, e) if v != nil || !hasErr(e) { t.Errorf("nil: got %v err=%v", v, e.GetError()) } }) t.Run("Map passthrough", func(t *testing.T) { input := Map{"a": 1} v := ObjToMap(input) if v == nil || v["a"] != 1 { t.Errorf("Map passthrough failed: %v", v) } }) t.Run("map[string]interface{} cast", func(t *testing.T) { input := map[string]interface{}{"b": 2} v := ObjToMap(input) if v == nil || v["b"] != 2 { t.Errorf("map[string]interface{} cast failed: %v", v) } }) t.Run("valid JSON string - integer preserved as int64", func(t *testing.T) { v := ObjToMap(`{"x":42}`) if v == nil || v["x"] != int64(42) { t.Errorf("JSON string: got %v (type %T), want int64(42)", v["x"], v["x"]) } }) t.Run("invalid JSON string returns nil with error", func(t *testing.T) { e := newErr() v := ObjToMap(`not json`, e) if v != nil || !hasErr(e) { t.Errorf("invalid JSON: got %v err=%v", v, e.GetError()) } }) t.Run("struct default branch marshal+unmarshal", func(t *testing.T) { type S struct{ Name string } v := ObjToMap(S{Name: "hello"}) if v == nil || v["Name"] != "hello" { t.Errorf("struct default: %v", v) } }) t.Run("marshal fail (channel) returns nil with error", func(t *testing.T) { e := newErr() ch := make(chan int) v := ObjToMap(ch, e) if v != nil || !hasErr(e) { t.Errorf("channel marshal: got %v err=%v", v, e.GetError()) } }) t.Run("default marshal OK but unmarshal to map fails (int)", func(t *testing.T) { // json.Marshal(123) = "123", which can't unmarshal to Map e := newErr() v := ObjToMap(123, e) if v != nil || !hasErr(e) { t.Errorf("int default: got %v err=%v", v, e.GetError()) } }) } // ───────────────────────────────────────────── // ObjToMapArray // ───────────────────────────────────────────── func TestObjToMapArray(t *testing.T) { t.Run("nil returns empty", func(t *testing.T) { e := newErr() v := ObjToMapArray(nil, e) if len(v) != 0 || !hasErr(e) { t.Errorf("nil: got %v err=%v", v, e.GetError()) } }) t.Run("JSON array of maps", func(t *testing.T) { v := ObjToMapArray(`[{"a":1},{"b":2}]`) if len(v) != 2 { t.Errorf("expected 2 elements, got %d", len(v)) } }) } // ───────────────────────────────────────────── // ObjToSlice // ───────────────────────────────────────────── func TestObjToSlice(t *testing.T) { t.Run("nil returns nil with error", func(t *testing.T) { e := newErr() v := ObjToSlice(nil, e) if v != nil || !hasErr(e) { t.Errorf("nil: got %v err=%v", v, e.GetError()) } }) t.Run("Slice passthrough", func(t *testing.T) { input := Slice{1, 2, 3} v := ObjToSlice(input) if len(v) != 3 { t.Errorf("Slice passthrough: %v", v) } }) t.Run("[]interface{} cast", func(t *testing.T) { input := []interface{}{4, 5} v := ObjToSlice(input) if len(v) != 2 || v[0] != 4 { t.Errorf("[]interface{} cast: %v", v) } }) t.Run("[]string conversion", func(t *testing.T) { input := []string{"a", "b", "c"} v := ObjToSlice(input) if len(v) != 3 || v[0] != "a" { t.Errorf("[]string: %v", v) } }) t.Run("valid JSON array string", func(t *testing.T) { v := ObjToSlice(`[1,2,3]`) if len(v) != 3 { t.Errorf("JSON array: %v", v) } }) t.Run("invalid JSON string returns error", func(t *testing.T) { e := newErr() v := ObjToSlice(`not json`, e) if !hasErr(e) { t.Errorf("invalid JSON: got %v err=%v", v, e.GetError()) } }) t.Run("default: []int marshals to JSON array", func(t *testing.T) { v := ObjToSlice([]int{10, 20, 30}) if len(v) != 3 { t.Errorf("[]int default: %v", v) } }) t.Run("default: marshal fail (channel) returns error", func(t *testing.T) { e := newErr() ch := make(chan int) ObjToSlice(ch, e) if !hasErr(e) { t.Errorf("channel marshal: expected error") } }) } // ───────────────────────────────────────────── // ObjToTime // ───────────────────────────────────────────── func TestObjToTime(t *testing.T) { mustParse := func(layout, s string) time.Time { tt, err := time.Parse(layout, s) if err != nil { panic(err) } return tt } t.Run("full datetime string", func(t *testing.T) { ts := ObjToTime("2006-01-02 15:04:05") want := mustParse("2006-01-02 15:04:05", "2006-01-02 15:04:05") if ts == nil || !ts.Equal(want) { t.Errorf("full datetime: %v", ts) } }) t.Run("single digit day+time (bug fix)", func(t *testing.T) { ts := ObjToTime("2006-1-2 15:04:05") want := mustParse("2006-01-02 15:04:05", "2006-01-02 15:04:05") if ts == nil || !ts.Equal(want) { t.Errorf("single digit day+time: got %v, want %v", ts, want) } }) t.Run("datetime without seconds", func(t *testing.T) { ts := ObjToTime("2006-01-02 15:04") want := mustParse("2006-01-02 15:04", "2006-01-02 15:04") if ts == nil || !ts.Equal(want) { t.Errorf("datetime without seconds: %v", ts) } }) t.Run("date+hour only", func(t *testing.T) { ts := ObjToTime("2006-01-02 15") want := mustParse("2006-01-02 15", "2006-01-02 15") if ts == nil || !ts.Equal(want) { t.Errorf("date+hour: %v", ts) } }) t.Run("date only", func(t *testing.T) { ts := ObjToTime("2006-01-02") want := mustParse("2006-01-02", "2006-01-02") if ts == nil || !ts.Equal(want) { t.Errorf("date only: %v", ts) } }) t.Run("month only", func(t *testing.T) { ts := ObjToTime("2006-01") want := mustParse("2006-01", "2006-01") if ts == nil || !ts.Equal(want) { t.Errorf("month only: %v", ts) } }) t.Run("invalid string returns nil", func(t *testing.T) { ts := ObjToTime("not-a-date") if ts != nil { t.Errorf("invalid string: expected nil, got %v", ts) } }) t.Run("unix second timestamp (10 digits)", func(t *testing.T) { ts := ObjToTime(int64(1700000000)) // 10 digits if ts == nil { t.Error("second timestamp: got nil") } want := time.Unix(1700000000, 0) if !ts.Equal(want) { t.Errorf("second: %v vs %v", ts, want) } }) t.Run("unix millisecond timestamp (13 digits)", func(t *testing.T) { ts := ObjToTime(int64(1700000000000)) // 13 digits if ts == nil { t.Error("millisecond timestamp: got nil") } want := time.UnixMilli(1700000000000) if !ts.Equal(want) { t.Errorf("millisecond: %v vs %v", ts, want) } }) t.Run("unix microsecond timestamp (16 digits)", func(t *testing.T) { ts := ObjToTime(int64(1700000000000000)) // 16 digits if ts == nil { t.Error("microsecond timestamp: got nil") } want := time.UnixMicro(1700000000000000) if !ts.Equal(want) { t.Errorf("microsecond: %v vs %v", ts, want) } }) t.Run("nanosecond timestamp (19 digits)", func(t *testing.T) { ts := ObjToTime(int64(1700000000000000000)) // 19 digits -> nano branch if ts == nil { t.Error("nanosecond timestamp: got nil") } want := time.UnixMicro(1700000000000000000 / 1000) if !ts.Equal(want) { t.Errorf("nanosecond: %v vs %v", ts, want) } }) t.Run("year int (4 digits)", func(t *testing.T) { ts := ObjToTime(int64(2024)) if ts == nil { t.Error("year int: got nil") } want := mustParse("2006", "2024") if !ts.Equal(want) { t.Errorf("year: %v vs %v", ts, want) } }) t.Run("small int returns nil", func(t *testing.T) { ts := ObjToTime(int64(1)) if ts != nil { t.Errorf("small int: expected nil, got %v", ts) } }) t.Run("nil returns nil", func(t *testing.T) { ts := ObjToTime(nil) if ts != nil { t.Errorf("nil: expected nil, got %v", ts) } }) } // ───────────────────────────────────────────── // ObjToFloat64 // ───────────────────────────────────────────── func TestObjToFloat64(t *testing.T) { cases := []struct { name string input interface{} want float64 wantErr bool }{ {"nil", nil, 0, true}, {"int", 42, 42.0, false}, {"int64", int64(100), 100.0, false}, {"string valid", "3.14", 3.14, false}, {"string invalid", "abc", 0, true}, {"float64", float64(1.5), 1.5, false}, // Fix4: float32 通过 strconv 转换,消除精度膨胀噪声 {"float32 no precision inflation", float32(1.2), 1.2, false}, {"[]byte valid", []byte("2.71"), 2.71, false}, {"[]byte invalid", []byte("xyz"), 0, true}, {"unknown type", struct{}{}, 0, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { e := newErr() got := ObjToFloat64(c.input, e) if math.Abs(got-c.want) > 1e-9 { t.Errorf("value: got %v, want %v", got, c.want) } if hasErr(e) != c.wantErr { t.Errorf("error: got hasErr=%v, wantErr=%v (err=%v)", hasErr(e), c.wantErr, e.GetError()) } }) } t.Run("NaN is caught and zeroed", func(t *testing.T) { e := newErr() v := ObjToFloat64(math.NaN(), e) if v != 0 || !hasErr(e) { t.Errorf("NaN: got %v err=%v", v, e.GetError()) } }) t.Run("Inf is caught and zeroed", func(t *testing.T) { e := newErr() v := ObjToFloat64(math.Inf(1), e) if v != 0 || !hasErr(e) { t.Errorf("Inf: got %v err=%v", v, e.GetError()) } }) t.Run("-Inf is caught and zeroed", func(t *testing.T) { e := newErr() v := ObjToFloat64(math.Inf(-1), e) if v != 0 || !hasErr(e) { t.Errorf("-Inf: got %v err=%v", v, e.GetError()) } }) } // ───────────────────────────────────────────── // ObjToRoundFloat64 // ───────────────────────────────────────────── func TestObjToRoundFloat64(t *testing.T) { t.Run("precision 2 rounds correctly", func(t *testing.T) { v := ObjToRoundFloat64(3.5000001, 2) if v != 3.5 { t.Errorf("got %v, want 3.5", v) } }) t.Run("precision 0 rounds to integer", func(t *testing.T) { v := ObjToRoundFloat64(2.7, 0) if v != 3.0 { t.Errorf("got %v, want 3.0", v) } }) t.Run("precision -1 returns raw float", func(t *testing.T) { v := ObjToRoundFloat64(1.23456789, -1) if math.Abs(v-1.23456789) > 1e-9 { t.Errorf("got %v, want ~1.23456789", v) } }) t.Run("string input with float noise", func(t *testing.T) { v := ObjToRoundFloat64("3.15000001", 2) if v != 3.15 { t.Errorf("got %v, want 3.15", v) } }) } // ───────────────────────────────────────────── // ObjToCeil* // ───────────────────────────────────────────── func TestObjToCeil(t *testing.T) { t.Run("ObjToCeilInt64 rounds up", func(t *testing.T) { v := ObjToCeilInt64(1.1) if v != 2 { t.Errorf("got %d, want 2", v) } }) t.Run("ObjToCeilInt64 already integer", func(t *testing.T) { v := ObjToCeilInt64(float64(3)) if v != 3 { t.Errorf("got %d, want 3", v) } }) t.Run("ObjToCeilInt64 no double ceil (bug fix)", func(t *testing.T) { // 3.0 -> Ceil(3.0)=3.0 -> int64(3), NOT Ceil(3.0) again v := ObjToCeilInt64(float64(3)) if v != 3 { t.Errorf("double ceil: got %d, want 3", v) } }) t.Run("ObjToCeilFloat64", func(t *testing.T) { v := ObjToCeilFloat64(1.1) if v != 2.0 { t.Errorf("got %v, want 2.0", v) } }) t.Run("ObjToCeilInt", func(t *testing.T) { v := ObjToCeilInt(2.3) if v != 3 { t.Errorf("got %d, want 3", v) } }) } // ───────────────────────────────────────────── // ObjToInt64 // ───────────────────────────────────────────── func TestObjToInt64(t *testing.T) { cases := []struct { name string input interface{} want int64 wantErr bool }{ {"nil", nil, 0, true}, {"int", 7, 7, false}, {"int64", int64(99), 99, false}, {"string valid int", "42", 42, false}, // Fix3 fallback: "1.20" 原来报错,现在截断得 1 {"string float-like", "1.20", 1, false}, {"string invalid", "abc", 0, true}, {"[]byte valid int", []byte("123"), 123, false}, // Fix3 fallback: []byte "2.70" 截断得 2 {"[]byte float-like", []byte("2.70"), 2, false}, {"[]byte invalid", []byte("xyz"), 0, true}, {"float64 truncate", float64(5.9), 5, false}, {"float32 truncate", float32(3.1), 3, false}, {"unknown type", struct{}{}, 0, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { e := newErr() got := ObjToInt64(c.input, e) if got != c.want { t.Errorf("value: got %d, want %d", got, c.want) } if hasErr(e) != c.wantErr { t.Errorf("error: got hasErr=%v, wantErr=%v (err=%v)", hasErr(e), c.wantErr, e.GetError()) } }) } } // ───────────────────────────────────────────── // ObjToInt // ───────────────────────────────────────────── func TestObjToInt(t *testing.T) { v := ObjToInt(10) if v != 10 { t.Errorf("got %d, want 10", v) } } // ───────────────────────────────────────────── // ObjToBool // ───────────────────────────────────────────── func TestObjToBool(t *testing.T) { t.Run("nil returns false with error", func(t *testing.T) { e := newErr() v := ObjToBool(nil, e) if v || !hasErr(e) { t.Errorf("nil: got %v err=%v", v, e.GetError()) } }) t.Run("bool true", func(t *testing.T) { e := newErr() v := ObjToBool(true, e) if !v || hasErr(e) { t.Errorf("bool true: got %v err=%v", v, e.GetError()) } }) t.Run("bool false", func(t *testing.T) { e := newErr() v := ObjToBool(false, e) if v || hasErr(e) { t.Errorf("bool false: got %v err=%v", v, e.GetError()) } }) t.Run("int 1 -> true, no error (bug fix)", func(t *testing.T) { e := newErr() v := ObjToBool(1, e) if !v || hasErr(e) { t.Errorf("int 1: got v=%v err=%v", v, e.GetError()) } }) t.Run("int 0 -> false, no error", func(t *testing.T) { e := newErr() v := ObjToBool(0, e) if v || hasErr(e) { t.Errorf("int 0: got v=%v err=%v", v, e.GetError()) } }) t.Run("unknown type returns false with error", func(t *testing.T) { e := newErr() v := ObjToBool(struct{}{}, e) if v || !hasErr(e) { t.Errorf("struct: got v=%v err=%v", v, e.GetError()) } }) } // ───────────────────────────────────────────── // ObjToStr // ───────────────────────────────────────────── func TestObjToStr(t *testing.T) { cases := []struct { name string input interface{} want string }{ {"nil", nil, ""}, {"int", 42, "42"}, {"uint8", uint8(65), "65"}, {"int64", int64(1234567890), "1234567890"}, {"[]byte", []byte("hello"), "hello"}, {"string", "world", "world"}, {"float64", float64(3.14), "3.14"}, {"float32", float32(2.5), "2.5"}, {"bool true", true, "true"}, {"bool false", false, "false"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := ObjToStr(c.input) if got != c.want { t.Errorf("got %q, want %q", got, c.want) } }) } t.Run("default (struct) marshals to JSON", func(t *testing.T) { type S struct{ X int } got := ObjToStr(S{X: 1}) if got == "" { t.Error("struct: expected non-empty JSON string") } }) } // ───────────────────────────────────────────── // StrToMap // ───────────────────────────────────────────── func TestStrToMap(t *testing.T) { t.Run("valid JSON", func(t *testing.T) { v := StrToMap(`{"k":"v"}`) if v["k"] != "v" { t.Errorf("StrToMap: %v", v) } }) t.Run("invalid JSON returns empty map", func(t *testing.T) { v := StrToMap(`not json`) if v == nil { t.Error("StrToMap invalid: expected empty map, got nil") } }) } // ───────────────────────────────────────────── // StrToSlice // ───────────────────────────────────────────── func TestStrToSlice(t *testing.T) { v := StrToSlice(`[1,2,3]`) if len(v) != 3 { t.Errorf("StrToSlice: expected 3 elements, got %d", len(v)) } } // ───────────────────────────────────────────── // StrArrayToJsonStr // ───────────────────────────────────────────── func TestStrArrayToJsonStr(t *testing.T) { cases := []struct { name string input string want string }{ {"empty returns []", "", "[]"}, {"short (<=2) returns []", "a", "[]"}, {"no commas", `"a","b"`, `["a","b"]`}, {"leading comma stripped", `,"a","b"`, `["a","b"]`}, {"trailing comma stripped", `"a","b",`, `["a","b"]`}, {"both commas stripped", `,"a","b",`, `["a","b"]`}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := StrArrayToJsonStr(c.input) if got != c.want { t.Errorf("got %q, want %q", got, c.want) } }) } } // ───────────────────────────────────────────── // JsonStrToStrArray // ───────────────────────────────────────────── func TestJsonStrToStrArray(t *testing.T) { t.Run("non-empty strips brackets", func(t *testing.T) { got := JsonStrToStrArray(`["a","b"]`) if got != `,"a","b",` { t.Errorf("got %q", got) } }) t.Run("empty string", func(t *testing.T) { got := JsonStrToStrArray("") if got != ",," { t.Errorf("got %q", got) } }) } // ───────────────────────────────────────────── // StrToInt // ───────────────────────────────────────────── func TestStrToInt(t *testing.T) { t.Run("valid", func(t *testing.T) { v, err := StrToInt("123") if v != 123 || err != nil { t.Errorf("got %d %v", v, err) } }) t.Run("invalid", func(t *testing.T) { _, err := StrToInt("abc") if err == nil { t.Error("expected error") } }) } // ───────────────────────────────────────────── // JsonToObj // ───────────────────────────────────────────── func TestJsonToObj(t *testing.T) { t.Run("integer preserved as int64", func(t *testing.T) { v, err := JsonToObj(`{"id":2}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m, ok := v.(map[string]interface{}) if !ok { t.Fatalf("expected map, got %T", v) } if m["id"] != int64(2) { t.Errorf("got %v (type %T), want int64(2)", m["id"], m["id"]) } }) t.Run("decimal preserved as float64 with correct precision", func(t *testing.T) { v, err := JsonToObj(`{"price":1.20}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m := v.(map[string]interface{}) price, ok := m["price"].(float64) if !ok { t.Fatalf("expected float64, got %T", m["price"]) } if price != 1.2 { t.Errorf("got %v, want 1.2", price) } }) t.Run("large integer preserved as int64", func(t *testing.T) { v, err := JsonToObj(`{"num":9999999}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m := v.(map[string]interface{}) if m["num"] != int64(9999999) { t.Errorf("got %v (type %T), want int64(9999999)", m["num"], m["num"]) } }) t.Run("array with mixed types", func(t *testing.T) { v, err := JsonToObj(`[1, 2.5, "hello", true]`) if err != nil { t.Fatalf("unexpected error: %v", err) } arr := v.([]interface{}) if len(arr) != 4 { t.Fatalf("expected 4 elements, got %d", len(arr)) } if arr[0] != int64(1) { t.Errorf("arr[0]: got %v (%T), want int64(1)", arr[0], arr[0]) } if arr[1] != float64(2.5) { t.Errorf("arr[1]: got %v (%T), want float64(2.5)", arr[1], arr[1]) } if arr[2] != "hello" { t.Errorf("arr[2]: got %v, want hello", arr[2]) } if arr[3] != true { t.Errorf("arr[3]: got %v, want true", arr[3]) } }) t.Run("nested object integers preserved", func(t *testing.T) { v, err := JsonToObj(`{"order":{"id":5,"num":3,"price":1.20}}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m := v.(map[string]interface{}) order := m["order"].(map[string]interface{}) if order["id"] != int64(5) { t.Errorf("nested id: got %v (%T), want int64(5)", order["id"], order["id"]) } if order["num"] != int64(3) { t.Errorf("nested num: got %v (%T), want int64(3)", order["num"], order["num"]) } if order["price"] != float64(1.2) { t.Errorf("nested price: got %v, want 1.2", order["price"]) } }) t.Run("invalid JSON returns error", func(t *testing.T) { _, err := JsonToObj(`not json`) if err == nil { t.Error("expected error for invalid JSON") } }) t.Run("empty object", func(t *testing.T) { v, err := JsonToObj(`{}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m, ok := v.(map[string]interface{}) if !ok || len(m) != 0 { t.Errorf("expected empty map, got %v", v) } }) t.Run("null value", func(t *testing.T) { v, err := JsonToObj(`{"x":null}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m := v.(map[string]interface{}) if m["x"] != nil { t.Errorf("expected nil, got %v", m["x"]) } }) t.Run("zero integer", func(t *testing.T) { v, err := JsonToObj(`{"count":0}`) if err != nil { t.Fatalf("unexpected error: %v", err) } m := v.(map[string]interface{}) if m["count"] != int64(0) { t.Errorf("got %v (%T), want int64(0)", m["count"], m["count"]) } }) } // ───────────────────────────────────────────── // ObjToMap 数字类型保留(新行为) // ───────────────────────────────────────────── func TestObjToMapNumberPreservation(t *testing.T) { t.Run("JSON integer -> int64", func(t *testing.T) { v := ObjToMap(`{"id":2,"num":0}`) if v["id"] != int64(2) { t.Errorf("id: got %v (%T), want int64(2)", v["id"], v["id"]) } if v["num"] != int64(0) { t.Errorf("num: got %v (%T), want int64(0)", v["num"], v["num"]) } }) t.Run("JSON decimal -> float64", func(t *testing.T) { v := ObjToMap(`{"price":1.20,"rate":0.5}`) if v["price"] != float64(1.2) { t.Errorf("price: got %v (%T), want float64(1.2)", v["price"], v["price"]) } if v["rate"] != float64(0.5) { t.Errorf("rate: got %v (%T), want float64(0.5)", v["rate"], v["rate"]) } }) t.Run("struct with int field -> int64", func(t *testing.T) { type S struct { Count int `json:"count"` Name string `json:"name"` } v := ObjToMap(S{Count: 3, Name: "test"}) if v == nil { t.Fatal("got nil") } if v["count"] != int64(3) { t.Errorf("count: got %v (%T), want int64(3)", v["count"], v["count"]) } }) } // ───────────────────────────────────────────── // ObjToSlice 数字类型保留(新行为) // ───────────────────────────────────────────── func TestObjToSliceNumberPreservation(t *testing.T) { t.Run("JSON integer array -> int64 elements", func(t *testing.T) { v := ObjToSlice(`[1, 2, 3]`) if len(v) != 3 { t.Fatalf("expected 3, got %d", len(v)) } if v[0] != int64(1) { t.Errorf("v[0]: got %v (%T), want int64(1)", v[0], v[0]) } }) t.Run("JSON mixed array - integer and float", func(t *testing.T) { v := ObjToSlice(`[2, 1.5, "hello"]`) if v[0] != int64(2) { t.Errorf("v[0]: got %v (%T), want int64(2)", v[0], v[0]) } if v[1] != float64(1.5) { t.Errorf("v[1]: got %v (%T), want float64(1.5)", v[1], v[1]) } }) } // ───────────────────────────────────────────── // Map.JsonToMap 数字类型保留(新行为) // ───────────────────────────────────────────── func TestMapJsonToMapNumberPreservation(t *testing.T) { t.Run("integer -> int64", func(t *testing.T) { m := Map{} m.JsonToMap(`{"id":5,"count":0}`) if m["id"] != int64(5) { t.Errorf("id: got %v (%T), want int64(5)", m["id"], m["id"]) } if m["count"] != int64(0) { t.Errorf("count: got %v (%T), want int64(0)", m["count"], m["count"]) } }) t.Run("decimal -> float64", func(t *testing.T) { m := Map{} m.JsonToMap(`{"price":1.20}`) if m["price"] != float64(1.2) { t.Errorf("price: got %v (%T), want float64(1.2)", m["price"], m["price"]) } }) t.Run("StrToMap integer -> int64", func(t *testing.T) { m := StrToMap(`{"total":100,"amount":1.50}`) if m["total"] != int64(100) { t.Errorf("total: got %v (%T), want int64(100)", m["total"], m["total"]) } if m["amount"] != float64(1.5) { t.Errorf("amount: got %v (%T), want float64(1.5)", m["amount"], m["amount"]) } }) t.Run("invalid JSON sets error", func(t *testing.T) { e := newErr() m := Map{} m.JsonToMap(`not json`, e) if !hasErr(e) { t.Error("expected error for invalid JSON") } }) }