github.com/goki/ki@v1.1.11/ki/props.go (about) 1 // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ki 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "log" 12 "reflect" 13 "strings" 14 15 "github.com/goki/ki/kit" 16 ) 17 18 // Props is the type used for holding generic properties -- the actual Go type 19 // is a mouthful and not very gui-friendly, and we need some special json methods 20 type Props map[string]interface{} 21 22 var KiT_Props = kit.Types.AddType(&Props{}, PropsProps) 23 24 var PropsProps = Props{ 25 "basic-type": true, // registers props as a basic type avail for type selection in creating property values -- many cases call for nested properties 26 } 27 28 // Set sets props value -- safely creates map 29 func (pr *Props) Set(key string, val interface{}) { 30 if *pr == nil { 31 *pr = make(Props) 32 } 33 (*pr)[key] = val 34 } 35 36 // Prop returns property of given key 37 func (pr Props) Prop(key string) interface{} { 38 return pr[key] 39 } 40 41 // Delete deletes props value at given key 42 func (pr Props) Delete(key string) { 43 delete(pr, key) 44 } 45 46 // SubProps returns a value that contains another props, or nil and false if 47 // it doesn't exist or isn't a Props 48 func SubProps(pr map[string]interface{}, key string) (Props, bool) { 49 sp, ok := pr[key] 50 if !ok { 51 return nil, false 52 } 53 spp, ok := sp.(Props) 54 if ok { 55 return spp, true 56 } 57 return nil, false 58 } 59 60 // SubTypeProps returns a value that contains another props, or nil and false if 61 // it doesn't exist or isn't a Props -- for TypeProps, uses locking 62 func SubTypeProps(pr map[string]interface{}, key string) (Props, bool) { 63 sp, ok := kit.TypeProp(pr, key) 64 if !ok { 65 return nil, false 66 } 67 spp, ok := sp.(Props) 68 if ok { 69 return spp, true 70 } 71 return nil, false 72 } 73 74 // SetPropStr is a convenience method for e.g., python wrapper that avoids need to deal 75 // directly with props interface{} type 76 func SetPropStr(pr Props, key, val string) { 77 pr[key] = val 78 } 79 80 // SetSubProps is a convenience method for e.g., python wrapper that avoids need to deal 81 // directly with props interface{} type 82 func SetSubProps(pr Props, key string, sp Props) { 83 pr[key] = sp 84 } 85 86 // special key prefix indicating type info 87 var struTypeKey = "__type:" 88 89 // special key prefix for enums 90 var enumTypeKey = "__enum:" 91 92 // BlankProp is an empty property, for when there isn't any need for the value 93 type BlankProp struct{} 94 95 // PropStruct is a struct of Name and Value, for use in a PropSlice to hold 96 // properties that require order information (maps do not retain any order) 97 type PropStruct struct { 98 Name string 99 Value interface{} 100 } 101 102 // PropSlice is a slice of PropStruct, for when order is important within a 103 // subset of properties (maps do not retain order) -- can set the value of a 104 // property to a PropSlice to create an ordered list of property values. 105 type PropSlice []PropStruct 106 107 // ElemLabel satisfies the gi.SliceLabeler interface to provide labels for slice elements 108 func (ps *PropSlice) ElemLabel(idx int) string { 109 return (*ps)[idx].Name 110 } 111 112 // SliceProps returns a value that contains a PropSlice, or nil and false if it doesn't 113 // exist or isn't a PropSlice 114 func SliceProps(pr map[string]interface{}, key string) (PropSlice, bool) { 115 sp, ok := pr[key] 116 if !ok { 117 return nil, false 118 } 119 spp, ok := sp.(PropSlice) 120 if ok { 121 return spp, true 122 } 123 return nil, false 124 } 125 126 // SliceTypeProps returns a value that contains a PropSlice, or nil and false if it doesn't 127 // exist or isn't a PropSlice -- for TypeProps, uses locking 128 func SliceTypeProps(pr map[string]interface{}, key string) (PropSlice, bool) { 129 sp, ok := kit.TypeProp(pr, key) 130 if !ok { 131 return nil, false 132 } 133 spp, ok := sp.(PropSlice) 134 if ok { 135 return spp, true 136 } 137 return nil, false 138 } 139 140 // CopyProps copies properties from source to destination map. If deepCopy 141 // is true, then any values that are Props or PropSlice are copied too 142 // *dest can be nil, in which case it is created. 143 func CopyProps(dest *map[string]interface{}, src map[string]interface{}, deepCopy bool) { 144 if *dest == nil { 145 *dest = make(Props, len(src)) 146 } 147 for key, val := range src { 148 if deepCopy { 149 if pv, ok := val.(map[string]interface{}); ok { 150 var nval Props 151 nval.CopyFrom(pv, deepCopy) 152 (*dest)[key] = nval 153 continue 154 } else if pv, ok := val.(Props); ok { 155 var nval Props 156 nval.CopyFrom(pv, deepCopy) 157 (*dest)[key] = nval 158 continue 159 } else if pv, ok := val.(PropSlice); ok { 160 var nval PropSlice 161 nval.CopyFrom(pv, deepCopy) 162 (*dest)[key] = nval 163 continue 164 } 165 } 166 (*dest)[key] = val 167 } 168 } 169 170 // CopyFrom copies properties from source to receiver destination map. If deepCopy 171 // is true, then any values that are Props or PropSlice are copied too 172 // *dest can be nil, in which case it is created. 173 func (dest *Props) CopyFrom(src map[string]interface{}, deepCopy bool) { 174 CopyProps((*map[string]interface{})(dest), src, deepCopy) 175 } 176 177 // CopyFrom copies properties from source to destination propslice. If deepCopy 178 // is true, then any values that are Props or PropSlice are copied too 179 // *dest can be nil, in which case it is created. 180 func (dest *PropSlice) CopyFrom(src PropSlice, deepCopy bool) { 181 if *dest == nil { 182 *dest = make(PropSlice, len(src)) 183 } 184 for i, val := range src { 185 if deepCopy { 186 if pv, ok := val.Value.(map[string]interface{}); ok { 187 var nval Props 188 CopyProps((*map[string]interface{})(&nval), pv, deepCopy) 189 (*dest)[i] = PropStruct{Name: val.Name, Value: nval} 190 continue 191 } else if pv, ok := val.Value.(Props); ok { 192 var nval Props 193 CopyProps((*map[string]interface{})(&nval), pv, deepCopy) 194 (*dest)[i] = PropStruct{Name: val.Name, Value: nval} 195 continue 196 } else if pv, ok := val.Value.(PropSlice); ok { 197 var nval PropSlice 198 nval.CopyFrom(pv, deepCopy) 199 (*dest)[i] = PropStruct{Name: val.Name, Value: nval} 200 continue 201 } 202 } 203 (*dest)[i] = src[i] 204 } 205 } 206 207 // MarshalJSON saves the type information for each struct used in props, as a 208 // separate key with the __type: prefix -- this allows the Unmarshal to 209 // create actual types 210 func (p Props) MarshalJSON() ([]byte, error) { 211 nk := len(p) 212 b := make([]byte, 0, nk*100+20) 213 if nk == 0 { 214 b = append(b, []byte("null")...) 215 return b, nil 216 } 217 b = append(b, []byte("{")...) 218 cnt := 0 219 var err error 220 for key, val := range p { 221 vt := kit.NonPtrType(reflect.TypeOf(val)) 222 vk := vt.Kind() 223 if vk == reflect.Struct { 224 knm := kit.Types.TypeName(vt) 225 tstr := fmt.Sprintf("\"%v%v\": \"%v\",", struTypeKey, key, knm) 226 b = append(b, []byte(tstr)...) 227 } 228 kstr := fmt.Sprintf("\"%v\": ", key) 229 b = append(b, []byte(kstr)...) 230 231 var kb []byte 232 kb, err = json.Marshal(val) 233 if err != nil { 234 log.Printf("error doing json.Marshall from val: %v\n%v\n", val, err) 235 log.Printf("output to point of error: %v\n", string(b)) 236 } else { 237 if vk >= reflect.Int && vk <= reflect.Uint64 && kit.Enums.TypeRegistered(vt) { 238 knm := kit.Types.TypeName(vt) 239 kb, _ = json.Marshal(val) 240 estr := fmt.Sprintf("\"%v(%v)%v\"", enumTypeKey, knm, string(bytes.Trim(kb, "\""))) 241 b = append(b, []byte(estr)...) 242 } else { 243 b = append(b, kb...) 244 } 245 } 246 if cnt < nk-1 { 247 b = append(b, []byte(",")...) 248 } 249 cnt++ 250 } 251 b = append(b, []byte("}")...) 252 // fmt.Printf("json out: %v\n", string(b)) 253 return b, nil 254 } 255 256 // UnmarshalJSON parses the type information in the map to restore actual 257 // objects -- this is super inefficient and really needs a native parser, but 258 // props are likely to be relatively small 259 func (p *Props) UnmarshalJSON(b []byte) error { 260 // fmt.Printf("json in: %v\n", string(b)) 261 if bytes.Equal(b, []byte("null")) { 262 *p = nil 263 return nil 264 } 265 266 // load into a temporary map and then process 267 tmp := make(map[string]interface{}) 268 err := json.Unmarshal(b, &tmp) 269 if err != nil { 270 return err 271 } 272 273 *p = make(Props, len(tmp)) 274 275 // create all the structure objects from the list -- have to do this first to get all 276 // the structs made b/c the order is random.. 277 for key, val := range tmp { 278 if strings.HasPrefix(key, struTypeKey) { 279 pkey := strings.TrimPrefix(key, struTypeKey) 280 rval := tmp[pkey] 281 tn := val.(string) 282 typ := kit.Types.Type(tn) 283 if typ == nil { 284 log.Printf("ki.Props: cannot load struct of type %v -- not registered in kit.Types\n", tn) 285 continue 286 } 287 if IsKi(typ) { // note: not really a good idea to store ki's in maps, but.. 288 kival := NewOfType(typ) 289 InitNode(kival) 290 if kival != nil { 291 // fmt.Printf("stored new ki of type %v in key: %v\n", typ.String(), pkey) 292 tmpb, _ := json.Marshal(rval) // string rep of this 293 err = kival.ReadJSON(bytes.NewReader(tmpb)) 294 if err != nil { 295 log.Printf("ki.Props failed to load Ki struct of type %v with error: %v\n", typ.String(), err) 296 } 297 (*p)[pkey] = kival 298 } 299 } else { 300 stval := reflect.New(typ).Interface() 301 // fmt.Printf("stored new struct of type %v in key: %v\n", typ.String(), pkey) 302 tmpb, _ := json.Marshal(rval) // string rep of this 303 err = json.Unmarshal(tmpb, stval) 304 if err != nil { 305 log.Printf("ki.Props failed to load struct of type %v with error: %v\n", typ.String(), err) 306 } 307 (*p)[pkey] = reflect.ValueOf(stval).Elem().Interface() 308 } 309 } 310 } 311 312 // now can re-iterate 313 for key, val := range tmp { 314 if strings.HasPrefix(key, struTypeKey) { 315 continue 316 } 317 if _, ok := (*p)[key]; ok { // already created -- was a struct -- skip 318 continue 319 } 320 // look for sub-maps, make them props.. 321 if _, ok := val.(map[string]interface{}); ok { 322 // fmt.Printf("stored new Props map in key: %v\n", key) 323 subp := Props{} 324 tmpb, _ := json.Marshal(val) // string rep of this 325 err = json.Unmarshal(tmpb, &subp) 326 if err != nil { 327 log.Printf("ki.Props failed to load sub-Props with error: %v\n", err) 328 } 329 (*p)[key] = subp 330 } else { // straight copy 331 if sval, ok := val.(string); ok { 332 if strings.HasPrefix(sval, enumTypeKey) { 333 tn := strings.TrimPrefix(sval, enumTypeKey) 334 rpi := strings.Index(tn, ")") 335 str := tn[rpi+1:] 336 tn = tn[1:rpi] 337 etyp := kit.Enums.Enum(tn) 338 if etyp != nil { 339 eval := kit.EnumIfaceFromString(str, etyp) 340 (*p)[key] = eval 341 // fmt.Printf("decoded enum typ %v into actual value: %v from %v\n", etyp.String(), eval, str) 342 } else { 343 (*p)[key] = str 344 } 345 continue 346 } 347 } 348 (*p)[key] = val 349 } 350 } 351 352 return nil 353 }