github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xorderedmap/xorderedmap.go (about) 1 package xorderedmap 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "github.com/Aoi-hosizora/ahlib/xreflect" 8 "reflect" 9 "strings" 10 "sync" 11 _ "unsafe" 12 ) 13 14 // OrderedMap represents a map whose keys are in ordered, it is implemented by go map to store kv data, and slice to store 15 // key order. Note that this is safe for concurrent use. 16 type OrderedMap struct { 17 // kv represents the inner dictionary 18 kv map[string]interface{} 19 20 // keys represents the inner key list in ordered 21 keys []string 22 23 // mu locks kv and keys 24 mu sync.RWMutex 25 } 26 27 // New creates an empty OrderedMap. 28 func New() *OrderedMap { 29 return &OrderedMap{ 30 kv: make(map[string]interface{}, 0), 31 keys: make([]string, 0), 32 } 33 } 34 35 // NewWithCap creates an empty OrderedMap with given capacity. 36 func NewWithCap(c int) *OrderedMap { 37 return &OrderedMap{ 38 kv: make(map[string]interface{}, c), 39 keys: make([]string, 0, c), 40 } 41 } 42 43 // Keys returns the keys in ordered. 44 func (l *OrderedMap) Keys() []string { 45 l.mu.RLock() 46 keys := make([]string, len(l.keys)) 47 copy(keys, l.keys) 48 l.mu.RUnlock() 49 return keys 50 } 51 52 // Values returns the values in ordered. 53 func (l *OrderedMap) Values() []interface{} { 54 l.mu.RLock() 55 values := make([]interface{}, len(l.keys)) 56 for idx, key := range l.keys { 57 values[idx] = l.kv[key] 58 } 59 l.mu.RUnlock() 60 return values 61 } 62 63 // Len returns the length of OrderedMap. 64 func (l *OrderedMap) Len() int { 65 l.mu.RLock() 66 length := len(l.keys) 67 l.mu.RUnlock() 68 return length 69 } 70 71 // Set sets a key-value pair to OrderedMap. Note that it does not change the order for the existed key. 72 func (l *OrderedMap) Set(key string, value interface{}) { 73 l.mu.Lock() 74 _, exist := l.kv[key] 75 l.kv[key] = value 76 if !exist { 77 l.keys = append(l.keys, key) 78 } 79 l.mu.Unlock() 80 } 81 82 // Has returns true if key exists. 83 func (l *OrderedMap) Has(key string) bool { 84 l.mu.RLock() 85 _, exist := l.kv[key] 86 l.mu.RUnlock() 87 return exist 88 } 89 90 // Get returns the value by key, returns false if the key not found. 91 func (l *OrderedMap) Get(key string) (interface{}, bool) { 92 l.mu.RLock() 93 value, exist := l.kv[key] 94 l.mu.RUnlock() 95 return value, exist 96 } 97 98 // GetOr returns the value by key, returns defaultValue if the key not found. 99 func (l *OrderedMap) GetOr(key string, defaultValue interface{}) interface{} { 100 l.mu.RLock() 101 value, exist := l.kv[key] 102 l.mu.RUnlock() 103 if !exist { 104 return defaultValue 105 } 106 return value 107 } 108 109 const ( 110 panicKeyNotFound = "xorderedmap: key `%s` not found" 111 ) 112 113 // MustGet returns the value by key, panics if the key not found. 114 func (l *OrderedMap) MustGet(key string) interface{} { 115 l.mu.RLock() 116 value, exist := l.kv[key] 117 l.mu.RUnlock() 118 if !exist { 119 panic(fmt.Sprintf(panicKeyNotFound, key)) 120 } 121 return value 122 } 123 124 // Remove removes the key-value pair by key, returns false if the key not found. 125 func (l *OrderedMap) Remove(key string) (interface{}, bool) { 126 l.mu.Lock() 127 defer l.mu.Unlock() 128 129 value, exist := l.kv[key] 130 if !exist { 131 return value, false 132 } 133 delete(l.kv, key) 134 135 targetIdx := -1 136 for idx, k := range l.keys { 137 if k == key { 138 targetIdx = idx 139 break 140 } 141 } 142 if targetIdx != -1 { 143 if targetIdx == len(l.keys)-1 { 144 l.keys = l.keys[:targetIdx] 145 } else { 146 l.keys = append(l.keys[:targetIdx], l.keys[targetIdx+1:]...) 147 } 148 } 149 150 return value, true 151 } 152 153 // Clear clears the OrderedMap. 154 func (l *OrderedMap) Clear() { 155 l.mu.Lock() 156 l.kv = make(map[string]interface{}) 157 l.keys = make([]string, 0) 158 l.mu.Unlock() 159 } 160 161 // MarshalJSON marshals OrderedMap to json bytes. 162 func (l *OrderedMap) MarshalJSON() ([]byte, error) { 163 l.mu.RLock() 164 defer l.mu.RUnlock() 165 166 tot := len(l.keys) 167 buf := &bytes.Buffer{} 168 buf.WriteRune('{') 169 for idx, field := range l.keys { 170 bs, err := json.Marshal(l.kv[field]) 171 if err != nil { 172 return []byte{}, err 173 } 174 buf.WriteRune('"') 175 buf.WriteString(field) 176 buf.WriteString(`":`) 177 buf.Write(bs) // "%s":%s 178 if idx < tot-1 { 179 buf.WriteRune(',') 180 } 181 } 182 buf.WriteRune('}') 183 184 return buf.Bytes(), nil 185 } 186 187 // CreateYamlMapSliceFunc represents a function which is used to create a yaml.MapSlice from a slice of kv pair (of 188 // [2]interface{} type), and it is used in OrderedMap.MarshalYAML. This can make OrderedMap support to marshal it to 189 // ordered yaml document. For more details, please visit https://blog.labix.org/2014/09/22/announcing-yaml-v2-for-go 190 // and https://github.com/go-yaml/yaml/issues/30#issuecomment-56246239. 191 // 192 // Example: 193 // // import "gopkg.in/yaml.v2" 194 // xorderedmap.CreateYamlMapSliceFunc = func(kvPairs [][2]interface{}) (interface{}, error) { 195 // slice := yaml.MapSlice{} 196 // for _, pair := range kvPairs { 197 // slice = append(slice, yaml.MapItem{Key: pair[0], Value: pair[1]}) 198 // } 199 // return slice, nil 200 // } 201 var CreateYamlMapSliceFunc func(kvPairs [][2]interface{}) (interface{}, error) 202 203 // MarshalYAML marshals the current OrderedMap to yaml supported object, you have to set CreateYamlMapSliceFunc before 204 // use MarshalYAML, otherwise the yaml.Marshal function will marshal to a map that is in no order. For more details, 205 // please visit xorderedmap.CreateYamlMapSliceFunc. 206 func (l *OrderedMap) MarshalYAML() (interface{}, error) { 207 if CreateYamlMapSliceFunc == nil { 208 l.mu.RLock() 209 m := make(map[string]interface{}, len(l.kv)) 210 for k, v := range l.kv { 211 m[k] = v 212 } 213 l.mu.RUnlock() 214 return m, nil 215 } 216 217 l.mu.RLock() 218 kvPairs := make([][2]interface{}, 0, len(l.kv)) 219 for _, k := range l.keys { 220 kvPairs = append(kvPairs, [2]interface{}{k, l.kv[k]}) 221 } 222 l.mu.RUnlock() 223 return CreateYamlMapSliceFunc(kvPairs) 224 } 225 226 // String returns the string in json format. 227 func (l *OrderedMap) String() string { 228 buf, err := l.MarshalJSON() 229 if err != nil { 230 return "" 231 } 232 return string(buf) 233 } 234 235 const ( 236 panicNilObject = "xorderedmap: nil object" 237 panicNonStructObject = "xorderedmap: non-struct or non-struct-pointer object" 238 ) 239 240 // FromInterface creates an OrderedMap from a struct (with json tag), panics if using nil or non-struct object. 241 func FromInterface(object interface{}) *OrderedMap { 242 if object == nil { 243 panic(panicNilObject) 244 } 245 typ := reflect.TypeOf(object) 246 val := reflect.ValueOf(object) 247 if typ.Kind() == reflect.Ptr { 248 typ = typ.Elem() 249 val = val.Elem() 250 } 251 if typ.Kind() != reflect.Struct { 252 panic(panicNonStructObject) 253 } 254 255 om := NewWithCap(typ.NumField()) 256 for i := 0; i < typ.NumField(); i++ { 257 field := typ.Field(i) 258 tag := field.Tag.Get("json") 259 if tag == "" { 260 tag = field.Name 261 } 262 sp := strings.SplitN(tag, ",", 2) 263 omitempty := len(sp) >= 2 && strings.TrimSpace(sp[1]) == "omitempty" // ignore null 264 265 // use json tag value as key name 266 key := strings.TrimSpace(sp[0]) 267 value := val.Field(i).Interface() 268 if key != "-" && (!omitempty || !xreflect.IsEmptyValue(value)) { 269 om.Set(key, value) 270 } 271 } 272 273 return om 274 }