Compare commits

...

12 Commits

Author SHA1 Message Date
9491b72b8d v0.0.424 timeext.SubtractYears
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m29s
2024-03-30 03:01:55 +01:00
6c4af4006b v0.0.423 fix createPaginationPipeline - different primary and secondary sort keys broke mongo ??!?? - it actually only sorted by the secondary condition (ignoring the primary?)
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m32s
2024-03-24 15:25:52 +01:00
8bf3a337cf v0.0.422
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m29s
2024-03-23 20:29:46 +01:00
16146494dc v0.0.421
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 32s
2024-03-23 20:28:51 +01:00
b0e443ad99 v0.0.420
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 36s
2024-03-23 18:01:41 +01:00
9955eacf96 v0.0.419 JsonOpt
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 39s
2024-03-23 17:49:56 +01:00
f0347a9435 v0.0.418 fix tests?
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m1s
2024-03-20 09:42:06 +01:00
7c869c65f3 v0.0.417 add GinWrapper.ForwardRequest
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 1m26s
2024-03-20 08:58:59 +01:00
14f39a9162 v0.0.416
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 1m43s
2024-03-18 11:19:01 +01:00
dcd106c1cd v0.0.415 add 'tagkey' to gojson.Decoder
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m45s
2024-03-18 10:42:00 +01:00
b704e2a362 v0.0.414 fix rfctime.Date bson marshalling for zero value
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m28s
2024-03-16 19:42:59 +01:00
6b4bd5a6f8 v0.0.413 fix tests
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m33s
2024-03-11 21:00:30 +01:00
13 changed files with 212 additions and 50 deletions

View File

@@ -34,10 +34,10 @@ func TestGenerateIDSpecs(t *testing.T) {
_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() _, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run()
tst.AssertNoErr(t, err) tst.AssertNoErr(t, err)
err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", &IDGenOptions{DebugOutput: langext.PTrue}) err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", IDGenOptions{DebugOutput: langext.PTrue})
tst.AssertNoErr(t, err) tst.AssertNoErr(t, err)
err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", &IDGenOptions{DebugOutput: langext.PTrue}) err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", IDGenOptions{DebugOutput: langext.PTrue})
tst.AssertNoErr(t, err) tst.AssertNoErr(t, err)
fmt.Println() fmt.Println()

58
dataext/optional.go Normal file
View File

@@ -0,0 +1,58 @@
package dataext
import (
"encoding/json"
"errors"
)
type JsonOpt[T any] struct {
isSet bool
value T
}
// MarshalJSON returns m as the JSON encoding of m.
func (m JsonOpt[T]) MarshalJSON() ([]byte, error) {
if !m.isSet {
return []byte("null"), nil // actually this would be undefined - but undefined is not valid JSON
}
return json.Marshal(m.value)
}
// UnmarshalJSON sets *m to a copy of data.
func (m *JsonOpt[T]) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("JsonOpt: UnmarshalJSON on nil pointer")
}
return json.Unmarshal(data, &m.value)
}
func (m JsonOpt[T]) IsSet() bool {
return m.isSet
}
func (m JsonOpt[T]) IsUnset() bool {
return !m.isSet
}
func (m JsonOpt[T]) Value() (T, bool) {
if !m.isSet {
return *new(T), false
}
return m.value, true
}
func (m JsonOpt[T]) ValueOrNil() *T {
if !m.isSet {
return nil
}
return &m.value
}
func (m JsonOpt[T]) MustValue() T {
if !m.isSet {
panic("value not set")
}
return m.value
}

View File

@@ -194,3 +194,9 @@ func (w *GinWrapper) ServeHTTP(req *http.Request) *httptest.ResponseRecorder {
w.engine.ServeHTTP(respRec, req) w.engine.ServeHTTP(respRec, req)
return respRec return respRec
} }
// ForwardRequest manually inserts a request into this router
// = behaves as if the request came from the outside (and writes the response to `writer`)
func (w *GinWrapper) ForwardRequest(writer http.ResponseWriter, req *http.Request) {
w.engine.ServeHTTP(writer, req)
}

6
go.mod
View File

