add json serialization+deserialization helpers
This commit is contained in:
parent
3eddc018d9
commit
d98fd68206
3
go.mod
3
go.mod
@ -11,7 +11,10 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Jeffail/gabs v1.4.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/henvic/pgq v0.0.4 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -1,9 +1,15 @@
|
||||
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
|
||||
github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/go-loremipsum/loremipsum v1.1.4 h1:RJaJlJwX4y9A2+CMgKIyPcjuFHFKTmaNMhxbL+sI6Vg=
|
||||
github.com/go-loremipsum/loremipsum v1.1.4/go.mod h1:whNWskGoefTakPnCu2CO23v5Y7RwiG4LMOEtTDaBeOY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/henvic/pgq v0.0.4 h1:BgLnxofZJSWWs+9VOf19Gr9uBkSVbHWGiu8wix1nsIY=
|
||||
github.com/henvic/pgq v0.0.4/go.mod h1:k0FMvOgmQ45MQ3TgCLe8I3+sDKy9lPAiC2m9gg37pVA=
|
||||
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
||||
|
319
json.go
Normal file
319
json.go
Normal file
@ -0,0 +1,319 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fatih/structtag"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
func defaultEngine() *Engine {
|
||||
return engines.Engines[defaultKey]
|
||||
}
|
||||
|
||||
func anyToModel(input any) *Model {
|
||||
rv := reflect.TypeOf(input)
|
||||
for rv.Kind() == reflect.Ptr ||
|
||||
rv.Kind() == reflect.Interface ||
|
||||
rv.Kind() == reflect.Slice || rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
maybeEngine := defaultEngine()
|
||||
if maybeEngine == nil {
|
||||
return nil
|
||||
}
|
||||
return maybeEngine.modelMap.Map[rv.Name()]
|
||||
}
|
||||
|
||||
func JSONSerialize(input any, pretty bool) ([]byte, error) {
|
||||
vp := reflect.ValueOf(input)
|
||||
vt := reflect.TypeOf(input)
|
||||
if vt.Kind() != reflect.Pointer {
|
||||
return nil, fmt.Errorf("Argument must be a pointer or pointer to a slice; got: %v", vt.Kind())
|
||||
}
|
||||
ser, err := innerSerialize(vp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pretty {
|
||||
return json.MarshalIndent(ser, "", "\t")
|
||||
}
|
||||
return json.Marshal(ser)
|
||||
}
|
||||
|
||||
func JSONDeserialize(val any, ser []byte) error {
|
||||
var fiv any
|
||||
if err := json.Unmarshal(ser, &fiv); err != nil {
|
||||
return err
|
||||
}
|
||||
vp := reflect.ValueOf(val)
|
||||
if vp.Kind() != reflect.Pointer {
|
||||
return fmt.Errorf("Argument must be a pointer or pointer to a slice; got: %v", vp.Kind())
|
||||
}
|
||||
m := anyToModel(val)
|
||||
if m == nil {
|
||||
return fmt.Errorf("No model found for type '%s'", vp.Type().Name())
|
||||
}
|
||||
maybeEngine := defaultEngine()
|
||||
if maybeEngine == nil {
|
||||
return fmt.Errorf("No engines have been created!?")
|
||||
}
|
||||
fv, err := innerDeserialize(fiv, m, maybeEngine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vp.Elem().Set(fv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func innerSerialize(v reflect.Value) (ret any, err error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Interface:
|
||||
v = v.Elem()
|
||||
fallthrough
|
||||
case reflect.Pointer:
|
||||
if v.IsNil() {
|
||||
return ret, nil
|
||||
}
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.IsZero() {
|
||||
return ret, nil
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
m := anyToModel(v.Interface())
|
||||
|
||||
if m == nil {
|
||||
if canConvertTo[time.Time](v.Type()) {
|
||||
ret = v.Interface().(time.Time).Format(time.RFC3339)
|
||||
} else {
|
||||
var bytes []byte
|
||||
bytes, err = json.Marshal(v.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ser := make(map[string]any)
|
||||
err = json.Unmarshal(bytes, &ser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = ser
|
||||
}
|
||||
|
||||
} else {
|
||||
depopulated, depopulatedId := isDepopulated(v, m.IDField)
|
||||
if depopulated {
|
||||
ret = depopulatedId
|
||||
} else {
|
||||
rmap := make(map[string]any)
|
||||
for i := range v.NumField() {
|
||||
fv := v.Field(i)
|
||||
ft := v.Type().Field(i)
|
||||
var tag *structtag.Tags
|
||||
tag, err = structtag.Parse(string(ft.Tag))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonTag *structtag.Tag
|
||||
jsonTag, err = tag.Get("json")
|
||||
if err != nil || jsonTag.Name == "-" {
|
||||
continue
|
||||
}
|
||||
if jsonTag.Name == "" {
|
||||
// we are dealing with an inlined/anonymous struct
|
||||
var maybeMap any
|
||||
maybeMap, err = innerSerialize(fv)
|
||||
if amap, ok := maybeMap.(map[string]any); ok {
|
||||
for k, vv := range amap {
|
||||
rmap[k] = vv
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rmap[jsonTag.Name], err = innerSerialize(fv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
ret = rmap
|
||||
}
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
ret0 := make([]any, 0)
|
||||
for i := range v.Len() {
|
||||
var ser any
|
||||
ser, err = innerSerialize(v.Index(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret0 = append(ret0, ser)
|
||||
}
|
||||
ret = ret0
|
||||
default:
|
||||
ret = v.Interface()
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func innerDeserialize(input any, m *Model, e *Engine) (nv reflect.Value, err error) {
|
||||
t := m.Type
|
||||
irv := reflect.ValueOf(input)
|
||||
if irv.Kind() == reflect.Slice || irv.Kind() == reflect.Array {
|
||||
nv = reflect.MakeSlice(reflect.SliceOf(t), 0, 0)
|
||||
for i := range irv.Len() {
|
||||
var snv reflect.Value
|
||||
cur := irv.Index(i)
|
||||
snv, err = innerDeserialize(cur.Interface(), m, e)
|
||||
if err != nil {
|
||||
return snv, err
|
||||
}
|
||||
nv = reflect.Append(nv, snv)
|
||||
}
|
||||
} else { // it's a map or primitive value
|
||||
nv = reflect.New(t).Elem()
|
||||
if asMap, ok := input.(map[string]any); ok {
|
||||
for _, f := range m.Fields {
|
||||
if f.Index < 0 {
|
||||
continue
|
||||
}
|
||||
ft := f.Original
|
||||
fv := nv.Field(f.Index)
|
||||
var tags *structtag.Tags
|
||||
var btag *structtag.Tag
|
||||
tags, err = structtag.Parse(string(ft.Tag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
btag, err = tags.Get("json")
|
||||
if err != nil || btag.Name == "-" {
|
||||
continue
|
||||
}
|
||||
interm := asMap[btag.Name]
|
||||
var tmp any
|
||||
if str, sok := interm.(string); sok {
|
||||
if ttmp, terr := time.Parse(time.RFC3339, str); terr == nil {
|
||||
tmp = ttmp
|
||||
} else {
|
||||
tmp = interm
|
||||
}
|
||||
} else {
|
||||
tmp = interm
|
||||
}
|
||||
switch fv.Kind() {
|
||||
case reflect.Int64, reflect.Int32, reflect.Int, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if tmp != nil {
|
||||
fv.Set(reflect.ValueOf(tmp).Convert(ft.Type))
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
if interm != nil {
|
||||
|
||||
slic := reflect.ValueOf(interm)
|
||||
fv.Set(handleSliceMaybe(slic, fv.Type().Elem()))
|
||||
}
|
||||
default:
|
||||
if ft.Anonymous {
|
||||
var nfv reflect.Value
|
||||
nfv, err = handleAnon(input, ft.Type)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fv.Set(nfv)
|
||||
} else {
|
||||
fv.Set(reflect.ValueOf(tmp))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range m.Relationships {
|
||||
if r.Type == BelongsTo || r.Type == ManyToOne || r.Idx < 0 {
|
||||
continue
|
||||
}
|
||||
ft := r.OriginalField
|
||||
fv := nv.Field(r.Idx)
|
||||
var tags *structtag.Tags
|
||||
var btag *structtag.Tag
|
||||
tags, err = structtag.Parse(string(ft.Tag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
btag, err = tags.Get("json")
|
||||
if err != nil || btag.Name == "-" {
|
||||
continue
|
||||
}
|
||||
var rv reflect.Value
|
||||
interm := asMap[btag.Name]
|
||||
rv, err = innerDeserialize(interm, r.RelatedModel, e)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
fv.Set(rv)
|
||||
}
|
||||
} else {
|
||||
iface := nv.Addr().Interface()
|
||||
err = e.Model(iface).Where(fmt.Sprintf("%s = ?", m.IDField), input).Find(iface)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleAnon(raw any, rtype reflect.Type) (nv reflect.Value, err error) {
|
||||
nv = reflect.New(rtype).Elem()
|
||||
amap, ok := raw.(map[string]any)
|
||||
if ok {
|
||||
for i := range rtype.NumField() {
|
||||
ft := rtype.Field(i)
|
||||
fv := nv.Field(i)
|
||||
var tags *structtag.Tags
|
||||
var btag *structtag.Tag
|
||||
tags, err = structtag.Parse(string(ft.Tag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
btag, terr := tags.Get("json")
|
||||
if terr != nil || btag.Name == "-" || !ft.IsExported() {
|
||||
continue
|
||||
}
|
||||
fval := amap[btag.Name]
|
||||
if reflect.TypeOf(fval) == reflect.TypeFor[string]() && ft.Type == reflect.TypeFor[time.Time]() {
|
||||
tt, _ := time.Parse(time.RFC3339, fval.(string))
|
||||
fv.Set(reflect.ValueOf(tt))
|
||||
} else if fval != nil {
|
||||
fv.Set(reflect.ValueOf(fval))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func handleSliceMaybe(iv reflect.Value, dstType reflect.Type) reflect.Value {
|
||||
if iv.Kind() != reflect.Slice && iv.Kind() != reflect.Pointer {
|
||||
return iv
|
||||
}
|
||||
dst := reflect.MakeSlice(reflect.SliceOf(dstType), 0, 0)
|
||||
for i := range iv.Len() {
|
||||
//dst.Set(reflect.Append(fv, handleSliceMaybe(iv.Index(i).Elem())))
|
||||
maybeIface := iv.Index(i)
|
||||
if maybeIface.Kind() == reflect.Interface {
|
||||
maybeIface = maybeIface.Elem()
|
||||
}
|
||||
maybeNdest := dstType
|
||||
if maybeNdest.Kind() == reflect.Slice || maybeNdest.Kind() == reflect.Array {
|
||||
maybeNdest = maybeNdest.Elem()
|
||||
}
|
||||
dst = reflect.Append(dst, handleSliceMaybe(maybeIface, maybeNdest))
|
||||
}
|
||||
return dst
|
||||
}
|
||||
func isDepopulated(v reflect.Value, idField string) (bool, any) {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
syn := reflect.New(v.Type()).Elem()
|
||||
syn.FieldByName(idField).Set(v.FieldByName(idField))
|
||||
finalId := v.FieldByName(idField).Interface()
|
||||
return reflect.DeepEqual(v.Interface(), syn.Interface()), finalId
|
||||
}
|
74
json_test.go
Normal file
74
json_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/Jeffail/gabs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonSerialize(t *testing.T) {
|
||||
e := initTest(t)
|
||||
defer e.Disconnect()
|
||||
u := author(t)
|
||||
err := e.Model(&user{}).Save(&u)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
insertBands(t, e)
|
||||
ns := storyBase(e, t, u, "Chapters.Bands")
|
||||
bytes, err := JSONSerialize(ns, true)
|
||||
assert.Nil(t, err)
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
|
||||
func TestJSONDeserialize(t *testing.T) {
|
||||
e := initTest(t)
|
||||
defer e.Disconnect()
|
||||
u := author(t)
|
||||
err := e.Model(&user{}).Save(&u)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
insertBands(t, e)
|
||||
ns := storyBase(e, t, u, "Chapters.Bands")
|
||||
bytes, err := JSONSerialize(ns, true)
|
||||
assert.Nil(t, err)
|
||||
fmt.Println(string(bytes))
|
||||
msi := make(map[string]any)
|
||||
err = json.Unmarshal(bytes, &msi)
|
||||
assert.Nil(t, err)
|
||||
obj, err := gabs.Consume(msi)
|
||||
assert.Nil(t, err)
|
||||
children, err := obj.S("chapters").Children()
|
||||
assert.Nil(t, err)
|
||||
for _, child := range children {
|
||||
bands := child.S("bands")
|
||||
var bcontainer []*gabs.Container
|
||||
bcontainer, err = bands.Children()
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for j := range bcontainer {
|
||||
id := bcontainer[j].S("_id").Data()
|
||||
//obj.S("chapters").Index(i).S("bands").Index
|
||||
_, err = bands.SetIndex(id, j)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
nbytes := obj.Bytes()
|
||||
var des story
|
||||
err = JSONDeserialize(&des, nbytes)
|
||||
assert.Nil(t, err)
|
||||
for _, c := range des.Chapters {
|
||||
assert.NotNil(t, c.Bands)
|
||||
assert.GreaterOrEqual(t, len(c.Bands), 1)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user