Compare commits

...

7 Commits
v1.2.8 ... main

Author SHA1 Message Date
AI Engineer
286e5e7317 chore: infrastructure alignment and doc sync (by AICoder) 2026-05-16 01:18:57 +08:00
AI Engineer
0d9b520680 chore: infrastructure alignment and doc sync (by codervall) 2026-05-14 20:46:19 +08:00
AI Engineer
879606d0ef fix(cast): JSON tag '-' handling consistency 2026-05-14 15:32:10 +08:00
Star
7140e29b9e feat: support hex strings in parseInt/parseUint (by AI) 2026-05-12 14:33:29 +08:00
AI Engineer
b3a4b5f13a 对齐 Tag v1.3.0 (By AI) 2026-05-10 15:48:08 +08:00
AI Engineer
08a089fb87 chore: infrastructure alignment 2026-05-10 12:48:06 +08:00
AI Engineer
8b5ff08d62 chore(cast): 移除 jsontag 依赖 2026-05-09 16:30:01 +08:00
10 changed files with 50 additions and 12 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.gemini/
.ai/
.geminiignore
.gemini
/CODE-FULL.md

View File

@ -1,5 +1,9 @@
# CHANGELOG # CHANGELOG
## [v1.2.9] - 2026-05-09
### Changed
- **移除第三方依赖**: 移除了对 `jsontag` 模块的依赖,统一使用标准库及自有基础设施对齐,增强了模块的独立性与长期稳定性。
## [v1.2.8] - 2026-05-05 ## [v1.2.8] - 2026-05-05
### Fixed ### Fixed
- **JSON 解码器深度增强**: 修复了 `cast.UnmarshalJSON` 在反序列化到 `interface{}` 类型字段时会跳过对象和数组的重大缺陷。 - **JSON 解码器深度增强**: 修复了 `cast.UnmarshalJSON` 在反序列化到 `interface{}` 类型字段时会跳过对象和数组的重大缺陷。

View File

