github.com/viant/toolbox@v0.34.5/data/map.go (about) 1 package data 2 3 import ( 4 "bytes" 5 "github.com/viant/toolbox" 6 "log" 7 "strings" 8 "time" 9 ) 10 11 //Map types is an alias type to map[string]interface{} with extended behaviours 12 type Map map[string]interface{} 13 14 //Udf represents a user defined function used to transform data. 15 type Udf func(interface{}, Map) (interface{}, error) 16 17 //Put puts key value into the map. 18 func (s *Map) Put(key string, value interface{}) { 19 (*s)[key] = value 20 } 21 22 //Delete removes the supplied keys, it supports key of path expression with dot i.e. request.method 23 func (s *Map) Delete(keys ...string) { 24 for _, key := range keys { 25 if !strings.Contains(key, ".") { 26 delete(*s, key) 27 continue 28 } 29 keyParts := strings.Split(key, ".") 30 var temp = *s 31 for i, part := range keyParts { 32 if temp == nil { 33 break 34 } 35 isLasPart := i+1 == len(keyParts) 36 if isLasPart { 37 delete(temp, part) 38 } else if temp[part] != nil && toolbox.IsMap(temp[part]) { 39 subMap := toolbox.AsMap(temp[part]) 40 temp = Map(subMap) 41 } else { 42 break 43 } 44 } 45 } 46 } 47 48 //Replace replaces supplied key/path with corresponding value 49 func (s *Map) Replace(key, val string) { 50 if !strings.Contains(key, ".") { 51 (*s)[key] = val 52 return 53 } 54 keyParts := strings.Split(key, ".") 55 var temp = *s 56 for i, part := range keyParts { 57 if temp == nil { 58 break 59 } 60 isLasPart := i+1 == len(keyParts) 61 if isLasPart { 62 temp[part] = val 63 } else if temp[part] != nil && toolbox.IsMap(temp[part]) { 64 subMap := toolbox.AsMap(temp[part]) 65 temp = Map(subMap) 66 } else { 67 break 68 } 69 } 70 71 } 72 73 //Has returns true if the provided key is present 74 func (s *Map) Has(key string) bool { 75 _, found := (*s)[key] 76 return found 77 } 78 79 //Get returns a value for provided key 80 func (s *Map) Get(key string) interface{} { 81 if result, found := (*s)[key]; found { 82 return result 83 } 84 return nil 85 } 86 87 /* 88 GetValue returns value for provided expression. 89 The expression uses dot (.) to denote nested data structure. 90 The following expression as supported 91 1) <-key shift 92 2) ++key pre increment 93 3) key++ post increment 94 4) $key reference access 95 */ 96 func (s *Map) GetValue(expr string) (interface{}, bool) { 97 if expr == "" { 98 return nil, false 99 } 100 if strings.HasPrefix(expr, "{") && strings.HasSuffix(expr, "}") { 101 expr = expr[1 : len(expr)-1] 102 } 103 104 isShiftOperation := strings.HasPrefix(expr, "<-") 105 if isShiftOperation { 106 expr = string(expr[2:]) 107 } 108 109 isPostIncrementOperation := strings.HasSuffix(expr, "++") 110 if isPostIncrementOperation { 111 expr = string(expr[:len(expr)-2]) 112 } 113 114 isPreIncrementOperation := strings.HasPrefix(expr, "++") 115 if isPreIncrementOperation { 116 expr = string(expr[2:]) 117 } 118 119 isReference := strings.HasPrefix(expr, "$") 120 if isReference { 121 expr = string(expr[1:]) 122 expr = s.GetString(expr) 123 if expr == "" { 124 return nil, false 125 } 126 } 127 128 state := *s 129 130 if strings.Contains(expr, ".") || strings.HasSuffix(expr, "]") { 131 fragments := strings.Split(expr, ".") 132 for i, fragment := range fragments { 133 var index interface{} 134 arrayIndexPosition := strings.Index(fragment, "[") 135 if arrayIndexPosition != -1 { 136 arrayEndPosition := strings.Index(fragment, "]") 137 if arrayEndPosition > arrayIndexPosition && arrayEndPosition < len(fragment) { 138 arrayIndex := string(fragment[arrayIndexPosition+1 : arrayEndPosition]) 139 index = arrayIndex 140 fragment = string(fragment[:arrayIndexPosition]) 141 } 142 } 143 isLast := i+1 == len(fragments) 144 145 hasKey := state.Has(fragment) 146 if !hasKey { 147 return nil, false 148 } 149 150 var candidate = state.Get(fragment) 151 if !isLast && candidate == nil { 152 return nil, false 153 } 154 155 if index != nil { 156 157 if intIndex, err := toolbox.ToInt(index); err == nil { 158 if !toolbox.IsSlice(candidate) { 159 return nil, false 160 } 161 var aSlice = toolbox.AsSlice(candidate) 162 if intIndex >= len(aSlice) { 163 return nil, false 164 } 165 if intIndex < len(aSlice) { 166 candidate = aSlice[intIndex] 167 } else { 168 candidate = nil 169 } 170 } else if textIndex, ok := index.(string); ok { 171 if !toolbox.IsMap(candidate) { 172 return nil, false 173 } 174 aMap := toolbox.AsMap(candidate) 175 if candidate, ok = aMap[textIndex]; !ok { 176 return nil, false 177 } 178 } else { 179 return nil, false 180 } 181 182 if isLast { 183 return candidate, true 184 } 185 } 186 187 if isLast { 188 expr = fragment 189 continue 190 } 191 if toolbox.IsMap(candidate) { 192 newState := toolbox.AsMap(candidate) 193 if newState != nil { 194 state = newState 195 } 196 } else { 197 value, _ := state.GetValue(fragment) 198 if f, ok := value.(func(key string) interface{}); ok { 199 return f(fragments[i+1]), true 200 } 201 return nil, false 202 } 203 } 204 } 205 206 if state.Has(expr) { 207 var result = state.Get(expr) 208 if isPostIncrementOperation { 209 state.Put(expr, toolbox.AsInt(result)+1) 210 } else if isPreIncrementOperation { 211 result = toolbox.AsInt(result) + 1 212 state.Put(expr, result) 213 } else if isShiftOperation { 214 215 aCollection := state.GetCollection(expr) 216 if len(*aCollection) == 0 { 217 return nil, false 218 } 219 var result = (*aCollection)[0] 220 var newCollection = (*aCollection)[1:] 221 state.Put(expr, &newCollection) 222 return result, true 223 } 224 if f, ok := result.(func() interface{}); ok { 225 return f(), true 226 } 227 return result, true 228 } 229 return nil, false 230 } 231 232 /* 233 Set value sets value in the map for the supplied expression. 234 The expression uses dot (.) to denote nested data structure. For instance the following expression key1.subKey1.attr1 235 Would take or create map for key1, followied bu takeing or creating antother map for subKey1 to set attr1 key with provided value 236 The following expression as supported 237 1) $key reference - the final key is determined from key's content. 238 2) ->key push expression to append value at the end of the slice 239 */ 240 func (s *Map) SetValue(expr string, value interface{}) { 241 if expr == "" { 242 return 243 } 244 if value == nil { 245 return 246 } 247 if strings.Index(expr, "$") != -1 { 248 expr = s.ExpandAsText(expr) 249 } 250 251 state := *s 252 isPushOperation := strings.HasPrefix(expr, "->") 253 if isPushOperation { 254 expr = string(expr[2:]) 255 } 256 if strings.HasPrefix(expr, "{") && strings.HasSuffix(expr, "}") { 257 expr = expr[1 : len(expr)-1] 258 } 259 260 if strings.Contains(expr, ".") { 261 fragments := strings.Split(expr, ".") 262 nodePath := strings.Join(fragments[:len(fragments)-1], ".") 263 if node, ok := s.GetValue(nodePath); ok && toolbox.IsMap(node) { 264 if _, writable := node.(map[string]interface{}); !writable { 265 node = Map(toolbox.AsMap(node)) 266 s.SetValue(nodePath, node) 267 } 268 expr = fragments[len(fragments)-1] 269 state = Map(toolbox.AsMap(node)) 270 } else { 271 for i, fragment := range fragments { 272 isLast := i+1 == len(fragments) 273 if isLast { 274 expr = fragment 275 } else { 276 subState := state.GetMap(fragment) 277 if subState == nil { 278 subState = NewMap() 279 state.Put(fragment, subState) 280 } 281 state = subState 282 } 283 } 284 } 285 } 286 287 if isPushOperation { 288 collection := state.GetCollection(expr) 289 if collection == nil { 290 collection = NewCollection() 291 state.Put(expr, collection) 292 } 293 collection.Push(value) 294 state.Put(expr, collection) 295 return 296 } 297 state.Put(expr, value) 298 } 299 300 //Apply copies all elements of provided map to this map. 301 func (s *Map) Apply(source map[string]interface{}) { 302 for k, v := range source { 303 (*s)[k] = v 304 } 305 } 306 307 //GetString returns value for provided key as string. 308 func (s *Map) GetString(key string) string { 309 if result, found := (*s)[key]; found { 310 return toolbox.AsString(result) 311 } 312 return "" 313 } 314 315 //GetInt returns value for provided key as int. 316 func (s *Map) GetInt(key string) int { 317 if result, found := (*s)[key]; found { 318 return toolbox.AsInt(result) 319 } 320 return 0 321 } 322 323 //GetFloat returns value for provided key as float64. 324 func (s *Map) GetFloat(key string) float64 { 325 if result, found := (*s)[key]; found { 326 return toolbox.AsFloat(result) 327 } 328 return 0.0 329 } 330 331 //GetBoolean returns value for provided key as boolean. 332 func (s *Map) GetBoolean(key string) bool { 333 if result, found := (*s)[key]; found { 334 return toolbox.AsBoolean(result) 335 } 336 return false 337 } 338 339 //GetCollection returns value for provided key as collection pointer. 340 func (s *Map) GetCollection(key string) *Collection { 341 if result, found := (*s)[key]; found { 342 collectionPointer, ok := result.(*Collection) 343 if ok { 344 return collectionPointer 345 } 346 347 aSlice, ok := result.([]interface{}) 348 collection := Collection(aSlice) 349 if ok { 350 return &collection 351 } 352 if !toolbox.IsSlice(result) { 353 return nil 354 } 355 aSlice = toolbox.AsSlice(result) 356 collection = Collection(aSlice) 357 return &collection 358 } 359 return nil 360 } 361 362 //GetMap returns value for provided key as map. 363 func (s *Map) GetMap(key string) Map { 364 if result, found := (*s)[key]; found { 365 aMap, ok := result.(Map) 366 if ok { 367 return aMap 368 } 369 aMap, ok = result.(map[string]interface{}) 370 if ok { 371 return aMap 372 } 373 var result = toolbox.AsMap(result) 374 (*s)[key] = result 375 return result 376 } 377 return nil 378 } 379 380 //Range iterates every key, value pair of this map, calling supplied callback as long it does return true. 381 func (s *Map) Range(callback func(k string, v interface{}) (bool, error)) error { 382 for k, v := range *s { 383 next, err := callback(k, v) 384 if err != nil { 385 return err 386 } 387 if !next { 388 break 389 } 390 } 391 return nil 392 } 393 394 //Clones create a clone of this map. 395 func (s *Map) Clone() Map { 396 var result = NewMap() 397 for key, value := range *s { 398 if aMap, casted := value.(Map); casted { 399 result[key] = aMap.Clone() 400 } else { 401 result[key] = value 402 } 403 } 404 return result 405 } 406 407 //Returns a map that can be encoded by json or other decoders. 408 //Since a map can store a function, it filters out any non marshalable types. 409 func (s *Map) AsEncodableMap() map[string]interface{} { 410 var result = make(map[string]interface{}) 411 for k, v := range *s { 412 if v == nil { 413 continue 414 } 415 result[k] = asEncodableValue(v) 416 } 417 return result 418 } 419 420 //asEncodableValue returns all non func values or func() literal for function. 421 func asEncodableValue(v interface{}) interface{} { 422 if v == nil { 423 return nil 424 } 425 value := v 426 if toolbox.IsFunc(v) { 427 return "func()" 428 } 429 if toolbox.IsMap(v) { 430 var aMap = Map(toolbox.AsMap(v)) 431 value = aMap.AsEncodableMap() 432 } else if toolbox.IsSlice(v) { 433 var targetSlice = make([]interface{}, 0) 434 var sourceSlice = toolbox.AsSlice(v) 435 for _, item := range sourceSlice { 436 targetSlice = append(targetSlice, asEncodableValue(item)) 437 } 438 value = targetSlice 439 } else if toolbox.IsString(v) || toolbox.IsInt(v) || toolbox.IsFloat(v) { 440 value = v 441 } else { 442 value = toolbox.AsString(v) 443 } 444 return value 445 } 446 447 func hasGenericKeys(aMap map[string]interface{}) bool { 448 for k := range aMap { 449 if strings.HasPrefix(k, "$AsInt") || strings.HasPrefix(k, "$AsFloat") || strings.HasPrefix(k, "$AsBool") { 450 return true 451 } 452 } 453 return false 454 } 455 456 //Expand expands provided value of any type with dollar sign expression/ 457 func (s *Map) Expand(source interface{}) interface{} { 458 switch value := source.(type) { 459 case bool, []byte, int, uint, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, time.Time: 460 return source 461 case *[]byte: 462 return s.expandExpressions(string(*value)) 463 case *string: 464 if value == nil { 465 return "" 466 } 467 return s.expandExpressions(*value) 468 case string: 469 return s.expandExpressions(value) 470 case map[string]interface{}: 471 if hasGenericKeys(value) { 472 result := make(map[interface{}]interface{}) 473 for k, v := range value { 474 var key = s.Expand(k) 475 result[key] = s.Expand(v) 476 } 477 return result 478 } 479 resultMap := make(map[string]interface{}) 480 for k, v := range value { 481 var key = s.ExpandAsText(k) 482 var expanded = s.Expand(v) 483 484 if key == "..." { 485 if expanded != nil && toolbox.IsMap(expanded) { 486 for key, value := range toolbox.AsMap(expanded) { 487 resultMap[key] = value 488 } 489 continue 490 } 491 } 492 resultMap[key] = expanded 493 } 494 return resultMap 495 case map[interface{}]interface{}: 496 var resultMap = make(map[interface{}]interface{}) 497 for k, v := range value { 498 var key = s.Expand(k) 499 var expanded = s.Expand(v) 500 if key == "..." { 501 if expanded != nil && toolbox.IsMap(expanded) { 502 for key, value := range toolbox.AsMap(expanded) { 503 resultMap[key] = value 504 } 505 continue 506 } 507 } 508 resultMap[key] = expanded 509 } 510 return resultMap 511 case []interface{}: 512 var resultSlice = make([]interface{}, len(value)) 513 for i, value := range value { 514 resultSlice[i] = s.Expand(value) 515 } 516 return resultSlice 517 default: 518 519 if source == nil { 520 return nil 521 } 522 523 if toolbox.IsMap(source) { 524 switch aMap := value.(type) { 525 case map[string]interface{}: 526 return s.Expand(aMap) 527 case map[interface{}]interface{}: 528 return s.Expand(aMap) 529 default: 530 return s.Expand(toolbox.AsMap(value)) 531 } 532 533 } else if toolbox.IsSlice(source) { 534 return s.Expand(toolbox.AsSlice(value)) 535 } else if toolbox.IsStruct(value) { 536 aMap := toolbox.AsMap(value) 537 return s.Expand(aMap) 538 } else if value != nil { 539 return s.Expand(toolbox.AsString(value)) 540 } 541 } 542 return source 543 } 544 545 //ExpandAsText expands all matching expressions starting with dollar sign ($) 546 func (s *Map) ExpandAsText(text string) string { 547 result := s.expandExpressions(text) 548 if toolbox.IsSlice(result) || toolbox.IsMap(result) { 549 buf := new(bytes.Buffer) 550 err := toolbox.NewJSONEncoderFactory().Create(buf).Encode(result) 551 if err == nil { 552 return buf.String() 553 } 554 } 555 if text, ok := result.(string); ok || result == nil { 556 return text 557 } 558 return toolbox.AsString(result) 559 } 560 561 func (s *Map) evaluateUDF(candidate interface{}, argument interface{}) (interface{}, bool) { 562 var canExpandAll = true 563 564 if toolbox.IsString(argument) { 565 var expandable = strings.TrimSpace(toolbox.AsString(argument)) 566 Parse(expandable, func(expression string, udf bool, argument interface{}) (interface{}, bool) { 567 if _, has := s.GetValue(string(expression[1:])); !has { 568 canExpandAll = false 569 } 570 return nil, false 571 }) 572 } 573 574 if !canExpandAll { 575 return nil, false 576 } 577 udf, ok := candidate.(func(interface{}, Map) (interface{}, error)) 578 if !ok { 579 return nil, false 580 } 581 582 expandedArgument := s.expandArgumentsExpressions(argument) 583 if toolbox.IsString(expandedArgument) { 584 expandedText := toolbox.AsString(expandedArgument) 585 if toolbox.IsStructuredJSON(expandedText) { 586 evaluated, err := toolbox.JSONToInterface(expandedText) 587 if err != nil { 588 return nil, false 589 } 590 expandedArgument = evaluated 591 } 592 } 593 evaluated, err := udf(expandedArgument, *s) 594 if err == nil { 595 return evaluated, true 596 } 597 log.Printf("failed to evaluate %v, %v", candidate, err) 598 return nil, false 599 } 600 601 func (s *Map) hasCycle(source interface{}, ownerVariable string) bool { 602 switch value := source.(type) { 603 case string: 604 return strings.Contains(value, ownerVariable) 605 case Map: 606 for k, v := range value { 607 if s.hasCycle(k, ownerVariable) || s.hasCycle(v, ownerVariable) { 608 return true 609 } 610 } 611 612 case map[string]interface{}: 613 for k, v := range value { 614 if s.hasCycle(k, ownerVariable) || s.hasCycle(v, ownerVariable) { 615 return true 616 } 617 } 618 619 case []interface{}: 620 for _, v := range value { 621 if s.hasCycle(v, ownerVariable) { 622 return true 623 } 624 } 625 case Collection: 626 for _, v := range value { 627 if s.hasCycle(v, ownerVariable) { 628 return true 629 } 630 } 631 } 632 return false 633 } 634 635 //expandExpressions will check provided text with any expression starting with dollar sign ($) to substitute it with key in the map if it is present. 636 //The result can be an expanded text or type of key referenced by the expression. 637 func (s *Map) expandExpressions(text string) interface{} { 638 if strings.Index(text, "$") == -1 { 639 return text 640 } 641 var expandVariable = func(expression string, isUDF bool, argument interface{}) (interface{}, bool) { 642 value, hasExpValue := s.GetValue(string(expression[1:])) 643 if hasExpValue { 644 if value != expression && s.hasCycle(value, expression) { 645 log.Printf("detected data cycle on %v in value: %v", expression, value) 646 return expression, true 647 } 648 if isUDF { 649 if evaluated, ok := s.evaluateUDF(value, argument); ok { 650 return evaluated, true 651 } 652 } else { 653 if value != nil && (toolbox.IsMap(value) || toolbox.IsSlice(value)) { 654 return s.Expand(value), true 655 } 656 if text, ok := value.(string); ok { 657 return text, true 658 } 659 if value != nil { 660 return toolbox.DereferenceValue(value), true 661 } 662 return value, true 663 } 664 } 665 666 if isUDF { 667 expandedArgument := s.expandArgumentsExpressions(argument) 668 _, isByteArray := expandedArgument.([]byte) 669 if !toolbox.IsMap(expandedArgument) && !toolbox.IsSlice(expandedArgument) || isByteArray { 670 argument = toolbox.AsString(expandedArgument) 671 } 672 return expression + "(" + toolbox.AsString(argument) + ")", true 673 } 674 return expression, true 675 } 676 677 return Parse(text, expandVariable) 678 } 679 680 //expandExpressions will check provided text with any expression starting with dollar sign ($) to substitute it with key in the map if it is present. 681 //The result can be an expanded text or type of key referenced by the expression. 682 func (s *Map) expandArgumentsExpressions(argument interface{}) interface{} { 683 if argument == nil || !toolbox.IsString(argument) { 684 return argument 685 } 686 687 argumentLiteral, ok := argument.(string) 688 689 if ok { 690 if toolbox.IsStructuredJSON(argumentLiteral) { 691 return s.expandExpressions(argumentLiteral) 692 } 693 } 694 695 var expandVariable = func(expression string, isUDF bool, argument interface{}) (interface{}, bool) { 696 value, hasExpValue := s.GetValue(string(expression[1:])) 697 if hasExpValue { 698 if value != expression && s.hasCycle(value, expression) { 699 log.Printf("detected data cycle on %v in value: %v", expression, value) 700 return expression, true 701 } 702 if isUDF { 703 if evaluated, ok := s.evaluateUDF(value, argument); ok { 704 return evaluated, true 705 } 706 } else { 707 if value != nil && (toolbox.IsMap(value) || toolbox.IsSlice(value)) { 708 return s.Expand(value), true 709 } 710 if text, ok := value.(string); ok { 711 return text, true 712 } 713 if value != nil { 714 return toolbox.DereferenceValue(value), true 715 } 716 return value, true 717 } 718 } 719 if isUDF { 720 expandedArgument := s.expandArgumentsExpressions(argument) 721 _, isByteArray := expandedArgument.([]byte) 722 if !toolbox.IsMap(expandedArgument) && !toolbox.IsSlice(expandedArgument) || isByteArray { 723 argument = toolbox.AsString(expandedArgument) 724 } 725 expression = expression + "(" + toolbox.AsString(argument) + ")" 726 return expression, true 727 } 728 return expression, true 729 } 730 731 tokenizer := toolbox.NewTokenizer(argumentLiteral, invalidToken, eofToken, matchers) 732 var result = make([]interface{}, 0) 733 for tokenizer.Index < len(argumentLiteral) { 734 match, err := toolbox.ExpectTokenOptionallyFollowedBy(tokenizer, whitespace, "expected argument", doubleQuoteEnclosedToken, comaToken, unmatchedToken, eofToken) 735 if err != nil { 736 return Parse(argumentLiteral, expandVariable) 737 } 738 switch match.Token { 739 case doubleQuoteEnclosedToken: 740 result = append(result, strings.Trim(match.Matched, `"`)) 741 case comaToken: 742 result = append(result, match.Matched) 743 tokenizer.Index++ 744 case unmatchedToken: 745 result = append(result, match.Matched) 746 } 747 } 748 749 for i, arg := range result { 750 textArg, ok := arg.(string) 751 if !ok { 752 continue 753 } 754 textArg = strings.Trim(textArg, "'") 755 result[i] = Parse(textArg, expandVariable) 756 757 } 758 if len(result) == 1 { 759 return result[0] 760 } 761 return result 762 } 763 764 //NewMap creates a new instance of a map. 765 func NewMap() Map { 766 return make(map[string]interface{}) 767 }