1 package ldvalue 2 3 import ( 4 "golang.org/x/exp/slices" 5 ) 6 7 // we reuse this for all non-nil zero-length ValueArray instances 8 var emptyArray = []Value{} //nolint:gochecknoglobals 9 10 // ValueArray is an immutable array of [Value]s. 11 // 12 // This is used internally to hold the contents of a JSON array in a Value. You can also use it 13 // separately in any context where you know that the data must be array-like, rather than any of the 14 // other types that a Value can be. 15 // 16 // The wrapped slice is not directly accessible, so it cannot be modified. You can obtain a copy of 17 // it with [ValueArray.AsSlice] if necessary. 18 // 19 // Like a Go slice, there is a distinction between an array in a nil state-- which is the zero 20 // value of ValueArray{}-- and a non-nil aray that is empty. The former is represented in JSON as a 21 // null; the latter is an empty JSON array []. 22 type ValueArray struct { 23 data []Value 24 } 25 26 // ValueArrayBuilder is a builder created by [ValueArrayBuild], for creating immutable JSON arrays. 27 // 28 // A ValueArrayBuilder should not be accessed by multiple goroutines at once. 29 type ValueArrayBuilder struct { 30 copyOnWrite bool 31 output []Value 32 } 33 34 // Add appends an element to the array builder. 35 func (b *ValueArrayBuilder) Add(value Value) *ValueArrayBuilder { 36 if b == nil { 37 return b 38 } 39 if b.copyOnWrite { 40 n := len(b.output) 41 newSlice := make([]Value, n, n+1) 42 copy(newSlice[0:n], b.output) 43 b.output = newSlice 44 b.copyOnWrite = false 45 } 46 if b.output == nil { 47 b.output = make([]Value, 0, 1) 48 } 49 b.output = append(b.output, value) 50 return b 51 } 52 53 // AddAllFromValueArray appends all elements from an existing ValueArray. 54 func (b *ValueArrayBuilder) AddAllFromValueArray(a ValueArray) *ValueArrayBuilder { 55 for _, v := range a.data { 56 b.Add(v) 57 } 58 return b 59 } 60 61 // Build creates a ValueArray containing the previously added array elements. Continuing to modify the 62 // same builder by calling Add after that point does not affect the returned array. 63 func (b *ValueArrayBuilder) Build() ValueArray { 64 if b == nil { 65 return ValueArray{} 66 } 67 if b.output == nil { 68 return ValueArray{emptyArray} 69 } 70 b.copyOnWrite = true 71 return ValueArray{b.output} 72 } 73 74 // ValueArrayBuild creates a builder for constructing an immutable [ValueArray]. 75 // 76 // ValueArray := ldvalue.ValueArrayBuild().Add(ldvalue.Int(100)).Add(ldvalue.Int(200)).Build() 77 func ValueArrayBuild() *ValueArrayBuilder { 78 return &ValueArrayBuilder{} 79 } 80 81 // ValueArrayBuildWithCapacity creates a builder for constructing an immutable [ValueArray]. 82 // 83 // The capacity parameter is the same as the capacity of a slice, allowing you to preallocate space 84 // if you know the number of elements; otherwise you can pass zero. 85 // 86 // arrayValue := ldvalue.ValueArrayBuildWithCapacity(2).Add(ldvalue.Int(100)).Add(ldvalue.Int(200)).Build() 87 func ValueArrayBuildWithCapacity(capacity int) *ValueArrayBuilder { 88 return &ValueArrayBuilder{output: make([]Value, 0, capacity)} 89 } 90 91 // ValueArrayBuildFromArray creates a builder for constructing an immutable [ValueArray], initializing it 92 // from an existing ValueArray. 93 // 94 // The builder has copy-on-write behavior, so if you make no changes before calling Build(), the 95 // original array is used as-is. 96 func ValueArrayBuildFromArray(a ValueArray) *ValueArrayBuilder { 97 return &ValueArrayBuilder{output: a.data, copyOnWrite: true} 98 } 99 100 // ValueArrayOf creates a ValueArray from a list of [Value]s. 101 // 102 // This requires a slice copy to ensure immutability; otherwise, an existing slice could be passed 103 // using the spread operator, and then modified. However, since Value is itself immutable, it does 104 // not need to deep-copy each item. 105 func ValueArrayOf(items ...Value) ValueArray { 106 // ValueArrayOf() with no parameters will pass nil rather than a zero-length slice; logically we 107 // still want it to create a non-nil array. 108 if items == nil { 109 return ValueArray{emptyArray} 110 } 111 return CopyValueArray(items) 112 } 113 114 // CopyValueArray copies an existing ordinary map to a ValueArray. 115 // 116 // If the parameter is nil, an uninitialized ValueArray{} is returned instead of a zero-length array. 117 func CopyValueArray(data []Value) ValueArray { 118 if data == nil { 119 return ValueArray{} 120 } 121 if len(data) == 0 { 122 return ValueArray{emptyArray} 123 } 124 return ValueArray{data: slices.Clone(data)} 125 } 126 127 // CopyArbitraryValueArray copies an existing ordinary slice of values of any type to a ValueArray. 128 // The behavior for each value is the same as [CopyArbitraryValue]. 129 // 130 // If the parameter is nil, an uninitialized ValueArray{} is returned instead of a zero-length map. 131 func CopyArbitraryValueArray(data []any) ValueArray { 132 if data == nil { 133 return ValueArray{} 134 } 135 a := make([]Value, len(data)) 136 for i, v := range data { 137 a[i] = CopyArbitraryValue(v) 138 } 139 return ValueArray{data: a} 140 } 141 142 // IsDefined returns true if the array is non-nil. 143 func (a ValueArray) IsDefined() bool { 144 return a.data != nil 145 } 146 147 // Count returns the number of elements in the array. For an uninitialized ValueArray{}, this is zero. 148 func (a ValueArray) Count() int { 149 return len(a.data) 150 } 151 152 // AsValue converts the ValueArray to a Value which is either [Null]() or an array. This does not 153 // cause any new allocations. 154 func (a ValueArray) AsValue() Value { 155 if a.data == nil { 156 return Null() 157 } 158 return Value{valueType: ArrayType, arrayValue: a} 159 } 160 161 // Get gets a value from the array by index. 162 // 163 // If the index is out of range, it returns [Null](). 164 func (a ValueArray) Get(index int) Value { 165 if index < 0 || index >= len(a.data) { 166 return Null() 167 } 168 return a.data[index] 169 } 170 171 // TryGet gets a value from the map by index, with a second return value of true if successful. 172 // 173 // If the index is out of range, it returns ([Null](), false). 174 func (a ValueArray) TryGet(index int) (Value, bool) { 175 if index < 0 || index >= len(a.data) { 176 return Null(), false 177 } 178 return a.data[index], true 179 } 180 181 // AsSlice returns a copy of the wrapped data as a simple Go slice whose values are of type [Value]. 182 // 183 // For an uninitialized ValueArray{}, this returns nil. 184 func (a ValueArray) AsSlice() []Value { 185 return slices.Clone(a.data) 186 } 187 188 // AsArbitraryValueSlice returns a copy of the wrapped data as a simple Go slice whose values are 189 // of any type. The behavior for each value is the same as [Value.AsArbitraryValue]. 190 // 191 // For an uninitialized ValueArray{}, this returns nil. 192 func (a ValueArray) AsArbitraryValueSlice() []any { 193 if a.data == nil { 194 return nil 195 } 196 ret := make([]any, len(a.data)) 197 for i, v := range a.data { 198 ret[i] = v.AsArbitraryValue() 199 } 200 return ret 201 } 202 203 // Equal returns true if the two arrays are deeply equal. Nil and zero-length arrays are not considered 204 // equal to each other. 205 func (a ValueArray) Equal(other ValueArray) bool { 206 if a.IsDefined() != other.IsDefined() { 207 return false 208 } 209 return slices.EqualFunc(a.data, other.data, Value.Equal) 210 } 211 212 // Transform applies a transformation function to a ValueArray, returning a new ValueArray. 213 // 214 // The behavior is as follows: 215 // 216 // If the input value is nil or zero-length, the result is identical and the function is not called. 217 // 218 // Otherwise, fn is called for each value. It should return a transformed value and true, or else 219 // return false for the second return value if the property should be dropped. 220 func (a ValueArray) Transform(fn func(index int, value Value) (Value, bool)) ValueArray { 221 if len(a.data) == 0 { 222 return a 223 } 224 ret := a.data 225 startedNewSlice := false 226 for i, v := range a.data { 227 transformedValue, ok := fn(i, v) 228 modified := !ok || !transformedValue.Equal(v) 229 if modified && !startedNewSlice { 230 // This is the first change we've seen, so we should start building a new slice and 231 // retroactively add any values to it that already passed the test without changes. 232 startedNewSlice = true 233 ret = make([]Value, i, len(a.data)) 234 copy(ret, a.data) 235 } 236 if startedNewSlice && ok { 237 ret = append(ret, transformedValue) 238 } 239 } 240 return ValueArray{ret} 241 } 242 243 // String converts the value to a string representation, equivalent to [ValueArray.JSONString]. 244 // 245 // This method is provided because it is common to use the Stringer interface as a quick way to 246 // summarize the contents of a value. The simplest way to do so in this case is to use the JSON 247 // representation. 248 func (a ValueArray) String() string { 249 return a.JSONString() 250 } 251