Support additional unmarshal formats.

- Additional time formats supported
- Support for hex format binary string unmarshal
- Support for base64 format with prefix
- Support for unmarshalling "0" and "1" to boolean value
This commit is contained in:
go-jet 2025-02-22 18:28:25 +01:00
parent c93c9f2888
commit f20bf50879
2 changed files with 70 additions and 11 deletions

View file

@ -14,7 +14,6 @@ import (
"bytes"
"compress/gzip"
"fmt"
"internal/testenv"
"io"
"os"
"reflect"
@ -462,9 +461,9 @@ func BenchmarkUnmapped(b *testing.B) {
func BenchmarkTypeFieldsCache(b *testing.B) {
b.ReportAllocs()
var maxTypes int = 1e6
if testenv.Builder() != "" {
maxTypes = 1e3 // restrict cache sizes on builders
}
//if testenv.Builder() != "" {
// maxTypes = 1e3 // restrict cache sizes on builders
//}
// Dynamically generate many new types.
types := make([]reflect.Type, maxTypes)

View file

@ -8,12 +8,16 @@
package json
import (
"bytes"
"encoding"
"encoding/base64"
hex2 "encoding/hex"
"fmt"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"reflect"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf16"
"unicode/utf8"
@ -851,7 +855,37 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
isNull := item[0] == 'n' // null
u, ut, pv := indirect(v, isNull)
if u != nil {
return u.UnmarshalJSON(item)
err := u.UnmarshalJSON(item)
if err != nil {
if t, ok := u.(*time.Time); ok {
if len(item) < 2 || item[0] != '"' || item[len(item)-1] != '"' {
d.saveError(fmt.Errorf("Time.UnmarshalJSON: input is not a JSON string"))
return nil
}
item = item[len(`"`) : len(item)-len(`"`)]
tt, parsed := datetime.TryParseAsTime(item, []string{
time.RFC3339Nano,
"2006-01-02 15:04:05.999999", // go-sql-driver/mysql
"15:04:05-07", // pgx
"15:04:05.999999", // pgx
})
if !parsed {
d.saveError(fmt.Errorf("json: invalid time, trying to unmarshal %q into %v", item, v.Type()))
return nil
}
*t = tt
return nil
}
return err
}
return nil
}
if ut != nil {
if item[0] != '"' {
@ -935,6 +969,21 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())})
break
}
if bytes.HasPrefix(s, []byte("\\x")) {
s = s[2:]
b := make([]byte, hex2.DecodedLen(len(s)))
n, err := hex2.Decode(b, s)
if err != nil {
d.saveError(err)
break
}
v.SetBytes(b[:n])
} else {
if bytes.HasPrefix(s, []byte("base64")) {
s = s[bytes.LastIndexByte(s, ':')+1:]
}
b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
n, err := base64.StdEncoding.Decode(b, s)
if err != nil {
@ -942,6 +991,8 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
break
}
v.SetBytes(b[:n])
}
case reflect.String:
t := string(s)
if v.Type() == numberType && !isValidNumber(t) {
@ -975,6 +1026,15 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
return fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())
}
d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())})
case reflect.Bool:
itemVal := string(item)
if itemVal != "1" && itemVal != "0" {
d.saveError(&UnmarshalTypeError{Value: "number", Type: v.Type(), Offset: int64(d.readIndex())})
break
}
v.SetBool(itemVal == "1")
case reflect.Interface:
n, err := d.convertNumber(string(item))
if err != nil {