Test Failed
Pull Request — master (#36)
by Frank
03:43 queued 02:03
created

objx.access   F

Complexity

Conditions 21

Size

Total Lines 87
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 48
nop 5
dl 0
loc 87
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like objx.access often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package objx
2
3
import (
4
	"fmt"
5
	"regexp"
6
	"strconv"
7
	"strings"
8
)
9
10
// arrayAccesRegexString is the regex used to extract the array number
11
// from the access path
12
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
13
14
// arrayAccesRegex is the compiled arrayAccesRegexString
15
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
16
17
// Get gets the value using the specified selector and
18
// returns it inside a new Obj object.
19
//
20
// If it cannot find the value, Get will return a nil
21
// value inside an instance of Obj.
22
//
23
// Get can only operate directly on map[string]interface{} and []interface.
24
//
25
// Example
26
//
27
// To access the title of the third chapter of the second book, do:
28
//
29
//    o.Get("books[1].chapters[2].title")
30
func (m Map) Get(selector string) *Value {
31
	rawObj := access(m, selector, nil, false, false)
32
	return &Value{data: rawObj}
33
}
34
35
// Set sets the value using the specified selector and
36
// returns the object on which Set was called.
37
//
38
// Set can only operate directly on map[string]interface{} and []interface
39
//
40
// Example
41
//
42
// To set the title of the third chapter of the second book, do:
43
//
44
//    o.Set("books[1].chapters[2].title","Time to Go")
45
func (m Map) Set(selector string, value interface{}) Map {
46
	access(m, selector, value, true, false)
47
	return m
48
}
49
50
// access accesses the object using the selector and performs the
51
// appropriate action.
52
func access(current, selector, value interface{}, isSet, panics bool) interface{} {
53
54
	switch selector.(type) {
55
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
56
57
		if array, ok := current.([]interface{}); ok {
58
			index := intFromInterface(selector)
59
60
			if index >= len(array) {
61
				if panics {
62
					panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
63
				}
64
				return nil
65
			}
66
67
			return array[index]
68
		}
69
70
		return nil
71
72
	case string:
73
74
		selStr := selector.(string)
75
		selSegs := strings.SplitN(selStr, PathSeparator, 2)
76
		thisSel := selSegs[0]
77
		index := -1
78
		var err error
79
80
		if strings.Contains(thisSel, "[") {
81
			arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
82
83
			if len(arrayMatches) > 0 {
84
				// Get the key into the map
85
				thisSel = arrayMatches[1]
86
87
				// Get the index into the array at the key
88
				index, err = strconv.Atoi(arrayMatches[2])
89
90
				if err != nil {
91
					// This should never happen. If it does, something has gone
92
					// seriously wrong. Panic.
93
					panic("objx: Array index is not an integer.  Must use array[int].")
94
				}
95
			}
96
		}
97
98
		if curMap, ok := current.(Map); ok {
99
			current = map[string]interface{}(curMap)
100
		}
101
102
		// get the object in question
103
		switch current.(type) {
104
		case map[string]interface{}:
105
			curMSI := current.(map[string]interface{})
106
			if len(selSegs) <= 1 && isSet {
107
				curMSI[thisSel] = value
108
				return nil
109
			}
110
			current = curMSI[thisSel]
111
		default:
112
			current = nil
113
		}
114
115
		if current == nil && panics {
116
			panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
117
		}
118
119
		// do we need to access the item of an array?
120
		if index > -1 {
121
			if array, ok := current.([]interface{}); ok {
122
				if index < len(array) {
123
					current = array[index]
124
				} else {
125
					if panics {
126
						panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
127
					}
128
					current = nil
129
				}
130
			}
131
		}
132
133
		if len(selSegs) > 1 {
134
			current = access(current, selSegs[1], value, isSet, panics)
135
		}
136
137
	}
138
	return current
139
}
140
141
// intFromInterface converts an interface object to the largest
142
// representation of an unsigned integer using a type switch and
143
// assertions
144
func intFromInterface(selector interface{}) int {
145
	var value int
146
	switch selector.(type) {
147
	case int:
148
		value = selector.(int)
149
	case int8:
150
		value = int(selector.(int8))
151
	case int16:
152
		value = int(selector.(int16))
153
	case int32:
154
		value = int(selector.(int32))
155
	case int64:
156
		value = int(selector.(int64))
157
	case uint:
158
		value = int(selector.(uint))
159
	case uint8:
160
		value = int(selector.(uint8))
161
	case uint16:
162
		value = int(selector.(uint16))
163
	case uint32:
164
		value = int(selector.(uint32))
165
	case uint64:
166
		value = int(selector.(uint64))
167
	default:
168
		panic("objx: array access argument is not an integer type (this should never happen)")
169
	}
170
	return value
171
}
172