Skip to content

Commit 5a62cf4

Browse files
committed
UnmarshalJSON and NewFromString performance improvements
This PR improves `UnmarshalJSON` performance by reducing unnecessary allocation caused by `unquoteIfQuoted` function. This also touches on `Scan` method to split `default` case into `string` and `[]byte` cases. This PR also slightly touches `NewFromString` function by making so scientific notation and dots are checked in a single loop.
1 parent a1bdfc3 commit 5a62cf4

2 files changed

Lines changed: 48 additions & 43 deletions

File tree

decimal.go

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,23 @@ func NewFromString(value string) (Decimal, error) {
182182
var intString string
183183
var exp int64
184184

185-
// Check if number is using scientific notation
186-
eIndex := strings.IndexAny(value, "Ee")
185+
// Check if number is using scientific notation and find dots
186+
eIndex := -1
187+
pIndex := -1
188+
for i, r := range value {
189+
if eIndex == -1 && (r == 'E' || r == 'e') {
190+
eIndex = i
191+
continue
192+
}
193+
194+
if r == '.' {
195+
if pIndex > -1 {
196+
return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value)
197+
}
198+
pIndex = i
199+
}
200+
}
201+
187202
if eIndex != -1 {
188203
expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32)
189204
if err != nil {
@@ -196,23 +211,12 @@ func NewFromString(value string) (Decimal, error) {
196211
exp = expInt
197212
}
198213

199-
pIndex := -1
200-
vLen := len(value)
201-
for i := 0; i < vLen; i++ {
202-
if value[i] == '.' {
203-
if pIndex > -1 {
204-
return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value)
205-
}
206-
pIndex = i
207-
}
208-
}
209-
210214
if pIndex == -1 {
211215
// There is no decimal point, we can just parse the original string as
212216
// an int
213217
intString = value
214218
} else {
215-
if pIndex+1 < vLen {
219+
if pIndex+1 < len(value) {
216220
intString = value[:pIndex] + value[pIndex+1:]
217221
} else {
218222
intString = value[:pIndex]
@@ -1766,15 +1770,10 @@ func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
17661770
return nil
17671771
}
17681772

1769-
str, err := unquoteIfQuoted(decimalBytes)
1770-
if err != nil {
1771-
return fmt.Errorf("error decoding string '%s': %s", decimalBytes, err)
1772-
}
1773-
1774-
decimal, err := NewFromString(str)
1773+
decimal, err := NewFromString(unquoteIfQuoted(string(decimalBytes)))
17751774
*d = decimal
17761775
if err != nil {
1777-
return fmt.Errorf("error decoding string '%s': %s", str, err)
1776+
return fmt.Errorf("error decoding string '%s': %s", string(decimalBytes), err)
17781777
}
17791778
return nil
17801779
}
@@ -1852,14 +1851,18 @@ func (d *Decimal) Scan(value interface{}) error {
18521851
*d = NewFromUint64(v)
18531852
return nil
18541853

1855-
default:
1856-
// default is trying to interpret value stored as string
1857-
str, err := unquoteIfQuoted(v)
1858-
if err != nil {
1859-
return err
1860-
}
1861-
*d, err = NewFromString(str)
1854+
case string:
1855+
var err error
1856+
*d, err = NewFromString(unquoteIfQuoted(v))
18621857
return err
1858+
1859+
case []byte:
1860+
var err error
1861+
*d, err = NewFromString(unquoteIfQuoted(string(v)))
1862+
return err
1863+
1864+
default:
1865+
return fmt.Errorf("could not convert value '%+v' to any known type", value)
18631866
}
18641867
}
18651868

@@ -2021,23 +2024,13 @@ func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) {
20212024
return d1, d2
20222025
}
20232026

2024-
func unquoteIfQuoted(value interface{}) (string, error) {
2025-
var bytes []byte
2026-
2027-
switch v := value.(type) {
2028-
case string:
2029-
bytes = []byte(v)
2030-
case []byte:
2031-
bytes = v
2032-
default:
2033-
return "", fmt.Errorf("could not convert value '%+v' to byte array of type '%T'", value, value)
2034-
}
2035-
2027+
func unquoteIfQuoted(value string) string {
20362028
// If the amount is quoted, strip the quotes
2037-
if len(bytes) > 2 && bytes[0] == '"' && bytes[len(bytes)-1] == '"' {
2038-
bytes = bytes[1 : len(bytes)-1]
2029+
if len(value) > 2 && value[0] == '"' && value[len(value)-1] == '"' {
2030+
return value[1 : len(value)-1]
20392031
}
2040-
return string(bytes), nil
2032+
2033+
return value
20412034
}
20422035

20432036
// NullDecimal represents a nullable decimal with compatibility for

decimal_bench_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,15 @@ func BenchmarkDecimal_ExpTaylor(b *testing.B) {
312312
_, _ = d.ExpTaylor(10)
313313
}
314314
}
315+
316+
func BenchmarkDecimal_UnmarshalJSON(b *testing.B) {
317+
b.ResetTimer()
318+
319+
bstr := []byte("1234.56789")
320+
321+
b.ReportAllocs()
322+
b.ResetTimer()
323+
for i := 0; i < b.N; i++ {
324+
_ = (&Decimal{}).UnmarshalJSON(bstr)
325+
}
326+
}

0 commit comments

Comments
 (0)