Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move mapToStruct to marshal package #114

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 2 additions & 67 deletions pkg/conn/gorilla/gorilla.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"sync"
"time"

"github.com/surrealdb/surrealdb.go/pkg/marshal"
"github.com/surrealdb/surrealdb.go/pkg/model"

gorilla "github.com/gorilla/websocket"
Expand Down Expand Up @@ -270,7 +269,7 @@ func (ws *WebSocket) handleResponse(res rpc.RPCResponse) {
return
}
var notification model.Notification
err := unmarshalMapToStruct(mappedRes, &notification)
err := marshal.UnmarshalMapToStruct(mappedRes, &notification)
if err != nil {
ws.logger.Error(err.Error(), "result", fmt.Sprint(res.Result))
return
Expand All @@ -284,67 +283,3 @@ func (ws *WebSocket) handleResponse(res rpc.RPCResponse) {
LiveNotificationChan <- notification
}
}

func unmarshalMapToStruct(data map[string]interface{}, outStruct interface{}) error {
outValue := reflect.ValueOf(outStruct)
if outValue.Kind() != reflect.Ptr || outValue.Elem().Kind() != reflect.Struct {
return fmt.Errorf("outStruct must be a pointer to a struct")
}

structValue := outValue.Elem()
structType := structValue.Type()

for i := 0; i < structValue.NumField(); i++ {
field := structType.Field(i)
fieldName := field.Name
jsonTag := field.Tag.Get("json")
if jsonTag != "" {
fieldName = jsonTag
}
mapValue, ok := data[fieldName]
if !ok {
return fmt.Errorf("missing field in map: %s", fieldName)
}

fieldValue := structValue.Field(i)
if !fieldValue.CanSet() {
return fmt.Errorf("cannot set field: %s", fieldName)
}

if mapValue == nil {
// Handle nil values appropriately for your struct fields
// For simplicity, we skip nil values in this example
continue
}

// Type conversion based on the field type
switch fieldValue.Kind() {
case reflect.String:
fieldValue.SetString(fmt.Sprint(mapValue))
case reflect.Int:
intVal, err := strconv.Atoi(fmt.Sprint(mapValue))
if err != nil {
return err
}
fieldValue.SetInt(int64(intVal))
case reflect.Bool:
boolVal, err := strconv.ParseBool(fmt.Sprint(mapValue))
if err != nil {
return err
}
fieldValue.SetBool(boolVal)
case reflect.Map:
mapVal, ok := mapValue.(map[string]interface{})
if !ok {
return fmt.Errorf("mapValue for property %s is not a map[string]interface{}", fieldName)
}
fieldValue.Set(reflect.ValueOf(mapVal))

// Add cases for other types as needed
default:
return fmt.Errorf("unsupported field type: %s", fieldName)
}
}

return nil
}
75 changes: 75 additions & 0 deletions pkg/marshal/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"reflect"
"strconv"

"github.com/surrealdb/surrealdb.go/pkg/constants"
"github.com/surrealdb/surrealdb.go/pkg/util"
Expand Down Expand Up @@ -162,3 +163,77 @@ func SmartMarshal[I any](inputfunc interface{}, data I) (output interface{}, err
}
return nil, ErrNotValidFunc
}

// UnmarshalMapToStruct unmarshals a map[string]interface{} to a struct.
func UnmarshalMapToStruct(data map[string]interface{}, outStruct interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this moving, but we need to remember that this will change with the introduction of Column and Rows

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course, but for now, we should continue to improve the project in meanwhile.

outValue := reflect.ValueOf(outStruct)
if outValue.Kind() != reflect.Ptr || outValue.Elem().Kind() != reflect.Struct {
return fmt.Errorf("outStruct must be a pointer to a struct")
}

structValue := outValue.Elem()
structType := structValue.Type()

for i := 0; i < structValue.NumField(); i++ {
field := structType.Field(i)
fieldName := field.Name
jsonTag, jsonOk := field.Tag.Lookup("json")
if jsonOk {
fieldName = jsonTag
}
mapValue, mapOk := data[fieldName]
if !mapOk {
return fmt.Errorf("missing field in map: %s", fieldName)
}

if mapValue == nil {
// if values nil we will skip for speed
continue
}

fieldValue := structValue.Field(i)
if !fieldValue.CanSet() {
return fmt.Errorf("cannot set field: %s", fieldName)
}

// Type conversion based on the field type
err := setField(&fieldValue, fieldName, mapValue)
if err != nil {
return err
}
}

return nil
}

// setField helper function for UnmarshalMapToStruct.
// Set Value of field based on its type.
func setField(fieldValue *reflect.Value, fieldName string, mapValue interface{}) error {
switch fieldValue.Kind() {
case reflect.String:
fieldValue.SetString(fmt.Sprint(mapValue))
case reflect.Int:
intVal, err := strconv.Atoi(fmt.Sprint(mapValue))
if err != nil {
return err
}
fieldValue.SetInt(int64(intVal))
case reflect.Bool:
boolVal, err := strconv.ParseBool(fmt.Sprint(mapValue))
if err != nil {
return err
}
fieldValue.SetBool(boolVal)
case reflect.Map:
mapVal, ok := mapValue.(map[string]interface{})
if !ok {
return fmt.Errorf("mapValue for property %s is not a map[string]interface{}", fieldName)
}
fieldValue.Set(reflect.ValueOf(mapVal))

// Add cases for other types as needed
default:
return fmt.Errorf("unsupported field type: %s", fieldName)
}
return nil
}
28 changes: 28 additions & 0 deletions pkg/marshal/marshal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package marshal_test

import (
"testing"

"github.com/stretchr/testify/require"
"github.com/surrealdb/surrealdb.go/pkg/marshal"
)

type testMarshalStruct struct {
TestInt int `json:"TestInt"`
TestBool bool `json:"TestBool"`
}

func TestUnMarshalMapToStruct(t *testing.T) {
testDataMap := make(map[string]interface{}, 2)

testObj := &testMarshalStruct{
TestInt: 15,
TestBool: true,
}

testDataMap["TestInt"] = testObj.TestInt
testDataMap["TestBool"] = testObj.TestBool

err := marshal.UnmarshalMapToStruct(testDataMap, testObj)
require.NoError(t, err)
}