mark
This commit is contained in:
parent
35014d50cb
commit
966f175846
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,6 +1,15 @@
|
||||
# CHANGELOG
|
||||
|
||||
## [v1.1.1] - 2026-05-02
|
||||
## [v1.1.2] - 2026-05-04
|
||||
### Added
|
||||
- 新增 `ToMap` 和 `ToSlice`: 侧重于原地填充/追加,支持 Struct 继承拍平、KV 序列化与自动类型转换。
|
||||
- 新增 `MakeMap`, `MakeSlice`, `FromJSON`, `FromYAML` 系列: 泛型构建新对象的零摩擦 API。
|
||||
- 增强 `UnmarshalJSON` / `UnmarshalYAML`: 重构为副作用模式(返回 error),支持多种输入类型。
|
||||
### Removed
|
||||
- 移除 `Ints` 和 `Strings`: 由更通用的 `MakeSlice` 替代,保持 API 简洁。
|
||||
|
||||
## [v1.1.1] - 2026-04-12
|
||||
|
||||
- **修复**: 解决 `FastEncoder` 在编码结构体时忽略匿名嵌入(Embedded)字段的问题,确保组合对象的完整序列化。
|
||||
|
||||
## [v1.1.0] - 2026-05-02
|
||||
|
||||
42
README.md
42
README.md
@ -22,21 +22,22 @@ go get apigo.cc/go/cast
|
||||
```go
|
||||
import "apigo.cc/go/cast"
|
||||
|
||||
// 像 JS 一样转换
|
||||
// 基础转换
|
||||
age := cast.Int("18") // 18
|
||||
|
||||
// 泛型三元运算
|
||||
status := cast.If(isAdmin, "Admin", "User")
|
||||
|
||||
// JSON 序列化增强
|
||||
var u struct { UserID int }
|
||||
cast.UnmarshalJSON("userId: 1001", &u) // u.UserID = 1001
|
||||
cast.MustToJSON(u) // "userId": 1001,首字母自动小写
|
||||
// 自动构建并填充 (Make 系列)
|
||||
list := cast.MakeSlice[int]("123") // []int{123}
|
||||
m := cast.MakeMap[string, any](map[string]int{"age": 18}) // map[string]any{"age": 18}
|
||||
|
||||
// 字符串切分增强
|
||||
cast.Split(",", ",") // [],忽略无效切片
|
||||
cast.Split("a, b, ", ",") // ["a", "b"],去除空白字符
|
||||
// JSON 泛型零摩擦构建 (From 系列,支持 string 或更安全的 []byte)
|
||||
user := cast.MustFromJSON[User](`{"name": "Tom"}`)
|
||||
|
||||
// 原地填充 (To 系列)
|
||||
var config map[string]any
|
||||
cast.ToMap(&config, myStruct) // 自动拍平 struct 并填充到 map
|
||||
```
|
||||
|
||||
## 🛠 API 指南
|
||||
@ -50,23 +51,26 @@ cast.Split("a, b, ", ",") // ["a", "b"],去除空白字符
|
||||
* `String(any) string` | `Bool(any) bool`
|
||||
* `Duration(any) time.Duration`
|
||||
|
||||
2. **批量转换(Slice Casting)**
|
||||
* `Ints(any) []int64`
|
||||
* `Strings(any) []string`
|
||||
2. **高级转化(Map/Slice Helpers)**
|
||||
* `ToMap(target any, source any)` —— 将 source 填充到目标 map。支持 struct (递归拍平且导出字段转小写)、slice (KV 序列) 或 map (合并)。
|
||||
* `ToSlice(target any, source any)` —— 将 source 填充到目标 slice。支持 map (KV 序列)、slice (追加) 或普通值 (追加)。
|
||||
* `MakeMap[K comparable, V any](source any) map[K]V` —— 泛型构建并填充新 Map。
|
||||
* `MakeSlice[T any](source any) []T` —— 泛型构建并填充新 Slice。
|
||||
|
||||
3. **泛型工具(Type Helpers)**
|
||||
3. **序列化与泛型构建(JSON & YAML)**
|
||||
* **JSON 编码**: `ToJSON(any)(string, error)` | `ToJSONDesensitize(any, []string)(string, error)` | `MustToJSON(any)string`
|
||||
* **JSON 解码 (原地)**: `UnmarshalJSON(data, any) error` (支持 string 或为了内存安全推荐使用的 []byte)
|
||||
* **JSON 构建 (泛型)**: `FromJSON[T](data) (T, error)` | `MustFromJSON[T](data) T`
|
||||
* **YAML 编码**: `ToYAML(any)(string, error)` | `MustToYAML(any)string`
|
||||
* **YAML 解码 (原地)**: `UnmarshalYAML(data, any) error`
|
||||
* **YAML 构建 (泛型)**: `FromYAML[T](data) (T, error)` | `MustFromYAML[T](data) T`
|
||||
|
||||
4. **泛型工具(Type Helpers)**
|
||||
* `If[T any](bool, T, T) T` —— 三元逻辑
|
||||
* `In[T comparable]([]T, T) bool` —— 包含判断
|
||||
* `Ptr[T any](T) *T` —— 取指针
|
||||
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速索引化
|
||||
|
||||
4. **序列化(JSON & YAML)**
|
||||
* **JSON 编码**: `ToJSON(any)(string, error)` | `ToJSONDesensitize(any, []string)(string, error)` | `MustToJSON(any)string` | `PrettyToJSON(any)string`
|
||||
* **JSON 字节**: `ToJSONBytes(any)([]byte, error)` | `ToJSONDesensitizeBytes(any, []string)([]byte, error)` | `MustJSONBytes(any)[]byte` | `PrettyToJSONBytes(any)[]byte`
|
||||
* **JSON 解码**: `UnmarshalJSON(string, any)(any, error)` | `MustUnmarshalJSON(string, any)any` | `UnmarshalJSONBytes([]byte, any)(any, error)` | `MustUnmarshalJSONBytes([]byte, any)any`
|
||||
* **YAML 编码**: `ToYAML(any)(string, error)` | `MustToYAML(any)string` | `YAMLBytes(any)([]byte, error)` | `MustYAMLBytes(any)[]byte`
|
||||
* **YAML 解码**: `UnmarshalYAML(string, any)(any, error)` | `MustUnmarshalYAML(string, any)any` | `UnmarshalYAMLBytes([]byte, any)(any, error)` | `MustUnmarshalYAMLBytes([]byte, any)any`
|
||||
|
||||
5. **辅助工具(Utilities)**
|
||||
* **切分**: `Split(s, sep) []string` | `SplitArgs(s) []string`
|
||||
* **拼接**: `UniqueAppend([]string, ...any) []string` | `JoinArgs([]string, sep) string`
|
||||
|
||||
13
TEST.md
13
TEST.md
@ -2,13 +2,14 @@
|
||||
|
||||
## 覆盖场景 (Coverage Scenarios)
|
||||
- **核心类型转换**: `Int64`, `Uint64`, `Float64`, `Bool`, `String`,包括边界值、零值及非法字符串输入。
|
||||
- **复合类型处理**: `Ints`, `Strings` 自动解析 JSON 字符串或直接转换。
|
||||
- **复合类型处理**: `ToMap`, `ToSlice`, `MakeMap`, `MakeSlice` 等全能转化工具,支持 Struct 拍平。
|
||||
- **JSON/YAML 序列化**:
|
||||
- 深度结构体映射,支持 `FastEncoder` 单路径处理。
|
||||
- **去标签化算法**: 自动识别 `UserID` -> `userID` 等符合工程习惯的转换。
|
||||
- **脱敏支持**: `ToJSONDesensitize` 在编码阶段原生支持字段脱敏。
|
||||
- **Map 兼容性**: 原生支持 `map[any]any` 及 Goja 伪数组转换。
|
||||
- **JSON 反序列化**:
|
||||
- **JSON/YAML 构建**:
|
||||
- **Make 系列**: 实现泛型零摩擦构建新对象。
|
||||
- **FastDecoder**: 实现单路径流式解析,跳过中间 Map 分配。
|
||||
- **Frictionless 匹配**: 支持大小写不敏感、忽略下划线等灵活的 Key 映射规则。
|
||||
- **智能初始化**: 自动处理嵌套指针、Slice 和 Map 的初始化。
|
||||
@ -17,8 +18,10 @@
|
||||
|
||||
## 性能基准 (Benchmark Results - Intel(R) Core(TM) i9)
|
||||
- `If`: ~0.24 ns/op
|
||||
- `Int64`: ~20.4 ns/op
|
||||
- `ToJSON (SimpleStruct)`: ~448.5 ns/op (相比旧版提升 ~30%)
|
||||
- `ToJSON (DirtyMap)`: ~1126 ns/op (相比旧版提升 ~70%)
|
||||
- `Int64`: ~20 ns/op
|
||||
- `ToMap`: ~700-800 ns/op (含 Struct 拍平与类型转换)
|
||||
- `ToSlice`: ~1200-1400 ns/op (含 KV 展开与类型转换)
|
||||
- `ToJSON (SimpleStruct)`: ~440 ns/op (相比旧版提升 ~30%)
|
||||
- `ToJSON (DirtyMap)`: ~1100 ns/op (相比旧版提升 ~70%)
|
||||
- `UnmarshalJSON`: 高性能单路径解析,显著降低内存分配。
|
||||
- `UniqueAppend`: 大数据量下的 $O(n)$ 时间复杂度。
|
||||
|
||||
@ -36,7 +36,7 @@ func TestEdgeCases(t *testing.T) {
|
||||
}
|
||||
|
||||
// 4. JSON 异常输入
|
||||
if _, err := cast.UnmarshalJSON("{invalid_json}", nil); err == nil {
|
||||
if err := cast.UnmarshalJSON("{invalid_json}", nil); err == nil {
|
||||
t.Log("UnmarshalJSON handled invalid input")
|
||||
}
|
||||
}
|
||||
@ -88,6 +88,25 @@ func BenchmarkSplit(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToMap(b *testing.B) {
|
||||
m := make(map[string]int)
|
||||
kv := []any{"a", 1, "b", 2, "c", 3, "d", 4, "e", 5}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cast.ToMap(m, kv)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToSlice(b *testing.B) {
|
||||
m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
|
||||
var s []any
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s = s[:0]
|
||||
cast.ToSlice(&s, m)
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 测试 Int 转换的快速路径 (命中 switch case)
|
||||
func BenchmarkInt_FastPath(b *testing.B) {
|
||||
val := "123456"
|
||||
|
||||
279
cast.go
279
cast.go
@ -235,57 +235,6 @@ func Bool(value any) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func Ints(value any) []int64 {
|
||||
switch realValue := value.(type) {
|
||||
case []any:
|
||||
result := make([]int64, len(realValue))
|
||||
for i, v := range realValue {
|
||||
result[i] = Int64(v)
|
||||
}
|
||||
return result
|
||||
case string:
|
||||
if strings.HasPrefix(realValue, "[") {
|
||||
result := make([]int64, 0)
|
||||
UnmarshalJSON(realValue, &result)
|
||||
return result
|
||||
}
|
||||
return []int64{Int64(value)}
|
||||
}
|
||||
rv := reflect.ValueOf(value)
|
||||
if rv.Kind() == reflect.Pointer || rv.Kind() == reflect.Interface {
|
||||
if rv = RealValue(rv); rv.IsValid() && rv.CanInterface() && rv.Kind() != reflect.Pointer {
|
||||
return Ints(rv.Interface())
|
||||
}
|
||||
}
|
||||
return []int64{Int64(value)}
|
||||
}
|
||||
|
||||
func Strings(value any) []string {
|
||||
switch realValue := value.(type) {
|
||||
case []any:
|
||||
result := make([]string, len(realValue))
|
||||
for i, v := range realValue {
|
||||
result[i] = String(v)
|
||||
}
|
||||
return result
|
||||
case string:
|
||||
if strings.HasPrefix(realValue, "[") {
|
||||
result := make([]string, 0)
|
||||
UnmarshalJSON(realValue, &result)
|
||||
return result
|
||||
}
|
||||
return []string{String(value)}
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(value)
|
||||
if rv.Kind() == reflect.Pointer || rv.Kind() == reflect.Interface {
|
||||
if rv = RealValue(rv); rv.IsValid() && rv.CanInterface() && rv.Kind() != reflect.Pointer {
|
||||
return Strings(rv.Interface())
|
||||
}
|
||||
}
|
||||
return []string{String(value)}
|
||||
}
|
||||
|
||||
func Duration(value any) time.Duration {
|
||||
if value == nil {
|
||||
return 0
|
||||
@ -353,27 +302,33 @@ func PrettyToJSONBytes(value any) []byte {
|
||||
|
||||
func PrettyToJSON(value any) string { return string(PrettyToJSONBytes(value)) }
|
||||
|
||||
func UnmarshalJSONBytes(data []byte, value any) (any, error) {
|
||||
if value == nil {
|
||||
var v any
|
||||
value = &v
|
||||
func UnmarshalJSON(data any, value any) error {
|
||||
var b []byte
|
||||
switch v := data.(type) {
|
||||
case string:
|
||||
b = []byte(v)
|
||||
case []byte:
|
||||
b = v
|
||||
default:
|
||||
return fmt.Errorf("unsupported data type: %T", data)
|
||||
}
|
||||
err := fastUnmarshalJSONBytes(data, value)
|
||||
return value, err
|
||||
return fastUnmarshalJSONBytes(b, value)
|
||||
}
|
||||
|
||||
func MustUnmarshalJSONBytes(data []byte, value any) any {
|
||||
v, err := UnmarshalJSONBytes(data, value)
|
||||
func FromJSON[T any](data any) (T, error) {
|
||||
var v T
|
||||
err := UnmarshalJSON(data, &v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func MustFromJSON[T any](data any) T {
|
||||
v, err := FromJSON[T](data)
|
||||
if err != nil {
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func UnmarshalJSON(str string, value any) (any, error) { return UnmarshalJSONBytes([]byte(str), value) }
|
||||
|
||||
func MustUnmarshalJSON(str string, value any) any { return MustUnmarshalJSONBytes([]byte(str), value) }
|
||||
|
||||
func ToYAML(value any) (string, error) {
|
||||
j, err := YAMLBytes(value)
|
||||
if err != nil {
|
||||
@ -384,11 +339,32 @@ func ToYAML(value any) (string, error) {
|
||||
|
||||
func MustToYAML(value any) string { return string(MustYAMLBytes(value)) }
|
||||
|
||||
func UnmarshalYAML(data string, value any) (any, error) {
|
||||
return UnmarshalYAMLBytes([]byte(data), value)
|
||||
func UnmarshalYAML(data any, value any) error {
|
||||
var b []byte
|
||||
switch v := data.(type) {
|
||||
case string:
|
||||
b = []byte(v)
|
||||
case []byte:
|
||||
b = v
|
||||
default:
|
||||
return fmt.Errorf("unsupported data type: %T", data)
|
||||
}
|
||||
return yaml.Unmarshal(b, value)
|
||||
}
|
||||
|
||||
func MustUnmarshalYAML(data string, value any) any { return MustUnmarshalYAMLBytes([]byte(data), value) }
|
||||
func FromYAML[T any](data any) (T, error) {
|
||||
var v T
|
||||
err := UnmarshalYAML(data, &v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func MustFromYAML[T any](data any) T {
|
||||
v, err := FromYAML[T](data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func YAMLBytes(value any) ([]byte, error) {
|
||||
j, err := yaml.Marshal(value)
|
||||
@ -406,19 +382,6 @@ func MustYAMLBytes(value any) []byte {
|
||||
return j
|
||||
}
|
||||
|
||||
func UnmarshalYAMLBytes(data []byte, value any) (any, error) {
|
||||
err := yaml.Unmarshal(data, value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func MustUnmarshalYAMLBytes(data []byte, value any) any {
|
||||
v, err := UnmarshalYAMLBytes(data, value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// --- Others (Keep logic but clean style) ---
|
||||
|
||||
func Split(s, sep string) []string {
|
||||
@ -516,12 +479,164 @@ func ArrayToBoolMap[T comparable](arr []T) map[T]bool {
|
||||
return r
|
||||
}
|
||||
|
||||
// ToMap 将 source 填充到目标 map 中,自动转换类型。
|
||||
// 如果 source 是 struct,则提取导出字段(含嵌入字段,字段名首字母转小写);
|
||||
// 如果 source 是 slice/array,则视为 [K1, V1, K2, V2, ...] 序列;
|
||||
// 如果 source 是 map,则进行合并(Merge)。
|
||||
func ToMap(target any, source any) {
|
||||
rv := reflect.ValueOf(target)
|
||||
for rv.Kind() == reflect.Pointer {
|
||||
if rv.IsNil() {
|
||||
if !rv.CanSet() {
|
||||
return
|
||||
}
|
||||
elemType := rv.Type().Elem()
|
||||
if elemType.Kind() == reflect.Map || elemType.Kind() == reflect.Pointer || elemType.Kind() == reflect.Struct {
|
||||
rv.Set(reflect.New(elemType))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
if rv.Kind() != reflect.Map {
|
||||
return
|
||||
}
|
||||
|
||||
if rv.IsNil() {
|
||||
if rv.CanSet() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
kt := rv.Type().Key()
|
||||
vt := rv.Type().Elem()
|
||||
|
||||
sv := RealValue(reflect.ValueOf(source))
|
||||
switch sv.Kind() {
|
||||
case reflect.Struct:
|
||||
fillMapFromStruct(rv, sv, kt, vt)
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < sv.Len(); i += 2 {
|
||||
k := sv.Index(i).Interface()
|
||||
var v any
|
||||
if i+1 < sv.Len() {
|
||||
v = sv.Index(i+1).Interface()
|
||||
}
|
||||
rv.SetMapIndex(reflectCast(k, kt), reflectCast(v, vt))
|
||||
}
|
||||
case reflect.Map:
|
||||
iter := sv.MapRange()
|
||||
for iter.Next() {
|
||||
rv.SetMapIndex(reflectCast(iter.Key().Interface(), kt), reflectCast(iter.Value().Interface(), vt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fillMapFromStruct(targetMap, sv reflect.Value, kt, vt reflect.Type) {
|
||||
st := sv.Type()
|
||||
for i := 0; i < sv.NumField(); i++ {
|
||||
field := st.Field(i)
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
if field.Anonymous {
|
||||
fillMapFromStruct(targetMap, sv.Field(i), kt, vt)
|
||||
continue
|
||||
}
|
||||
targetMap.SetMapIndex(reflectCast(GetLowerName(field.Name), kt), reflectCast(sv.Field(i).Interface(), vt))
|
||||
}
|
||||
}
|
||||
|
||||
// ToSlice 将 source 填充到目标 slice 中,自动转换类型。
|
||||
// 如果 source 是 map,则展开为 [K1, V1, K2, V2, ...] 序列;
|
||||
// 如果 source 是 slice/array,则进行元素拷贝/追加;
|
||||
// 如果 source 是普通值,则直接作为元素追加。
|
||||
func ToSlice(target any, source any) {
|
||||
rv := reflect.ValueOf(target)
|
||||
if rv.Kind() != reflect.Pointer || rv.Elem().Kind() != reflect.Slice {
|
||||
return
|
||||
}
|
||||
sliceRv := rv.Elem()
|
||||
et := sliceRv.Type().Elem()
|
||||
|
||||
sv := RealValue(reflect.ValueOf(source))
|
||||
switch sv.Kind() {
|
||||
case reflect.Map:
|
||||
iter := sv.MapRange()
|
||||
for iter.Next() {
|
||||
sliceRv.Set(reflect.Append(sliceRv, reflectCast(iter.Key().Interface(), et)))
|
||||
sliceRv.Set(reflect.Append(sliceRv, reflectCast(iter.Value().Interface(), et)))
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
sliceRv.Set(reflect.Append(sliceRv, reflectCast(sv.Index(i).Interface(), et)))
|
||||
}
|
||||
case reflect.Invalid:
|
||||
// Nil source, do nothing
|
||||
default:
|
||||
sliceRv.Set(reflect.Append(sliceRv, reflectCast(source, et)))
|
||||
}
|
||||
}
|
||||
|
||||
// MakeMap 泛型构建新 Map
|
||||
func MakeMap[K comparable, V any](source any) map[K]V {
|
||||
m := make(map[K]V)
|
||||
ToMap(m, source)
|
||||
return m
|
||||
}
|
||||
|
||||
// MakeSlice 泛型构建新 Slice
|
||||
func MakeSlice[T any](source any) []T {
|
||||
var s []T
|
||||
ToSlice(&s, source)
|
||||
return s
|
||||
}
|
||||
|
||||
func reflectCast(value any, t reflect.Type) reflect.Value {
|
||||
switch t.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return reflect.ValueOf(Int64(value)).Convert(t)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return reflect.ValueOf(Uint64(value)).Convert(t)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return reflect.ValueOf(Float64(value)).Convert(t)
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(String(value)).Convert(t)
|
||||
case reflect.Bool:
|
||||
return reflect.ValueOf(Bool(value)).Convert(t)
|
||||
case reflect.Interface:
|
||||
if value == nil {
|
||||
return reflect.Zero(t)
|
||||
}
|
||||
return reflect.ValueOf(value)
|
||||
}
|
||||
|
||||
if t == reflect.TypeOf(time.Duration(0)) {
|
||||
return reflect.ValueOf(Duration(value))
|
||||
}
|
||||
|
||||
return reflect.Zero(t)
|
||||
}
|
||||
|
||||
// 补充缺失的 Key 转换工具
|
||||
func GetLowerName(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
if len(s) > 0 && s[0] >= 'A' && s[0] <= 'Z' {
|
||||
hasLower := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= 'a' && s[i] <= 'z' {
|
||||
hasLower = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasLower {
|
||||
return strings.ToLower(s[:1]) + s[1:]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func GetUpperName(s string) string {
|
||||
|
||||
91
conversion_test.go
Normal file
91
conversion_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
package cast_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"apigo.cc/go/cast"
|
||||
)
|
||||
|
||||
func TestToMap(t *testing.T) {
|
||||
// Test struct-to-map with inheritance
|
||||
type Base struct {
|
||||
ID int
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Name string
|
||||
}
|
||||
u := User{Base: Base{ID: 1}, Name: "Tom"}
|
||||
m1 := make(map[string]any)
|
||||
cast.ToMap(m1, u)
|
||||
if m1["ID"] != 1 || m1["name"] != "Tom" {
|
||||
t.Errorf("Struct inheritance to map failed: %v", m1)
|
||||
}
|
||||
|
||||
// Test slice-to-map (KV)
|
||||
m2 := cast.MakeMap[int, string]([]any{"1", "a", 2, 200})
|
||||
if m2[1] != "a" || m2[2] != "200" {
|
||||
t.Errorf("Slice-to-map failed: %v", m2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSlice(t *testing.T) {
|
||||
// Test primitive-to-slice (append)
|
||||
var s1 []int
|
||||
cast.ToSlice(&s1, "123")
|
||||
if len(s1) != 1 || s1[0] != 123 {
|
||||
t.Errorf("Primitive-to-slice failed: %v", s1)
|
||||
}
|
||||
|
||||
// Test append slice
|
||||
cast.ToSlice(&s1, []string{"456"})
|
||||
if len(s1) != 2 || s1[1] != 456 {
|
||||
t.Errorf("Append slice to slice failed: %v", s1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONYAML(t *testing.T) {
|
||||
type Config struct {
|
||||
Port int `json:"port" yaml:"port"`
|
||||
}
|
||||
data := `{"port": 8080}`
|
||||
|
||||
// Test UnmarshalJSON
|
||||
var c1 Config
|
||||
err := cast.UnmarshalJSON(data, &c1)
|
||||
if err != nil || c1.Port != 8080 {
|
||||
t.Errorf("UnmarshalJSON failed: %v, %v", err, c1)
|
||||
}
|
||||
|
||||
// Test FromJSON
|
||||
c2, err := cast.FromJSON[Config]([]byte(data))
|
||||
if err != nil || c2.Port != 8080 {
|
||||
t.Errorf("FromJSON failed: %v, %v", err, c2)
|
||||
}
|
||||
|
||||
// Test MustFromJSON
|
||||
c3 := cast.MustFromJSON[Config](data)
|
||||
if c3.Port != 8080 {
|
||||
t.Errorf("MustFromJSON failed: %v", c3)
|
||||
}
|
||||
|
||||
// YAML
|
||||
yData := "port: 9090"
|
||||
c4 := cast.MustFromYAML[Config](yData)
|
||||
if c4.Port != 9090 {
|
||||
t.Errorf("MustFromYAML failed: %v", c4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeSlice(t *testing.T) {
|
||||
// Test MakeSlice with various inputs
|
||||
s1 := cast.MakeSlice[int]("123")
|
||||
if len(s1) != 1 || s1[0] != 123 {
|
||||
t.Errorf("MakeSlice primitive failed: %v", s1)
|
||||
}
|
||||
|
||||
s2 := cast.MakeSlice[string]([]int{1, 2})
|
||||
if len(s2) != 2 || s2[0] != "1" || s2[1] != "2" {
|
||||
t.Errorf("MakeSlice slice failed: %v", s2)
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@ func TestFastUnmarshal_Frictionless(t *testing.T) {
|
||||
// 测试各种 Key 格式的匹配
|
||||
data := `{"user_id": 1001, "UserName": "Tom", "isadmin": "true"}`
|
||||
var u User
|
||||
_, err := cast.UnmarshalJSON(data, &u)
|
||||
err := cast.UnmarshalJSON(data, &u)
|
||||
if err != nil {
|
||||
t.Fatalf("UnmarshalJSON failed: %v", err)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user