@@ -1,6 +1,6 @@
module gogs.mikescher.com/BlackForestBytes/goext module gogs.mikescher.com/BlackForestBytes/goext
go 1.21 go 1.22
require ( require (
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
@@ -15,7 +15,7 @@ require (
) )
require ( require (
github.com/bytedance/sonic v1.11.2 // indirect github.com/bytedance/sonic v1.11.3 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
@@ -36,7 +36,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect

7
go.sum
View File

@@ -8,6 +8,8 @@ github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn
github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@@ -102,6 +104,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -116,6 +120,7 @@ github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -124,6 +129,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=

View File

@@ -1,5 +1,5 @@
package goext package goext
const GoextVersion = "0.0.412" const GoextVersion = "0.0.424"
const GoextVersionTimestamp = "2024-03-11T20:58:06+0100" const GoextVersionTimestamp = "2024-03-30T03:01:55+0100"

View File

@@ -217,6 +217,7 @@ type decodeState struct {
savedError error savedError error
useNumber bool useNumber bool
disallowUnknownFields bool disallowUnknownFields bool
tagkey *string
} }
// readIndex returns the position of the last byte read. // readIndex returns the position of the last byte read.
@@ -652,7 +653,11 @@ func (d *decodeState) object(v reflect.Value) error {
v.Set(reflect.MakeMap(t)) v.Set(reflect.MakeMap(t))
} }
case reflect.Struct: case reflect.Struct:
fields = cachedTypeFields(t) tagkey := "json"
if d.tagkey != nil {
tagkey = *d.tagkey
}
fields = cachedTypeFields(t, tagkey)
// ok // ok
default: default:
d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})

View File

