Compare commits
No commits in common. "main" and "v1.2.7" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,5 +0,0 @@
|
|||||||
.gemini/
|
|
||||||
.ai/
|
|
||||||
.geminiignore
|
|
||||||
.gemini
|
|
||||||
/CODE-FULL.md
|
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,15 +1,5 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## [v1.2.9] - 2026-05-09
|
|
||||||
### Changed
|
|
||||||
- **移除第三方依赖**: 移除了对 `jsontag` 模块的依赖,统一使用标准库及自有基础设施对齐,增强了模块的独立性与长期稳定性。
|
|
||||||
|
|
||||||
## [v1.2.8] - 2026-05-05
|
|
||||||
### Fixed
|
|
||||||
- **JSON 解码器深度增强**: 修复了 `cast.UnmarshalJSON` 在反序列化到 `interface{}` 类型字段时会跳过对象和数组的重大缺陷。
|
|
||||||
- **高精度整数解析**: 优化了 JSON 解码过程中的数字处理逻辑。当目标为 `interface{}` 时,优先将整数解析为 `int64` 而非 `float64`,彻底解决了纳秒级时间戳(Nanosecond Timestamp)和 64 位长 ID 的精度丢失问题。
|
|
||||||
- **接口兼容性**: 完善了对 `interface{}` 类型的自动类型识别,支持正确解析为 `map[string]any` 和 `[]any`。
|
|
||||||
|
|
||||||
## [v1.2.6] - 2026-05-04
|
## [v1.2.6] - 2026-05-04
|
||||||
### Fixed
|
### Fixed
|
||||||
- **Map 深度合并修复**: 修复了在 `Convert` 或 `ToMap` 过程中,如果目标 Map 已存在该 Key,其原有结构体/Map 值会被直接覆盖而非深度合并的问题。通过引入 `dst.MapIndex` 预读取与临时寻址变量,现已完美支持 Map 下非指针结构体的局部字段覆盖。
|
- **Map 深度合并修复**: 修复了在 `Convert` 或 `ToMap` 过程中,如果目标 Map 已存在该 Key,其原有结构体/Map 值会被直接覆盖而非深度合并的问题。通过引入 `dst.MapIndex` 预读取与临时寻址变量,现已完美支持 Map 下非指针结构体的局部字段覆盖。
|
||||||
|
|||||||
14
README.md
14
README.md
@ -77,23 +77,13 @@ 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()` —— 获取时区上下文下的当前时间。
|
||||||
|
|||||||
@ -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
|
ID int `json:"id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
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
|
ID int `json:"id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
u := User{ID: 1, Name: "Benchmark User"}
|
u := User{ID: 1, Name: "Benchmark User"}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|||||||
12
cast.go
12
cast.go
@ -527,12 +527,6 @@ 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
|
||||||
@ -544,12 +538,6 @@ 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
|
||||||
|
|||||||
@ -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
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
c := Content{Text: "<a> & <b>"}
|
c := Content{Text: "<a> & <b>"}
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ func TestToSlice(t *testing.T) {
|
|||||||
|
|
||||||
func TestJSON(t *testing.T) {
|
func TestJSON(t *testing.T) {
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port int
|
Port int `json:"port"`
|
||||||
}
|
}
|
||||||
data := `{"port": 8080}`
|
data := `{"port": 8080}`
|
||||||
|
|
||||||
|
|||||||
@ -103,24 +103,8 @@ func (d *decoder) decodeValue(reflectValue reflect.Value, timeFormat string) err
|
|||||||
|
|
||||||
switch char {
|
switch char {
|
||||||
case '{':
|
case '{':
|
||||||
if reflectValue.Kind() == reflect.Interface {
|
|
||||||
m := make(map[string]any)
|
|
||||||
if err := d.decodeObject(reflect.ValueOf(&m).Elem()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reflectValue.Set(reflect.ValueOf(m))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return d.decodeObject(reflectValue)
|
return d.decodeObject(reflectValue)
|
||||||
case '[':
|
case '[':
|
||||||
if reflectValue.Kind() == reflect.Interface {
|
|
||||||
var s []any
|
|
||||||
if err := d.decodeArray(reflect.ValueOf(&s).Elem()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reflectValue.Set(reflect.ValueOf(s))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return d.decodeArray(reflectValue)
|
return d.decodeArray(reflectValue)
|
||||||
case '"':
|
case '"':
|
||||||
str, err := d.parseString()
|
str, err := d.parseString()
|
||||||
@ -139,8 +123,6 @@ func (d *decoder) decodeValue(reflectValue reflect.Value, timeFormat string) err
|
|||||||
reflectValue.SetFloat(Float64(str))
|
reflectValue.SetFloat(Float64(str))
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
reflectValue.SetBool(Bool(str))
|
reflectValue.SetBool(Bool(str))
|
||||||
case reflect.Interface:
|
|
||||||
reflectValue.Set(reflect.ValueOf(str))
|
|
||||||
default:
|
default:
|
||||||
// 尝试将字符串解析为具体对象(比如内部又是 JSON)
|
// 尝试将字符串解析为具体对象(比如内部又是 JSON)
|
||||||
if strings.HasPrefix(str, "{") || strings.HasPrefix(str, "[") {
|
if strings.HasPrefix(str, "{") || strings.HasPrefix(str, "[") {
|
||||||
@ -166,22 +148,9 @@ func (d *decoder) decodeValue(reflectValue reflect.Value, timeFormat string) err
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
reflectValue.SetString(literal)
|
reflectValue.SetString(literal)
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
// 优先作为数字处理以保留精度 (int64)
|
|
||||||
if literal == "true" {
|
|
||||||
reflectValue.Set(reflect.ValueOf(true))
|
|
||||||
} else if literal == "false" {
|
|
||||||
reflectValue.Set(reflect.ValueOf(false))
|
|
||||||
} else if literal == "null" {
|
|
||||||
reflectValue.Set(reflect.Zero(reflectValue.Type()))
|
|
||||||
} else if strings.Contains(literal, ".") || strings.Contains(literal, "e") || strings.Contains(literal, "E") {
|
|
||||||
reflectValue.Set(reflect.ValueOf(Float64(literal)))
|
|
||||||
} else if i, err := strconv.ParseInt(literal, 10, 64); err == nil {
|
|
||||||
reflectValue.Set(reflect.ValueOf(i))
|
|
||||||
} else {
|
|
||||||
reflectValue.Set(reflect.ValueOf(literal))
|
reflectValue.Set(reflect.ValueOf(literal))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,10 +414,7 @@ func getDecoderFieldMap(reflectType reflect.Type) *decoderStructDescriptor {
|
|||||||
|
|
||||||
// 1. Tag
|
// 1. Tag
|
||||||
tag := field.Tag.Get("json")
|
tag := field.Tag.Get("json")
|
||||||
if tag == "-" {
|
if tag != "" && 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 {
|
||||||
|
|||||||
@ -217,12 +217,9 @@ 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 != "" {
|
if tag != "" && 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=") {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package cast_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"apigo.cc/go/cast"
|
"apigo.cc/go/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +28,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
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
u := User{Name: "Alice"}
|
u := User{Name: "Alice"}
|
||||||
js := cast.To[string](u)
|
js := cast.To[string](u)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user