@ -77,13 +77,23 @@ list, _ := cast.ToSlice[int]([]string{"1", "2", "3"})
* `Ptr[T any](T) *T` —— 快速取指针。 * `Ptr[T any](T) *T` —— 快速取指针。
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速构建索引 Map。 * `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速构建索引 Map。
6. **基础转换 (直接调用,极致性能)** 6. **基础转换与时间 (直接调用,极致性能)**
* `Int`, `Int64`, `Uint`, `Uint64`, `Float`, `Float64`, `String`, `Bool`, `Duration` * `Int`, `Int64`, `Uint`, `Uint64`, `Float`, `Float64`, `String`, `Bool`, `Duration`
* `RealValue(reflect.Value) reflect.Value` —— 递归获取指针或接口下的真实值。
* `ParseTime(any) time.Time` —— 强大的时间解析支持时间戳、RFC3339、JS 格式、紧凑格式及中文日期。 * `ParseTime(any) time.Time` —— 强大的时间解析支持时间戳、RFC3339、JS 格式、紧凑格式及中文日期。
* `FormatTime(layout, any) string` —— 直观格式化(如 YYYY-MM-DD HH:mm:ss * `FormatTime(layout, any) string` —— 直观格式化(如 YYYY-MM-DD HH:mm:ss
* `AddTime(expr, any) time.Time` —— DSL 时间计算(如 +1Y-2M+3D * `AddTime(expr, any) time.Time` —— DSL 时间计算(如 +1Y-2M+3D
* `DescribeDuration(time.Duration) string` —— 将时长转化为自然语言描述(如 1h 5m
7. **时区支持** 7. **字符串与参数处理**
* `Split(s, sep string) []string` —— 增强型分割,自动 Trim 并过滤空字符串。
* `SplitArgs(string) []string` —— 命令行参数分割,支持引号与转义。
* `JoinArgs([]string, sep string) string` —— 命令行参数合并,自动处理引号与转义。
* `UniqueAppend(to []string, from ...any) []string` —— 去重追加。
* `GetLowerName(string) string` | `GetUpperName(string) string` —— 首字母大小写转换工具。
* `FixUpperCase([]byte, []string)` —— 针对 JSON Key 的特殊大小写修复工具。
8. **时区支持**
* `DefaultTimeZone` —— 全局默认时区上下文。 * `DefaultTimeZone` —— 全局默认时区上下文。
* `SetDefaultTimeZone(*time.Location)` —— 修改全局默认时区(影响所有 Convert 与 ToTime 操作)。 * `SetDefaultTimeZone(*time.Location)` —— 修改全局默认时区(影响所有 Convert 与 ToTime 操作)。
* `DefaultTimeZone.Now()` —— 获取时区上下文下的当前时间。 * `DefaultTimeZone.Now()` —— 获取时区上下文下的当前时间。

View File

@ -66,8 +66,8 @@ func BenchmarkString(b *testing.B) {
func BenchmarkJSON(b *testing.B) { func BenchmarkJSON(b *testing.B) {
type User struct { type User struct {
ID int `json:"id"` ID int
Name string `json:"name"` Name string
} }
u := User{ID: 1, Name: "Benchmark User"} u := User{ID: 1, Name: "Benchmark User"}
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -140,8 +140,8 @@ func BenchmarkString_FastPath(b *testing.B) {
// 4. 测试 ToJSON 性能 (对比自定义逻辑与标准库) // 4. 测试 ToJSON 性能 (对比自定义逻辑与标准库)
func BenchmarkToJSON_SimpleStruct(b *testing.B) { func BenchmarkToJSON_SimpleStruct(b *testing.B) {
type User struct { type User struct {
ID int `json:"id"` ID int
Name string `json:"name"` Name string
} }
u := User{ID: 1, Name: "Benchmark User"} u := User{ID: 1, Name: "Benchmark User"}
b.ResetTimer() b.ResetTimer()

12
cast.go
View File

@ -527,6 +527,12 @@ func isComplexValue(v any) bool {
} }
func parseInt(s string) int64 { func parseInt(s string) int64 {
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
i, err := strconv.ParseInt(s, 0, 64)
if err == nil {
return i
}
}
i, err := strconv.ParseInt(s, 10, 64) i, err := strconv.ParseInt(s, 10, 64)
if err == nil { if err == nil {
return i return i
@ -538,6 +544,12 @@ func parseInt(s string) int64 {
} }
func parseUint(s string) uint64 { func parseUint(s string) uint64 {
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
i, err := strconv.ParseUint(s, 0, 64)
if err == nil {
return i
}
}
i, err := strconv.ParseUint(s, 10, 64) i, err := strconv.ParseUint(s, 10, 64)
if err == nil { if err == nil {
return i return i

View File

@ -55,7 +55,7 @@ func TestJSONToStruct(t *testing.T) {
func TestSpecialJSON(t *testing.T) { func TestSpecialJSON(t *testing.T) {
// 关键测试:特殊 HTML 字符序列化不应被转义 // 关键测试:特殊 HTML 字符序列化不应被转义
type Content struct { type Content struct {
Text string `json:"text"` Text string
} }
c := Content{Text: "<a> & <b>"} c := Content{Text: "<a> & <b>"}

View File

@ -46,10 +46,10 @@ func TestToSlice(t *testing.T) {
func TestJSON(t *testing.T) { func TestJSON(t *testing.T) {
type Config struct { type Config struct {
Port int `json:"port"` Port int
} }
data := `{"port": 8080}` data := `{"port": 8080}`
// Test UnmarshalJSON // Test UnmarshalJSON
var c1 Config var c1 Config
err := cast.UnmarshalJSON(data, &c1) err := cast.UnmarshalJSON(data, &c1)

View File

@ -445,7 +445,10 @@ func getDecoderFieldMap(reflectType reflect.Type) *decoderStructDescriptor {
// 1. Tag // 1. Tag
tag := field.Tag.Get("json") tag := field.Tag.Get("json")
if tag != "" && tag != "-" { if tag == "-" {
continue
}
if tag != "" {
parts := strings.Split(tag, ",") parts := strings.Split(tag, ",")
tagName := parts[0] tagName := parts[0]
for _, part := range parts { for _, part := range parts {

View File

@ -217,9 +217,12 @@ func getEncoderStructDescriptor(reflectType reflect.Type) *encoderStructDescript
} }
tag := field.Tag.Get("json") tag := field.Tag.Get("json")
if tag == "-" {
continue
}
fieldDesc.keepKey = strings.Contains(string(field.Tag), "keepKey") fieldDesc.keepKey = strings.Contains(string(field.Tag), "keepKey")
if tag != "" && tag != "-" { if tag != "" {
parts := strings.Split(tag, ",") parts := strings.Split(tag, ",")
for _, part := range parts { for _, part := range parts {
if strings.HasPrefix(part, "format=") { if strings.HasPrefix(part, "format=") {

View File

@ -2,6 +2,7 @@ package cast_test
import ( import (
"testing" "testing"
"apigo.cc/go/cast" "apigo.cc/go/cast"
) )
@ -28,7 +29,7 @@ func TestTo(t *testing.T) {
// JSON Auto conversion (Struct to String) // JSON Auto conversion (Struct to String)
type User struct { type User struct {
Name string `json:"name"` Name string
} }
u := User{Name: "Alice"} u := User{Name: "Alice"}
js := cast.To[string](u) js := cast.To[string](u)