@@ -382,7 +382,12 @@ func isEmptyValue(v reflect.Value) bool {
} }
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) { func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
valueEncoder(v)(e, v, opts) tagkey := "json"
if opts.tagkey != nil {
tagkey = *opts.tagkey
}
valueEncoder(v, tagkey)(e, v, opts)
} }
type encOpts struct { type encOpts struct {
@@ -397,20 +402,22 @@ type encOpts struct {
// filter matches jsonfilter tag of struct // filter matches jsonfilter tag of struct
// marshals if no jsonfilter is set or otherwise if jsonfilter has the filter value // marshals if no jsonfilter is set or otherwise if jsonfilter has the filter value
filter *string filter *string
// use different tag instead of "json"
tagkey *string
} }
type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts)
var encoderCache sync.Map // map[reflect.Type]encoderFunc var encoderCache sync.Map // map[reflect.Type]encoderFunc
func valueEncoder(v reflect.Value) encoderFunc { func valueEncoder(v reflect.Value, tagkey string) encoderFunc {
if !v.IsValid() { if !v.IsValid() {
return invalidValueEncoder return invalidValueEncoder
} }
return typeEncoder(v.Type()) return typeEncoder(v.Type(), tagkey)
} }
func typeEncoder(t reflect.Type) encoderFunc { func typeEncoder(t reflect.Type, tagkey string) encoderFunc {
if fi, ok := encoderCache.Load(t); ok { if fi, ok := encoderCache.Load(t); ok {
return fi.(encoderFunc) return fi.(encoderFunc)
} }
@@ -433,7 +440,7 @@ func typeEncoder(t reflect.Type) encoderFunc {
} }
// Compute the real encoder and replace the indirect func with it. // Compute the real encoder and replace the indirect func with it.
f = newTypeEncoder(t, true) f = newTypeEncoder(t, true, tagkey)
wg.Done() wg.Done()
encoderCache.Store(t, f) encoderCache.Store(t, f)
return f return f
@@ -446,19 +453,19 @@ var (
// newTypeEncoder constructs an encoderFunc for a type. // newTypeEncoder constructs an encoderFunc for a type.
// The returned encoder only checks CanAddr when allowAddr is true. // The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { func newTypeEncoder(t reflect.Type, allowAddr bool, tagkey string) encoderFunc {
// If we have a non-pointer value whose type implements // If we have a non-pointer value whose type implements
// Marshaler with a value receiver, then we're better off taking // Marshaler with a value receiver, then we're better off taking
// the address of the value - otherwise we end up with an // the address of the value - otherwise we end up with an
// allocation as we cast the value to an interface. // allocation as we cast the value to an interface.
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) { if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false, tagkey))
} }
if t.Implements(marshalerType) { if t.Implements(marshalerType) {
return marshalerEncoder return marshalerEncoder
} }
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) { if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false, tagkey))
} }
if t.Implements(textMarshalerType) { if t.Implements(textMarshalerType) {
return textMarshalerEncoder return textMarshalerEncoder
@@ -480,15 +487,15 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
case reflect.Interface: case reflect.Interface:
return interfaceEncoder return interfaceEncoder
case reflect.Struct: case reflect.Struct:
return newStructEncoder(t) return newStructEncoder(t, tagkey)
case reflect.Map: case reflect.Map:
return newMapEncoder(t) return newMapEncoder(t, tagkey)
case reflect.Slice: case reflect.Slice:
return newSliceEncoder(t) return newSliceEncoder(t, tagkey)
case reflect.Array: case reflect.Array:
return newArrayEncoder(t) return newArrayEncoder(t, tagkey)
case reflect.Pointer: case reflect.Pointer:
return newPtrEncoder(t) return newPtrEncoder(t, tagkey)
default: default:
return unsupportedTypeEncoder return unsupportedTypeEncoder
} }
@@ -801,8 +808,8 @@ FieldLoop:
} }
} }
func newStructEncoder(t reflect.Type) encoderFunc { func newStructEncoder(t reflect.Type, tagkey string) encoderFunc {
se := structEncoder{fields: cachedTypeFields(t)} se := structEncoder{fields: cachedTypeFields(t, tagkey)}
return se.encode return se.encode
} }
@@ -855,7 +862,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.ptrLevel-- e.ptrLevel--
} }
func newMapEncoder(t reflect.Type) encoderFunc { func newMapEncoder(t reflect.Type, tagkey string) encoderFunc {
switch t.Key().Kind() { switch t.Key().Kind() {
case reflect.String, case reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
@@ -865,7 +872,7 @@ func newMapEncoder(t reflect.Type) encoderFunc {
return unsupportedTypeEncoder return unsupportedTypeEncoder
} }
} }
me := mapEncoder{typeEncoder(t.Elem())} me := mapEncoder{typeEncoder(t.Elem(), tagkey)}
return me.encode return me.encode
} }
@@ -936,7 +943,7 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.ptrLevel-- e.ptrLevel--
} }
func newSliceEncoder(t reflect.Type) encoderFunc { func newSliceEncoder(t reflect.Type, tagkey string) encoderFunc {
// Byte slices get special treatment; arrays don't. // Byte slices get special treatment; arrays don't.
if t.Elem().Kind() == reflect.Uint8 { if t.Elem().Kind() == reflect.Uint8 {
p := reflect.PointerTo(t.Elem()) p := reflect.PointerTo(t.Elem())
@@ -944,7 +951,7 @@ func newSliceEncoder(t reflect.Type) encoderFunc {
return encodeByteSlice return encodeByteSlice
} }
} }
enc := sliceEncoder{newArrayEncoder(t)} enc := sliceEncoder{newArrayEncoder(t, tagkey)}
return enc.encode return enc.encode
} }
@@ -964,8 +971,8 @@ func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.WriteByte(']') e.WriteByte(']')
} }
func newArrayEncoder(t reflect.Type) encoderFunc { func newArrayEncoder(t reflect.Type, tagkey string) encoderFunc {
enc := arrayEncoder{typeEncoder(t.Elem())} enc := arrayEncoder{typeEncoder(t.Elem(), tagkey)}
return enc.encode return enc.encode
} }
@@ -992,8 +999,8 @@ func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
e.ptrLevel-- e.ptrLevel--
} }
func newPtrEncoder(t reflect.Type) encoderFunc { func newPtrEncoder(t reflect.Type, tagkey string) encoderFunc {
enc := ptrEncoder{typeEncoder(t.Elem())} enc := ptrEncoder{typeEncoder(t.Elem(), tagkey)}
return enc.encode return enc.encode
} }
@@ -1270,7 +1277,7 @@ func (x byIndex) Less(i, j int) bool {
// typeFields returns a list of fields that JSON should recognize for the given type. // typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct // The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs. // and then any reachable anonymous structs.
func typeFields(t reflect.Type) structFields { func typeFields(t reflect.Type, tagkey string) structFields {
// Anonymous fields to explore at the current level and the next. // Anonymous fields to explore at the current level and the next.
current := []field{} current := []field{}
next := []field{{typ: t}} next := []field{{typ: t}}
@@ -1315,7 +1322,7 @@ func typeFields(t reflect.Type) structFields {
// Ignore unexported non-embedded fields. // Ignore unexported non-embedded fields.
continue continue
} }
tag := sf.Tag.Get("json") tag := sf.Tag.Get(tagkey)
if tag == "-" { if tag == "-" {
continue continue
} }
@@ -1449,7 +1456,7 @@ func typeFields(t reflect.Type) structFields {
for i := range fields { for i := range fields {
f := &fields[i] f := &fields[i]
f.encoder = typeEncoder(typeByIndex(t, f.index)) f.encoder = typeEncoder(typeByIndex(t, f.index), tagkey)
} }
nameIndex := make(map[string]int, len(fields)) nameIndex := make(map[string]int, len(fields))
for i, field := range fields { for i, field := range fields {
@@ -1474,13 +1481,26 @@ func dominantField(fields []field) (field, bool) {
return fields[0], true return fields[0], true
} }
var fieldCache sync.Map // map[reflect.Type]structFields var fieldCache sync.Map // map[string]map[reflect.Type]structFields
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. // cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
func cachedTypeFields(t reflect.Type) structFields { func cachedTypeFields(t reflect.Type, tagkey string) structFields {
if f, ok := fieldCache.Load(t); ok { if m0, ok := fieldCache.Load(tagkey); ok {
if f, ok := m0.(*sync.Map).Load(t); ok {
return f.(structFields)
}
f, _ := m0.(*sync.Map).LoadOrStore(t, typeFields(t, tagkey))
return f.(structFields)
} else {
m0 := &sync.Map{}
f, _ := m0.LoadOrStore(t, typeFields(t, tagkey))
fieldCache.Store(tagkey, m0)
return f.(structFields) return f.(structFields)
} }
f, _ := fieldCache.LoadOrStore(t, typeFields(t))
return f.(structFields)
} }

View File

@@ -41,6 +41,9 @@ func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
// non-ignored, exported fields in the destination. // non-ignored, exported fields in the destination.
func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true } func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true }
// TagKey sets a different TagKey (instead of "json")
func (dec *Decoder) TagKey(v string) { dec.d.tagkey = &v }
// Decode reads the next JSON-encoded value from its // Decode reads the next JSON-encoded value from its
// input and stores it in the value pointed to by v. // input and stores it in the value pointed to by v.
// //

View File

@@ -1,19 +1,30 @@
package reflectext package reflectext
import "reflect" import (
"encoding/json"
"reflect"
)
func ConvertStructToMap(v any) any { type ConvertStructToMapOpt struct {
return reflectToMap(reflect.ValueOf(v)) KeepJsonMarshalTypes bool
} }
func reflectToMap(fv reflect.Value) any { func ConvertStructToMap(v any, opts ...ConvertStructToMapOpt) any {
opt := ConvertStructToMapOpt{}
if len(opts) > 0 {
opt = opts[0]
}
return reflectToMap(reflect.ValueOf(v), opt)
}
func reflectToMap(fv reflect.Value, opt ConvertStructToMapOpt) any {
if fv.Kind() == reflect.Ptr { if fv.Kind() == reflect.Ptr {
if fv.IsNil() { if fv.IsNil() {
return nil return nil
} else { } else {
return reflectToMap(fv.Elem()) return reflectToMap(fv.Elem(), opt)
} }
} }
@@ -30,7 +41,7 @@ func reflectToMap(fv reflect.Value) any {
arrlen := fv.Len() arrlen := fv.Len()
arr := make([]any, arrlen) arr := make([]any, arrlen)
for i := 0; i < arrlen; i++ { for i := 0; i < arrlen; i++ {
arr[i] = reflectToMap(fv.Index(i)) arr[i] = reflectToMap(fv.Index(i), opt)
} }
return arr return arr
@@ -41,7 +52,7 @@ func reflectToMap(fv reflect.Value) any {
arrlen := fv.Len() arrlen := fv.Len()
arr := make([]any, arrlen) arr := make([]any, arrlen)
for i := 0; i < arrlen; i++ { for i := 0; i < arrlen; i++ {
arr[i] = reflectToMap(fv.Index(i)) arr[i] = reflectToMap(fv.Index(i), opt)
} }
return arr return arr
@@ -56,11 +67,15 @@ func reflectToMap(fv reflect.Value) any {
if fv.Kind() == reflect.Struct { if fv.Kind() == reflect.Struct {
if opt.KeepJsonMarshalTypes && fv.Type().Implements(reflect.TypeFor[json.Marshaler]()) {
return fv.Interface()
}
res := make(map[string]any) res := make(map[string]any)
for i := 0; i < fv.NumField(); i++ { for i := 0; i < fv.NumField(); i++ {
if fv.Type().Field(i).IsExported() { if fv.Type().Field(i).IsExported() {
res[fv.Type().Field(i).Name] = reflectToMap(fv.Field(i)) res[fv.Type().Field(i).Name] = reflectToMap(fv.Field(i), opt)
} }
} }

View File

@@ -102,6 +102,13 @@ func (t *Date) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
return err return err
} }
if tt == "" {
t.Year = 0
t.Month = 0
t.Day = 0
return nil
}
v, err := time.Parse(t.FormatStr(), tt) v, err := time.Parse(t.FormatStr(), tt)
if err != nil { if err != nil {
return err return err
@@ -114,7 +121,10 @@ func (t *Date) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
} }
func (t Date) MarshalBSONValue() (bsontype.Type, []byte, error) { func (t Date) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(t.TimeUTC().Format(t.FormatStr())) if t.IsZero() {
return bson.MarshalValue("")
}
return bson.MarshalValue(t.String())
} }
func (t Date) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error { func (t Date) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
@@ -242,6 +252,10 @@ func (t *Date) ParseString(v string) error {
return nil return nil
} }
func (t Date) IsZero() bool {
return t.Year == 0 && t.Month == 0 && t.Day == 0
}
func NewDate(t time.Time) Date { func NewDate(t time.Time) Date {
return Date{ return Date{
Year: t.Year(), Year: t.Year(),

View File

@@ -146,3 +146,37 @@ func UnixFloatSeconds(v float64) time.Time {
func FloorTime(t time.Time) time.Time { func FloorTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
} }
func SubtractYears(t time.Time, yearCount float64, tz *time.Location) time.Time {
t = t.In(tz)
if yearCount < 0 {
return AddYears(t, -yearCount, tz)
}
intCount, floatCount := math.Modf(yearCount)
t.AddDate(-int(intCount), 0, 0)
t0 := TimeToYearStart(t, tz)
t1 := TimeToYearEnd(t, tz)
return t.Add(time.Duration(float64(t1.Sub(t0)) * floatCount * -1))
}
func AddYears(t time.Time, yearCount float64, tz *time.Location) time.Time {
t = t.In(tz)
if yearCount < 0 {
return SubtractYears(t, -yearCount, tz)
}
intCount, floatCount := math.Modf(yearCount)
t.AddDate(int(intCount), 0, 0)
t0 := TimeToYearStart(t, tz)
t1 := TimeToYearEnd(t, tz)
return t.Add(time.Duration(float64(t1.Sub(t0)) * floatCount))
}

View File

@@ -171,7 +171,7 @@ func createPaginationPipeline[TData any](coll *Coll[TData], token ct.CursorToken
bson.M{*fieldSecondary: bson.M{"$gt": valueSecondary}}, bson.M{*fieldSecondary: bson.M{"$gt": valueSecondary}},
}}) }})
sort = append(sort, bson.E{Key: fieldPrimary, Value: +1}) sort = append(sort, bson.E{Key: *fieldSecondary, Value: +1})
} else if *sortSecondary == ct.SortDESC { } else if *sortSecondary == ct.SortDESC {
@@ -181,7 +181,7 @@ func createPaginationPipeline[TData any](coll *Coll[TData], token ct.CursorToken
bson.M{*fieldSecondary: bson.M{"$lt": valueSecondary}}, bson.M{*fieldSecondary: bson.M{"$lt": valueSecondary}},
}}) }})
sort = append(sort, bson.E{Key: fieldPrimary, Value: -1}) sort = append(sort, bson.E{Key: *fieldSecondary, Value: -1})
} }
} }