accessors.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package objx
  2. import (
  3. "regexp"
  4. "strconv"
  5. "strings"
  6. )
  7. const (
  8. // PathSeparator is the character used to separate the elements
  9. // of the keypath.
  10. //
  11. // For example, `location.address.city`
  12. PathSeparator string = "."
  13. // arrayAccesRegexString is the regex used to extract the array number
  14. // from the access path
  15. arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
  16. )
  17. // arrayAccesRegex is the compiled arrayAccesRegexString
  18. var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
  19. // Get gets the value using the specified selector and
  20. // returns it inside a new Obj object.
  21. //
  22. // If it cannot find the value, Get will return a nil
  23. // value inside an instance of Obj.
  24. //
  25. // Get can only operate directly on map[string]interface{} and []interface.
  26. //
  27. // Example
  28. //
  29. // To access the title of the third chapter of the second book, do:
  30. //
  31. // o.Get("books[1].chapters[2].title")
  32. func (m Map) Get(selector string) *Value {
  33. rawObj := access(m, selector, nil, false)
  34. return &Value{data: rawObj}
  35. }
  36. // Set sets the value using the specified selector and
  37. // returns the object on which Set was called.
  38. //
  39. // Set can only operate directly on map[string]interface{} and []interface
  40. //
  41. // Example
  42. //
  43. // To set the title of the third chapter of the second book, do:
  44. //
  45. // o.Set("books[1].chapters[2].title","Time to Go")
  46. func (m Map) Set(selector string, value interface{}) Map {
  47. access(m, selector, value, true)
  48. return m
  49. }
  50. // getIndex returns the index, which is hold in s by two braches.
  51. // It also returns s withour the index part, e.g. name[1] will return (1, name).
  52. // If no index is found, -1 is returned
  53. func getIndex(s string) (int, string) {
  54. arrayMatches := arrayAccesRegex.FindStringSubmatch(s)
  55. if len(arrayMatches) > 0 {
  56. // Get the key into the map
  57. selector := arrayMatches[1]
  58. // Get the index into the array at the key
  59. // We know this cannt fail because arrayMatches[2] is an int for sure
  60. index, _ := strconv.Atoi(arrayMatches[2])
  61. return index, selector
  62. }
  63. return -1, s
  64. }
  65. // access accesses the object using the selector and performs the
  66. // appropriate action.
  67. func access(current interface{}, selector string, value interface{}, isSet bool) interface{} {
  68. selSegs := strings.SplitN(selector, PathSeparator, 2)
  69. thisSel := selSegs[0]
  70. index := -1
  71. if strings.Contains(thisSel, "[") {
  72. index, thisSel = getIndex(thisSel)
  73. }
  74. if curMap, ok := current.(Map); ok {
  75. current = map[string]interface{}(curMap)
  76. }
  77. // get the object in question
  78. switch current.(type) {
  79. case map[string]interface{}:
  80. curMSI := current.(map[string]interface{})
  81. if len(selSegs) <= 1 && isSet {
  82. curMSI[thisSel] = value
  83. return nil
  84. }
  85. _, ok := curMSI[thisSel].(map[string]interface{})
  86. if (curMSI[thisSel] == nil || !ok) && index == -1 && isSet {
  87. curMSI[thisSel] = map[string]interface{}{}
  88. }
  89. current = curMSI[thisSel]
  90. default:
  91. current = nil
  92. }
  93. // do we need to access the item of an array?
  94. if index > -1 {
  95. if array, ok := current.([]interface{}); ok {
  96. if index < len(array) {
  97. current = array[index]
  98. } else {
  99. current = nil
  100. }
  101. }
  102. }
  103. if len(selSegs) > 1 {
  104. current = access(current, selSegs[1], value, isSet)
  105. }
  106. return current
  107. }