Skip to content

Commit

Permalink
common: add functions: ConvertBytesToSlice(), SerializeBytes(), Deser…
Browse files Browse the repository at this point in the history
…ializeBytes()

mysql: customize MarshalJSON() and UnmarshalJSON() of Result to serialize and deserialize correctly
  • Loading branch information
romberli committed Aug 22, 2024
1 parent ecd2c0c commit 8ea59f7
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 59 deletions.
50 changes: 10 additions & 40 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,49 +572,19 @@ func SetValueOfStructByKind(in interface{}, field string, value interface{}, kin
}

k := fieldType.Type.Elem().Kind()
b, ok := value.([]uint8)
b, ok := value.([]byte)
if ok && json.Valid(b) {
switch k {
case reflect.Bool:
result := make([]bool, constant.ZeroInt)
// unmarshal
err := json.Unmarshal(b, &result)
if err != nil {
return errors.Trace(err)
}

return SetValueOfStruct(in, field, result)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
result := make([]int, constant.ZeroInt)
// unmarshal
err := json.Unmarshal(b, &result)
if err != nil {
return errors.Trace(err)
}

return SetValueOfStruct(in, field, result)
case reflect.Float32, reflect.Float64:
result := make([]float64, constant.ZeroInt)
// unmarshal
err := json.Unmarshal(b, &result)
if err != nil {
return errors.Trace(err)
}

return SetValueOfStruct(in, field, result)
case reflect.String:
result := make([]string, constant.ZeroInt)
// unmarshal
err := json.Unmarshal(b, &result)
if err != nil {
return errors.Trace(err)
}
v, err := ConvertBytesToSlice(b, k)
if err != nil {
return err
}

return SetValueOfStruct(in, field, result)
default:
return errors.Errorf("unsupported data type: %s", k.String())
err = SetValueOfStruct(in, field, v)
if err != nil {
return err
}

return nil
}

v, err := ConvertToSlice(value, k)
Expand Down
65 changes: 62 additions & 3 deletions common/convert.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"encoding/json"
"fmt"
"reflect"
"strconv"
Expand Down Expand Up @@ -69,7 +70,7 @@ func ConvertInterfaceToSliceInterface(in interface{}) ([]interface{}, error) {
return sliceInterface, nil
}

// ConvertInterfaceToMapInterfaceInterface converts input data which must be map type to map interface interface,
// ConvertInterfaceToMapInterfaceInterface converts input data which must be map type to map[interface]interface,
// it means each pair of key and value in the map will be interface type
func ConvertInterfaceToMapInterfaceInterface(in interface{}) (map[interface{}]interface{}, error) {
inType := reflect.TypeOf(in)
Expand All @@ -89,6 +90,46 @@ func ConvertInterfaceToMapInterfaceInterface(in interface{}) (map[interface{}]in
return mapInterface, nil
}

func ConvertBytesToSlice(bytes []byte, kind reflect.Kind) (interface{}, error) {
switch kind {
case reflect.Bool:
result := make([]bool, constant.ZeroInt)
err := json.Unmarshal(bytes, &result)
if err != nil {
return nil, errors.Trace(err)
}

return result, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
result := make([]int, constant.ZeroInt)
err := json.Unmarshal(bytes, &result)
if err != nil {
return nil, errors.Trace(err)
}

return result, nil
case reflect.Float32, reflect.Float64:
result := make([]float64, constant.ZeroInt)
err := json.Unmarshal(bytes, &result)
if err != nil {
return nil, errors.Trace(err)
}

return result, nil
case reflect.String:
result := make([]string, constant.ZeroInt)
err := json.Unmarshal(bytes, &result)
if err != nil {
return nil, errors.Trace(err)
}

return result, nil
default:
return nil, errors.Errorf("unsupported data type: %s", kind.String())
}
}

func ConvertToBool(in interface{}) (bool, error) {
switch in.(type) {
case bool:
Expand Down Expand Up @@ -265,8 +306,26 @@ func ConvertToString(in interface{}) (string, error) {

func ConvertToSlice(in interface{}, kind reflect.Kind) (interface{}, error) {
inKind := reflect.TypeOf(in).Kind()
if inKind != reflect.Slice {
return nil, errors.Errorf("value must be a slice, not %s", inKind.String())
if inKind != reflect.String && inKind != reflect.Slice {
return nil, errors.Errorf("value must be a string or a slice, not %s", inKind.String())
}

if inKind == reflect.String {
inStr, ok := in.(string)
if !ok {
return nil, errors.New("convert to string failed")
}
inBytes := []byte(inStr)
if json.Valid(inBytes) {
val, err := ConvertBytesToSlice(inBytes, kind)
if err != nil {
return nil, err
}

return val, nil
} else {
return nil, errors.New("invalid json string")
}
}

inVal := reflect.ValueOf(in)
Expand Down
131 changes: 131 additions & 0 deletions common/json.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package common

import (
"fmt"
"reflect"
"strings"

"github.com/buger/jsonparser"
Expand Down Expand Up @@ -36,3 +38,132 @@ func GetLength(data []byte, path string) (int, error) {

return len(result.Array()), nil
}

// SerializeBytes serializes the struct to map[string]interface{} and []byte to string
func SerializeBytes(v interface{}) interface{} {
val := reflect.ValueOf(v)

switch val.Kind() {
case reflect.Ptr:
if val.IsNil() {
return nil
}
elem := val.Elem().Interface()
return SerializeBytes(elem)
case reflect.Struct:
serialized := make(map[string]interface{})
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)

// get json tag
tag := field.Tag.Get(constant.DefaultJSONTag)
// if tag is "-", skip this field
if tag == constant.DashString {
continue
}
key := field.Name
if tag != constant.EmptyString {
tagParts := strings.Split(tag, constant.CommaString)
if tagParts[constant.ZeroInt] != constant.EmptyString {
key = tagParts[constant.ZeroInt]
}
}

fieldValue := val.Field(i).Interface()
serialized[key] = SerializeBytes(fieldValue)
}
return serialized
case reflect.Slice:
if val.Type().Elem().Kind() == reflect.Uint8 {
// convert []byte to string
return string(val.Bytes())
}
// other slice types
serialized := make([]interface{}, val.Len())
for i := 0; i < val.Len(); i++ {
serialized[i] = SerializeBytes(val.Index(i).Interface())
}
return serialized
case reflect.Map:
serialized := make(map[string]interface{})
for _, key := range val.MapKeys() {
keyStr := fmt.Sprintf("%v", key)
serialized[keyStr] = SerializeBytes(val.MapIndex(key).Interface())
}
return serialized
default:
return v
}
}

// DeserializeBytes deserializes the map[string]interface{} to struct and string to []byte
func DeserializeBytes(v interface{}, t reflect.Type) interface{} {
val := reflect.ValueOf(v)

switch t.Kind() {
case reflect.Ptr:
if val.IsNil() {
return reflect.Zero(t).Interface()
}
elemType := t.Elem()
elemValue := DeserializeBytes(val.Interface(), elemType)
ptrValue := reflect.New(elemType)
ptrValue.Elem().Set(reflect.ValueOf(elemValue))
return ptrValue.Interface()
case reflect.Struct:
structValue := reflect.New(t).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)

// get json tag
tag := field.Tag.Get(constant.DefaultJSONTag)
// if tag is "-", skip this field
if tag == constant.DashString {
continue
}
key := field.Name
if tag != constant.EmptyString {
tagParts := strings.Split(tag, constant.CommaString)
if tagParts[constant.ZeroInt] != constant.EmptyString {
key = tagParts[constant.ZeroInt]
}
}

fieldValue := DeserializeBytes(val.MapIndex(reflect.ValueOf(key)).Interface(), field.Type)
structValue.Field(i).Set(reflect.ValueOf(fieldValue))
}
return structValue.Interface()
case reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 {
// convert string to []byte
return []byte(val.String())
}
// other slice types
sliceValue := reflect.MakeSlice(t, val.Len(), val.Len())
for i := 0; i < val.Len(); i++ {
sliceValue.Index(i).Set(reflect.ValueOf(DeserializeBytes(val.Index(i).Interface(), t.Elem())))
}
return sliceValue.Interface()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if val.Kind() == reflect.Float64 {
return int(val.Float())
}
// convert to int
return int(val.Float())
case reflect.Float32, reflect.Float64:
return val.Float()
case reflect.String:
return val.String()
case reflect.Map:
mapType := reflect.MapOf(t.Key(), t.Elem())
mapValue := reflect.MakeMap(mapType)
for _, key := range val.MapKeys() {
mapKey := reflect.ValueOf(key.Interface()).Convert(t.Key())
mapElem := DeserializeBytes(val.MapIndex(key).Interface(), t.Elem())
mapValue.SetMapIndex(mapKey, reflect.ValueOf(mapElem))
}
return mapValue.Interface()
default:
return v
}
}
76 changes: 76 additions & 0 deletions common/json_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
package common

import (
"encoding/json"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
)

type OtherStruct struct {
Data []byte
Other string `json:"other"`
Another int `json:",omitempty"`
FloatPropA float64 `json:"float_property_a"`
FloatPropB float64 `json:"float_property_b,omitempty"`
Nested *NestedStruct
}

type NestedStruct struct {
InnerData []byte `json:"-,omitempty"`
}

type Example struct {
Data *OtherStruct
}

func (e *Example) MarshalJSON() ([]byte, error) {
serialized := SerializeBytes(e)
return json.Marshal(serialized)
}

func (e *Example) UnmarshalJSON(data []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
deserialized := DeserializeBytes(raw, reflect.TypeOf(*e))
*e = deserialized.(Example)
return nil
}

func TestKeyPathExists(t *testing.T) {
asst := assert.New(t)

Expand All @@ -22,3 +56,45 @@ func TestGetLength(t *testing.T) {
asst.Nil(err, "test GetLength() failed")
asst.Equal(3, length, "test GetLength() failed")
}

func TestSerializeBytes(t *testing.T) {
asst := assert.New(t)

nested := &NestedStruct{
InnerData: []byte("Nested data!"),
}

os := &OtherStruct{
Data: []byte("Hello, World!"),
Other: "Some other data",
Another: 123,
FloatPropA: 1.23,
FloatPropB: 4.0,
Nested: nested,
}

tBytes, err := json.Marshal(t)
asst.Nil(err, "test SerializeBytes() failed")
t.Logf("T Serialized: %s", string(tBytes))

ex1 := &Example{
Data: os,
}

// serialize Example struct
serialized, err := json.Marshal(ex1)
asst.Nil(err, "test SerializeBytes() failed")
t.Logf("Serialized: %s", string(serialized))

var ex2 Example
err = json.Unmarshal(serialized, &ex2)
asst.Nil(err, "test SerializeBytes() failed")
t.Logf("Deserialized: %+v", ex2)

asst.Equal(ex1.Data.Data, ex2.Data.Data, "test SerializeBytes() failed")
asst.Equal(ex1.Data.Other, ex2.Data.Other, "test SerializeBytes() failed")
asst.Equal(ex1.Data.Another, ex2.Data.Another, "test SerializeBytes() failed")
asst.Equal(ex1.Data.FloatPropA, ex2.Data.FloatPropA, "test SerializeBytes() failed")
asst.Equal(ex1.Data.FloatPropB, ex2.Data.FloatPropB, "test SerializeBytes() failed")
asst.Equal(ex1.Data.Nested.InnerData, ex2.Data.Nested.InnerData, "test SerializeBytes() failed")
}
Loading

0 comments on commit 8ea59f7

Please sign in to comment.