forked from apache/dubbo-go
/
map.go
208 lines (184 loc) · 5.53 KB
/
map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package generalizer
import (
"reflect"
"strings"
"sync"
"time"
)
import (
hessian "github.com/apache/dubbo-go-hessian2"
"github.com/mitchellh/mapstructure"
perrors "github.com/pkg/errors"
)
import (
"github.com/xd-luqiang/dubbo-go/v3/common/logger"
"github.com/xd-luqiang/dubbo-go/v3/protocol/dubbo/hessian2"
)
var (
mapGeneralizer Generalizer
mapGeneralizerOnce sync.Once
)
func GetMapGeneralizer() Generalizer {
mapGeneralizerOnce.Do(func() {
mapGeneralizer = &MapGeneralizer{}
})
return mapGeneralizer
}
type MapGeneralizer struct{}
func (g *MapGeneralizer) Generalize(obj interface{}) (gobj interface{}, err error) {
gobj = objToMap(obj)
return
}
func (g *MapGeneralizer) Realize(obj interface{}, typ reflect.Type) (interface{}, error) {
newobj := reflect.New(typ).Interface()
err := mapstructure.Decode(obj, newobj)
if err != nil {
return nil, perrors.Errorf("realizing map failed, %v", err)
}
return reflect.ValueOf(newobj).Elem().Interface(), nil
}
func (g *MapGeneralizer) GetType(obj interface{}) (typ string, err error) {
typ, err = hessian2.GetJavaName(obj)
// no error or error is not NilError
if err == nil || err != hessian2.NilError {
return
}
typ = "java.lang.Object"
if err == hessian2.NilError {
logger.Debugf("the type of nil object couldn't be inferred, use the default value(\"%s\")", typ)
return
}
logger.Debugf("the type of object(=%T) couldn't be recognized as a POJO, use the default value(\"%s\")", obj, typ)
return
}
// objToMap converts an object(interface{}) to a map
func objToMap(obj interface{}) interface{} {
if obj == nil {
return obj
}
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
// if obj is a POJO, get the struct from the pointer (if it is a pointer)
pojo, isPojo := obj.(hessian.POJO)
if isPojo {
for t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
}
switch t.Kind() {
case reflect.Struct:
result := make(map[string]interface{}, t.NumField())
if isPojo {
result["class"] = pojo.JavaClassName()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
kind := value.Kind()
if !value.CanInterface() {
logger.Debugf("objToMap for %v is skipped because it couldn't be converted to interface", field)
continue
}
valueIface := value.Interface()
switch kind {
case reflect.Ptr:
if value.IsNil() {
setInMap(result, field, nil)
continue
}
setInMap(result, field, objToMap(valueIface))
case reflect.Struct, reflect.Slice, reflect.Map:
if isPrimitive(valueIface) {
logger.Warnf("\"%s\" is primitive. The application may crash if it's transferred between "+
"systems implemented by different languages, e.g. dubbo-go <-> dubbo-java. We recommend "+
"you represent the object by basic types, like string.", value.Type())
setInMap(result, field, valueIface)
continue
}
setInMap(result, field, objToMap(valueIface))
default:
setInMap(result, field, valueIface)
}
}
return result
case reflect.Array, reflect.Slice:
value := reflect.ValueOf(obj)
newTemps := make([]interface{}, 0, value.Len())
for i := 0; i < value.Len(); i++ {
newTemp := objToMap(value.Index(i).Interface())
newTemps = append(newTemps, newTemp)
}
return newTemps
case reflect.Map:
newTempMap := make(map[interface{}]interface{}, v.Len())
iter := v.MapRange()
for iter.Next() {
if !iter.Value().CanInterface() {
continue
}
key := iter.Key()
mapV := iter.Value().Interface()
newTempMap[mapKey(key)] = objToMap(mapV)
}
return newTempMap
case reflect.Ptr:
return objToMap(v.Elem().Interface())
default:
return obj
}
}
// mapKey converts the map key to interface type
func mapKey(key reflect.Value) interface{} {
switch key.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8,
reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Float32,
reflect.Float64, reflect.String:
return key.Interface()
default:
name := key.String()
if name == "class" {
panic(`"class" is a reserved keyword`)
}
return name
}
}
// setInMap sets the struct into the map using the tag or the name of the struct as the key
func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) {
result = m
if tagName := structField.Tag.Get("m"); tagName == "" {
result[toUnexport(structField.Name)] = value
} else {
result[tagName] = value
}
return
}
// toUnexport is to lower the first letter
func toUnexport(a string) string {
return strings.ToLower(a[:1]) + a[1:]
}
// isPrimitive determines if the object is primitive
func isPrimitive(obj interface{}) bool {
if _, ok := obj.(time.Time); ok {
return true
}
return false
}