1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-11 10:18:28 +09:00
anytype-heart/util/pbtypes/struct.go
2023-04-25 18:17:59 +02:00

279 lines
5.7 KiB
Go

// https://gist.github.com/jsmouret/2bc876e8def6c63410556350eca3e43d
package pbtypes
import (
"fmt"
"reflect"
st "github.com/gogo/protobuf/types"
)
func StructDeleteEmptyFields(v *st.Struct) {
if v == nil || v.Fields == nil {
return
}
for key, val := range v.Fields {
if val == nil {
delete(v.Fields, key)
}
}
}
// ToStruct converts a map[string]interface{} to a ptypes.Struct
func ToStruct(v map[string]interface{}) *st.Struct {
size := len(v)
if size == 0 {
return nil
}
fields := make(map[string]*st.Value, size)
for k, v := range v {
fields[k] = ToValue(v)
}
return &st.Struct{
Fields: fields,
}
}
// ToValue converts an interface{} to a ptypes.Value
func ToValue(v interface{}) *st.Value {
switch v := v.(type) {
case nil:
return nil
case bool:
return &st.Value{
Kind: &st.Value_BoolValue{
BoolValue: v,
},
}
case int:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case int8:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case int32:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case int64:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case uint:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case uint8:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case uint32:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case uint64:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case float32:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v),
},
}
case float64:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: v,
},
}
case string:
return &st.Value{
Kind: &st.Value_StringValue{
StringValue: v,
},
}
case error:
return &st.Value{
Kind: &st.Value_StringValue{
StringValue: v.Error(),
},
}
default:
// Fallback to reflection for other types
return toValue(reflect.ValueOf(v))
}
}
func toValue(v reflect.Value) *st.Value {
switch v.Kind() {
case reflect.Bool:
return &st.Value{
Kind: &st.Value_BoolValue{
BoolValue: v.Bool(),
},
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v.Int()),
},
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: float64(v.Uint()),
},
}
case reflect.Float32, reflect.Float64:
return &st.Value{
Kind: &st.Value_NumberValue{
NumberValue: 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([]*st.Value, size)
for i := 0; i < size; i++ {
values[i] = toValue(v.Index(i))
}
return &st.Value{
Kind: &st.Value_ListValue{
ListValue: &st.ListValue{
Values: values,
},
},
}
case reflect.Struct:
t := v.Type()
size := v.NumField()
if size == 0 {
return nil
}
fields := make(map[string]*st.Value, size)
for i := 0; i < size; i++ {
name := t.Field(i).Name
// Better way?
if len(name) > 0 && 'A' <= name[0] && name[0] <= 'Z' {
fields[name] = toValue(v.Field(i))
}
}
if len(fields) == 0 {
return nil
}
return &st.Value{
Kind: &st.Value_StructValue{
StructValue: &st.Struct{
Fields: fields,
},
},
}
case reflect.Map:
keys := v.MapKeys()
if len(keys) == 0 {
return nil
}
fields := make(map[string]*st.Value, len(keys))
for _, k := range keys {
if k.Kind() == reflect.String {
fields[k.String()] = toValue(v.MapIndex(k))
}
}
if len(fields) == 0 {
return nil
}
return &st.Value{
Kind: &st.Value_StructValue{
StructValue: &st.Struct{
Fields: fields,
},
},
}
default:
// Last resort
return &st.Value{
Kind: &st.Value_StringValue{
StringValue: fmt.Sprint(v),
},
}
}
}
// The following is modified from:
// https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/internal/protostruct/protostruct.go
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ToMap converts a pb.Struct to a map from strings to Go types.
// ToMap panics if s is invalid.
func ToMap(s *st.Struct) map[string]interface{} {
if s == nil {
return nil
}
m := map[string]interface{}{}
for k, v := range s.Fields {
m[k] = ToInterface(v)
}
return m
}
func ToInterface(v *st.Value) interface{} {
switch k := v.Kind.(type) {
case *st.Value_NullValue:
return nil
case *st.Value_NumberValue:
return k.NumberValue
case *st.Value_StringValue:
return k.StringValue
case *st.Value_BoolValue:
return k.BoolValue
case *st.Value_StructValue:
return ToMap(k.StructValue)
case *st.Value_ListValue:
s := make([]interface{}, len(k.ListValue.Values))
for i, e := range k.ListValue.Values {
s[i] = ToInterface(e)
}
return s
default:
panic("protostruct: unknown kind")
}
}