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)
case srcKind == reflect.Slice: // slice to slice
return ConvertSlice(src, dst, checkValidateTag)
}
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
}
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 dstT.Kind() != reflect.Slice {
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String())
if dst.Kind() != reflect.Slice {
return ErrUnsupportedConversion.Subjectf("%s to %s", dst.Type(), src.Type())
}
var sliceErrs gperr.Builder
i := 0
numValid := 0
gi.ReflectInitSlice(dst, srcLen, srcLen)
for j, v := range src.Seq2() {
err := Convert(v, dst.Index(i), checkValidateTag)
for j := range srcLen {
err := Convert(src.Index(j), dst.Index(numValid), checkValidateTag)
if err != nil {
sliceErrs.Add(err.Subjectf("[%d]", j))
continue
}
i++
numValid++
}
if err := sliceErrs.Error(); err != nil {
dst.SetLen(i) // shrink to number of elements that were successfully converted
dst.SetLen(numValid) // shrink to number of elements that were successfully converted
return err
}
return nil
}
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT.String(), dstT.String())
}
var parserType = reflect.TypeFor[strutils.Parser]()
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) {
convertible = true
dstT := dst.Type()
if dst.Kind() == reflect.Pointer {
if dst.IsNil() {
// Early return for empty string
if src == "" {
return true, nil
}
initPtr(dst)
}
dst = dst.Elem()
dstT = dst.Type()
}
if dst.Kind() == reflect.String {
dst.SetString(src)
return true, nil
}
// Early return for empty string
if src == "" {
@@ -501,6 +508,17 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
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 {
case reflect.TypeFor[time.Duration]():
d, err := time.ParseDuration(src)
@@ -512,12 +530,6 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
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 {
err := gi.ReflectStrToNumBool(dst, src)
if err != nil {
@@ -527,29 +539,25 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
}
// yaml like
var tmp any
switch dst.Kind() {
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
isMultiline := strings.ContainsRune(src, '\n')
if !isMultiline && src[0] != '-' {
isMultiline := strings.IndexByte(src, '\n') != -1
if !isMultiline && src[0] != '-' && src[0] != '[' {
values := strutils.CommaSeperatedList(src)
gi.ReflectInitSlice(dst, len(values), len(values))
size := len(values)
gi.ReflectInitSlice(dst, size, size)
var errs gperr.Builder
for i, v := range values {
_, err := ConvertString(v, dst.Index(i))
if err != nil {
errs.Add(err.Subjectf("[%d]", i))
errs.AddSubjectf(err, "[%d]", i)
}
}
err := errs.Error()
return true, err
if errs.HasError() {
return true, errs.Error()
}
return true, nil
}
sl := []any{}
@@ -557,18 +565,17 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
if err != nil {
return true, gperr.Wrap(err)
}
tmp = sl
return true, ConvertSlice(reflect.ValueOf(sl), dst, true)
case reflect.Map, reflect.Struct:
rawMap := SerializedObject{}
err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &rawMap)
if err != nil {
return true, gperr.Wrap(err)
}
tmp = rawMap
return true, mapUnmarshalValidate(rawMap, dst, true)
default:
return false, nil
}
return true, Convert(reflect.ValueOf(tmp), dst, true)
}
var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY}

View File

@@ -32,9 +32,9 @@ func BenchmarkDeserialize(b *testing.B) {
"c": "1,2,3",
"d": "a: a\nb: b\nc: 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},
"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",
"k": 123,
}