refactor(serialization): optimize deserialization

This commit is contained in:
yusing
2026-01-10 15:43:34 +08:00
parent cc1fe30045
commit 71f6636cc3
2 changed files with 61 additions and 54 deletions

View File

@@ -449,51 +449,58 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.
} }
return mapUnmarshalValidate(obj, dst.Addr(), checkValidateTag) return mapUnmarshalValidate(obj, dst.Addr(), checkValidateTag)
case srcKind == reflect.Slice: // slice to slice case srcKind == reflect.Slice: // slice to slice
srcLen := src.Len() return ConvertSlice(src, dst, checkValidateTag)
if srcLen == 0 {
dst.SetZero()
return nil
}
if dstT.Kind() != reflect.Slice {
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String())
}
var sliceErrs gperr.Builder
i := 0
gi.ReflectInitSlice(dst, srcLen, srcLen)
for j, v := range src.Seq2() {
err := Convert(v, dst.Index(i), checkValidateTag)
if err != nil {
sliceErrs.Add(err.Subjectf("[%d]", j))
continue
}
i++
}
if err := sliceErrs.Error(); err != nil {
dst.SetLen(i) // shrink to number of elements that were successfully converted
return err
}
return nil
} }
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT.String(), dstT.String()) return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
} }
var parserType = reflect.TypeFor[strutils.Parser]() func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error {
if src.Kind() != reflect.Slice {
return Convert(src, dst, checkValidateTag)
}
srcLen := src.Len()
if srcLen == 0 {
dst.SetZero()
return nil
}
if dst.Kind() != reflect.Slice {
return ErrUnsupportedConversion.Subjectf("%s to %s", dst.Type(), src.Type())
}
var sliceErrs gperr.Builder
numValid := 0
gi.ReflectInitSlice(dst, srcLen, srcLen)
for j := range srcLen {
err := Convert(src.Index(j), dst.Index(numValid), checkValidateTag)
if err != nil {
sliceErrs.Add(err.Subjectf("[%d]", j))
continue
}
numValid++
}
if err := sliceErrs.Error(); err != nil {
dst.SetLen(numValid) // shrink to number of elements that were successfully converted
return err
}
return nil
}
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) { func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) {
convertible = true convertible = true
dstT := dst.Type() dstT := dst.Type()
if dst.Kind() == reflect.Pointer { if dst.Kind() == reflect.Pointer {
if dst.IsNil() { if dst.IsNil() {
// Early return for empty string
if src == "" {
return true, nil
}
initPtr(dst) initPtr(dst)
} }
dst = dst.Elem() dst = dst.Elem()
dstT = dst.Type() dstT = dst.Type()
} }
if dst.Kind() == reflect.String {
dst.SetString(src)
return true, nil
}
// Early return for empty string // Early return for empty string
if src == "" { if src == "" {
@@ -501,6 +508,17 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
return true, nil return true, nil
} }
if dst.Kind() == reflect.String {
dst.SetString(src)
return true, nil
}
// check if (*T).Convertor is implemented
if addr := dst.Addr(); addr.Type().Implements(reflect.TypeFor[strutils.Parser]()) {
parser := addr.Interface().(strutils.Parser)
return true, gperr.Wrap(parser.Parse(src))
}
switch dstT { switch dstT {
case reflect.TypeFor[time.Duration](): case reflect.TypeFor[time.Duration]():
d, err := time.ParseDuration(src) d, err := time.ParseDuration(src)
@@ -512,12 +530,6 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
default: default:
} }
// check if (*T).Convertor is implemented
if dst.Addr().Type().Implements(parserType) {
parser := dst.Addr().Interface().(strutils.Parser)
return true, gperr.Wrap(parser.Parse(src))
}
if gi.ReflectIsNumeric(dst) || dst.Kind() == reflect.Bool { if gi.ReflectIsNumeric(dst) || dst.Kind() == reflect.Bool {
err := gi.ReflectStrToNumBool(dst, src) err := gi.ReflectStrToNumBool(dst, src)
if err != nil { if err != nil {
@@ -527,29 +539,25 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
} }
// yaml like // yaml like
var tmp any
switch dst.Kind() { switch dst.Kind() {
case reflect.Slice: case reflect.Slice:
// Avoid unnecessary TrimSpace if we can detect the format early
srcLen := len(src)
if srcLen == 0 {
return true, nil
}
// one liner is comma separated list // one liner is comma separated list
isMultiline := strings.ContainsRune(src, '\n') isMultiline := strings.IndexByte(src, '\n') != -1
if !isMultiline && src[0] != '-' { if !isMultiline && src[0] != '-' && src[0] != '[' {
values := strutils.CommaSeperatedList(src) values := strutils.CommaSeperatedList(src)
gi.ReflectInitSlice(dst, len(values), len(values)) size := len(values)
gi.ReflectInitSlice(dst, size, size)
var errs gperr.Builder var errs gperr.Builder
for i, v := range values { for i, v := range values {
_, err := ConvertString(v, dst.Index(i)) _, err := ConvertString(v, dst.Index(i))
if err != nil { if err != nil {
errs.Add(err.Subjectf("[%d]", i)) errs.AddSubjectf(err, "[%d]", i)
} }
} }
err := errs.Error() if errs.HasError() {
return true, err return true, errs.Error()
}
return true, nil
} }
sl := []any{} sl := []any{}
@@ -557,18 +565,17 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
if err != nil { if err != nil {
return true, gperr.Wrap(err) return true, gperr.Wrap(err)
} }
tmp = sl return true, ConvertSlice(reflect.ValueOf(sl), dst, true)
case reflect.Map, reflect.Struct: case reflect.Map, reflect.Struct:
rawMap := SerializedObject{} rawMap := SerializedObject{}
err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &rawMap) err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &rawMap)
if err != nil { if err != nil {
return true, gperr.Wrap(err) return true, gperr.Wrap(err)
} }
tmp = rawMap return true, mapUnmarshalValidate(rawMap, dst, true)
default: default:
return false, nil return false, nil
} }
return true, Convert(reflect.ValueOf(tmp), dst, true)
} }
var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY} var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY}

View File

@@ -32,9 +32,9 @@ func BenchmarkDeserialize(b *testing.B) {
"c": "1,2,3", "c": "1,2,3",
"d": "a: a\nb: b\nc: c", "d": "a: a\nb: b\nc: c",
"e": "- a: a\n b: b\n c: c", "e": "- a: a\n b: b\n c: c",
"f": map[string]any{"a": "a", "b": "456", "c": []string{"1", "2", "3"}}, "f": map[string]any{"a": "a", "b": "456", "c": `1,2,3`},
"g": map[string]any{"g1": "1.23", "g2": 123}, "g": map[string]any{"g1": "1.23", "g2": 123},
"h": []map[string]any{{"a": 123, "b": "456", "c": []string{"1", "2", "3"}}}, "h": []map[string]any{{"a": 123, "b": "456", "c": `["1","2","3"]`}},
"j": "1.23", "j": "1.23",
"k": 123, "k": 123,
} }