/
jsonc_marshal.go
151 lines (129 loc) · 3.99 KB
/
jsonc_marshal.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
package generic
import (
"encoding/json"
"errors"
"fmt"
"reflect"
)
func JsonFromObject(o interface{}) ([]byte, error) {
// is it a pointer of struct?
structValue, ok := pointerOfStruct(&o)
if ok == false {
return nil, errors.New("input is not a pointer of struct")
}
// Convert the struct to a map
mapValue, err := mapFromStruct(structValue)
if err != nil {
return nil, err
}
// Marshal the map to a json string
j, err := json.Marshal(mapValue)
if err != nil {
return nil, err
}
return j, nil
}
/*
Maps a given struct to a map.
Handles `json:...` tags and `jsonc:...` tags for flattening.
Returns the map or an error
*/
func mapFromStruct(structValue *reflect.Value) (*map[string]interface{}, error) {
targetMap := make(map[string]interface{})
structType := structValue.Type()
// Iterate over the struct fields
for i := 0; i < structValue.NumField(); i++ {
fieldType := structType.Field(i)
fieldValue := structValue.Field(i)
jsonCTag := getJsonTag(&fieldType, "jsonc")
// If no `jsonc` tag: Just add the field into the map - maybe has `json`-Tags
if jsonCTag == nil {
insertTaggedFieldIntoMap(&targetMap, &fieldType, &fieldValue)
} else {
switch jsonCTag.Name {
// `jsonc:"flat"` -> Must be a map. Flatten it to the target Map.
case "flat":
err := insertReflectMapIntoMap(&targetMap, &fieldValue)
if err != nil {
return nil, errors.New(fmt.Sprintf("error: on collection %s: %s", fieldType.Name, err.Error()))
}
break
// `jsonc:"collection"` -> Must be a slice. Handle each element recursive with `mapFromStruct`
case "collection":
err := handleCollection(&targetMap, &fieldType, &fieldValue)
if err != nil {
return nil, errors.New(fmt.Sprintf("error: on collection %s: %s", fieldType.Name, err.Error()))
}
break
}
}
}
return &targetMap, nil
}
/*
* Handles `json:"collection"`
* Must be a slice. Then handle every slice element as a struct and call it with `mapFromStruct`. Then insert this new
* map into the `targetMap`.
*/
func handleCollection(targetMapPtr *map[string]interface{}, fieldType *reflect.StructField, fieldValue *reflect.Value) error {
if fieldValue.Kind() != reflect.Slice {
return errors.New("is not a slice")
}
slice := make([]map[string]interface{}, fieldValue.Len())
for i := 0; i < fieldValue.Len(); i++ {
structItem := fieldValue.Index(i)
mapItem, err := mapFromStruct(&structItem)
if err != nil {
return errors.New(fmt.Sprintf("error: Can not convert item %d: %s", i, err.Error()))
}
slice[i] = *mapItem
}
v := reflect.ValueOf(slice)
insertTaggedFieldIntoMap(targetMapPtr, fieldType, &v)
return nil
}
// `fieldValue` must be a Map. Then insert every element into the target map.
func insertReflectMapIntoMap(targetMapPtr *map[string]interface{}, fieldValue *reflect.Value) error {
targetMap := *targetMapPtr
if fieldValue.Kind() != reflect.Map {
return errors.New("is not a map")
}
// flat process
iter := fieldValue.MapRange()
for iter.Next() {
targetMap[iter.Key().String()] = iter.Value().Interface()
}
return nil
}
/*
* Handles a field and its value based on the given tags and insert it into the given map.
* eg: A field "A -> "foo" without any tag, will result in map["A"] -> "foo"
A field "A -> "foo" `json:customA` will result in map["customA"] -> "foo"
*/
func insertTaggedFieldIntoMap(targetMapPtr *map[string]interface{}, fieldType *reflect.StructField, fieldValue *reflect.Value) {
tag := getJsonTag(fieldType, "json")
targetMap := *targetMapPtr
// no tag -> original name
if tag == nil {
targetMap[fieldType.Name] = fieldValue.Interface()
return
}
// - -> omit value
if tag.Name == "-" {
return
}
// OmitEmpty and is empty -> omit value
if tag.OmitEmpty && isEmptyValue(fieldValue) {
return
} else {
targetMap[tag.Name] = fieldValue.Interface()
}
}
func isEmptyValue(v *reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
default:
return !v.IsValid() || v.IsZero()
}
}