Skip to content
Merged
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
171 changes: 171 additions & 0 deletions pb/struct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package pb

import (
"fmt"
"reflect"

types "github.com/gogo/protobuf/types"
)

// ToStruct converts a map[string]interface{} to a types.Struct
func ToStruct(v map[string]interface{}) *types.Struct {
size := len(v)
if size == 0 {
return nil
}

fields := make(map[string]*types.Value, size)
for k, v := range v {
fields[k] = ToValue(v)
}

return &types.Struct{
Fields: fields,
}
}

func newBoolValue(v bool) *types.Value {
return &types.Value{
Kind: &types.Value_BoolValue{
BoolValue: v,
},
}
}

func newNumberValue(v float64) *types.Value {
return &types.Value{
Kind: &types.Value_NumberValue{
NumberValue: v,
},
}
}

// ToValue converts an interface{} to a types.Value
func ToValue(v interface{}) *types.Value {
switch v := v.(type) {
case nil:
return nil
case bool:
return newBoolValue(v)
case int:
return newNumberValue(float64(v))
case int8:
return newNumberValue(float64(v))
case int32:
return newNumberValue(float64(v))
case int64:
return newNumberValue(float64(v))
case uint:
return newNumberValue(float64(v))
case uint8:
return newNumberValue(float64(v))
case uint32:
return newNumberValue(float64(v))
case uint64:
return newNumberValue(float64(v))
case float32:
return newNumberValue(float64(v))
case float64:
return newNumberValue(float64(v))
case string:
return &types.Value{
Kind: &types.Value_StringValue{
StringValue: v,
},
}
default:
return toValue(reflect.ValueOf(v))
}
}

func toValue(v reflect.Value) *types.Value {
switch v.Kind() {
case reflect.Bool:
return newBoolValue(v.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return newNumberValue(float64(v.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return newNumberValue(float64(v.Uint()))
case reflect.Float32, reflect.Float64:
return newNumberValue(v.Float())
case reflect.Ptr:
if v.IsNil() {
return nil
}
return toValue(reflect.Indirect(v))
case reflect.Array, reflect.Slice:
size := v.Len()
if size == 0 {
return nil
}
values := make([]*types.Value, size)
for i := 0; i < size; i++ {
values[i] = toValue(v.Index(i))
}
return &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: values,
},
},
}
case reflect.Struct:
t := v.Type()
size := v.NumField()
if size == 0 {
return nil
}
fields := make(map[string]*types.Value, size)
for i := 0; i < size; i++ {
name := t.Field(i).Name
if len(name) > 0 {
fields[name] = toValue(v.Field(i))
}
}
if len(fields) == 0 {
return nil
}
return &types.Value{
Kind: &types.Value_StructValue{
StructValue: &types.Struct{
Fields: fields,
},
},
}
case reflect.Map:
keys := v.MapKeys()
if len(keys) == 0 {
return nil
}
fields := make(map[string]*types.Value, len(keys))
for _, k := range keys {
if k.Kind() == reflect.String {
fields[k.String()] = toValue(v.MapIndex(k))
} else if k.Kind() == reflect.Interface {
ik := k.Interface()
sk, ok := ik.(string)
if ok {
fields[sk] = toValue(v.MapIndex(k))
}
}
}
if len(fields) == 0 {
return nil
}
return &types.Value{
Kind: &types.Value_StructValue{
StructValue: &types.Struct{
Fields: fields,
},
},
}
case reflect.Interface:
return ToValue(v.Interface())
default:
return &types.Value{
Kind: &types.Value_StringValue{
StringValue: fmt.Sprint(v),
},
}
}
}
170 changes: 170 additions & 0 deletions pb/struct_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package pb

import (
"testing"

types "github.com/gogo/protobuf/types"
"github.com/stretchr/testify/require"
)

func TestToStruct(t *testing.T) {
require := require.New(t)

inputMap := map[string]interface{}{
"bool": true,
"int": 1,
"string": "val",
"float": 0.5,
"nil": nil,
"array": []string{"val1", "val2"},
"map": map[string]int{
"field1": 1,
},
"struct": struct {
Val string
}{Val: "val"},
}

expectedSt := &types.Struct{
Fields: map[string]*types.Value{
"bool": &types.Value{
Kind: &types.Value_BoolValue{
BoolValue: true,
},
},
"int": &types.Value{
Kind: &types.Value_NumberValue{
NumberValue: 1,
},
},
"string": &types.Value{
Kind: &types.Value_StringValue{
StringValue: "val",
},
},
"float": &types.Value{
Kind: &types.Value_NumberValue{
NumberValue: 0.5,
},
},
"nil": nil,
"array": &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: []*types.Value{
&types.Value{
Kind: &types.Value_StringValue{
StringValue: "val1",
},
},
&types.Value{
Kind: &types.Value_StringValue{
StringValue: "val2",
},
},
},
},
},
},
"map": &types.Value{
Kind: &types.Value_StructValue{
StructValue: &types.Struct{
Fields: map[string]*types.Value{
"field1": &types.Value{
Kind: &types.Value_NumberValue{
NumberValue: 1,
},
},
},
},
},
},
"struct": &types.Value{
Kind: &types.Value_StructValue{
StructValue: &types.Struct{
Fields: map[string]*types.Value{
"Val": &types.Value{
Kind: &types.Value_StringValue{
StringValue: "val",
},
},
},
},
},
},
},
}

st := ToStruct(inputMap)
require.Equal(expectedSt, st)
}

func TestToStructMapInterfaceInterface(t *testing.T) {
require := require.New(t)

inputMap := map[string]interface{}{
"map": map[interface{}]interface{}{
"field1": "val",
},
}

expectedSt := &types.Struct{
Fields: map[string]*types.Value{
"map": &types.Value{
Kind: &types.Value_StructValue{
StructValue: &types.Struct{
Fields: map[string]*types.Value{
"field1": &types.Value{
Kind: &types.Value_StringValue{
StringValue: "val",
},
},
},
},
},
},
},
}

st := ToStruct(inputMap)
require.Equal(expectedSt, st)
}

func TestToStructSliceInterfaceWithMap(t *testing.T) {
require := require.New(t)

inputMap := map[string]interface{}{
"array": []interface{}{map[interface{}]interface{}{
"field1": "val",
}},
}

expectedSt := &types.Struct{
Fields: map[string]*types.Value{
"array": &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: []*types.Value{
&types.Value{
Kind: &types.Value_StructValue{
StructValue: &types.Struct{
Fields: map[string]*types.Value{
"field1": &types.Value{
Kind: &types.Value_StringValue{
StringValue: "val",
},
},
},
},
},
},
},
},
},
},
},
}

st := ToStruct(inputMap)
require.Equal(expectedSt, st)
}