320 lines
7.7 KiB
Go
320 lines
7.7 KiB
Go
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
|
|
}
|