1 package ldvalue 2 3 import ( 4 "encoding/json" 5 6 "golang.org/x/exp/slices" 7 ) 8 9 // Value represents any of the data types supported by JSON, all of which can be used for a LaunchDarkly 10 // feature flag variation, or for an attribute in an evaluation context. Value instances are immutable. 11 // 12 // # Uses of JSON types in LaunchDarkly 13 // 14 // LaunchDarkly feature flags can have variations of any JSON type other than null. If you want to 15 // evaluate a feature flag in a general way that does not have expectations about the variation type, 16 // or if the variation value is a complex data structure such as an array or object, you can use the 17 // SDK method [github.com/launchdarkly/go-server-sdk/v6/LDClient.JSONVariationDetail] to get the value 18 // and then use Value methods to examine it. 19 // 20 // Similarly, attributes of an evaluation context ([github.com/launchdarkly/go-sdk-common/v3/ldcontext.Context]) 21 // can have variations of any JSON type other than null. If you want to set a context attribute in a general 22 // way that will accept any type, or set the attribute value to a complex data structure such as an array 23 // or object, you can use the builder method [github.com/launchdarkly/go-sdk-common/v3/ldcontext.Builder.SetValue]. 24 // 25 // Arrays and objects have special meanings in LaunchDarkly flag evaluation: 26 // - An array of values means "try to match any of these values to the targeting rule." 27 // - An object allows you to match a property within the object to the targeting rule. For instance, 28 // in the example above, a targeting rule could reference /objectAttr1/color to match the value 29 // "green". Nested property references like /objectAttr1/address/street are allowed if a property 30 // contains another JSON object. 31 // 32 // # Constructors and builders 33 // 34 // The most efficient way to create a Value is with typed constructors such as [Bool] and [String], or, for 35 // complex data, the builders [ArrayBuild] and [ObjectBuild]. However, any value that can be represented in 36 // JSON can be converted to a Value with [CopyArbitraryValue] or [FromJSONMarshal], or you can parse a 37 // Value from JSON. The following code examples all produce the same value: 38 // 39 // value := ldvalue.ObjectBuild().SetString("thing", "x").Build() 40 // 41 // type MyStruct struct { 42 // Thing string `json:"thing"` 43 // } 44 // value := ldvalue.FromJSONMarshal(MyStruct{Thing: "x"}) 45 // 46 // value := ldvalue.Parse([]byte(`{"thing": "x"}`)) 47 // 48 // # Comparisons 49 // 50 // You cannot compare Value instances with the == operator, because the struct may contain a slice. or a 51 // map. Value has the [Value.Equal] method for this purpose; [reflect.DeepEqual] will also work. 52 type Value struct { 53 valueType ValueType 54 // Used when the value is a boolean. 55 boolValue bool 56 // Used when the value is a number. 57 numberValue float64 58 // Used when the value is a string. 59 stringValue string 60 // Used when the value is an array, zero-valued otherwise. 61 arrayValue ValueArray 62 // Used when the value is an object, zero-valued otherwise. 63 objectValue ValueMap 64 rawValue []byte 65 } 66 67 // ValueType indicates which JSON type is contained in a [Value]. 68 type ValueType int 69 70 // String returns the name of the value type. 71 func (t ValueType) String() string { 72 switch t { 73 case NullType: 74 return nullAsJSON 75 case BoolType: 76 return "bool" 77 case NumberType: 78 return "number" 79 case StringType: 80 return "string" 81 case ArrayType: 82 return "array" 83 case ObjectType: 84 return "object" 85 case RawType: 86 return "raw" 87 default: 88 return "unknown" 89 } 90 } 91 92 // Null creates a null Value. 93 func Null() Value { 94 return Value{valueType: NullType} 95 } 96 97 // Bool creates a boolean Value. 98 func Bool(value bool) Value { 99 return Value{valueType: BoolType, boolValue: value} 100 } 101 102 // Int creates a numeric Value from an integer. 103 // 104 // Note that all numbers are represented internally as the same type (float64), so Int(2) is 105 // exactly equal to Float64(2). 106 func Int(value int) Value { 107 return Float64(float64(value)) 108 } 109 110 // Float64 creates a numeric Value from a float64. 111 func Float64(value float64) Value { 112 return Value{valueType: NumberType, numberValue: value} 113 } 114 115 // String creates a string Value. 116 func String(value string) Value { 117 return Value{valueType: StringType, stringValue: value} 118 } 119 120 // Raw creates an unparsed JSON Value. 121 // 122 // This constructor stores a copy of the [json.RawMessage] value as-is without syntax validation, and 123 // sets the type of the Value to [RawType]. The advantage of this is that if you have some 124 // data that you do not expect to do any computations with, but simply want to include in JSON data 125 // to be sent to LaunchDarkly, the original data will be output as-is and does not need to be parsed. 126 // 127 // However, if you do anything that involves inspecting the value (such as comparing it to another 128 // value, or evaluating a feature flag that references the value in a context attribute), the JSON 129 // data will be parsed automatically each time that happens, so there will be no efficiency gain. 130 // Therefore, if you expect any such operations to happen, it is better to use [Parse] instead to 131 // parse the JSON immediately, or use value builder methods such as [ObjectBuild]. 132 // 133 // If you pass malformed data that is not valid JSON, you will get malformed data if it is re-encoded 134 // to JSON. It is the caller's responsibility to make sure the json.RawMessage really is valid JSON. 135 // However, since it is easy to mistakenly write json.RawMessage(nil) (an invalid zero-length value) 136 // when what is really meant is a JSON null value, this constructor transparently converts both 137 // json.RawMessage(nil) and json.RawMessage([]byte("")) to Null(). 138 func Raw(value json.RawMessage) Value { 139 if len(value) == 0 { 140 return Null() 141 } 142 return Value{valueType: RawType, rawValue: slices.Clone(value)} 143 } 144 145 // FromJSONMarshal creates a Value from the JSON representation of any Go value. 146 // 147 // This is based on [encoding/json.Marshal], so it can be used with any value that can be passed to that 148 // function, and follows the usual behavior of json.Marshal with regard to types and field names. 149 // For instance, you could use it with a custom struct type: 150 // 151 // type MyStruct struct { 152 // Thing string `json:"thing"` 153 // } 154 // value := ldvalue.FromJSONMarshal(MyStruct{Thing: "x"}) 155 // 156 // It is equivalent to calling json.Marshal followed by [Parse], so it incurs the same overhead of 157 // first marshaling the value and then parsing the resulting JSON. 158 func FromJSONMarshal(anyValue any) Value { 159 jsonBytes, err := json.Marshal(anyValue) 160 if err != nil { 161 return Null() 162 } 163 return Parse(jsonBytes) 164 } 165 166 // CopyArbitraryValue creates a Value from an arbitrary value of any type. 167 // 168 // If the value is nil, a boolean, an integer, a floating-point number, or a string, it becomes the 169 // corresponding JSON primitive value type. If it is a slice of values ([]any or []Value), it is 170 // deep-copied to an array value. If it is a map of strings to values (map[string]any or 171 // map[string]Value), it is deep-copied to an object value. 172 // 173 // If it is a pointer to any of the above types, then it is dereferenced and treated the same as above, 174 // unless the pointer is nil, in which case it becomes [Null](). 175 // 176 // For all other types, the value is marshaled to JSON and then converted to the corresponding Value 177 // type, just as if you had called [FromJSONMarshal]. The difference is only that CopyArbitraryValue is 178 // more efficient for primitive types, slices, and maps, which it can copy without first marshaling 179 // them to JSON. 180 func CopyArbitraryValue(anyValue any) Value { //nolint:gocyclo // yes, we know it's a long function 181 if anyValue == nil { 182 return Null() 183 // Note that an interface value can be nil in two ways: nil with no type at all, which is this case, 184 // or a nil pointer of some specific pointer type, which we have to check for separately below. 185 } 186 switch o := anyValue.(type) { 187 case Value: 188 return o 189 case *Value: 190 if o == nil { 191 return Null() 192 } 193 return *o 194 case OptionalString: 195 return o.AsValue() 196 case *OptionalString: 197 if o == nil { 198 return Null() 199 } 200 return o.AsValue() 201 case bool: 202 return Bool(o) 203 case *bool: 204 if o == nil { 205 return Null() 206 } 207 return Bool(*o) 208 case int8: 209 return Float64(float64(o)) 210 case *int8: 211 if o == nil { 212 return Null() 213 } 214 return Float64(float64(*o)) 215 case uint8: 216 return Float64(float64(o)) 217 case *uint8: 218 if o == nil { 219 return Null() 220 } 221 return Float64(float64(*o)) 222 case int16: 223 return Float64(float64(o)) 224 case *int16: 225 if o == nil { 226 return Null() 227 } 228 return Float64(float64(*o)) 229 case uint16: 230 return Float64(float64(o)) 231 case *uint16: 232 if o == nil { 233 return Null() 234 } 235 return Float64(float64(*o)) 236 case int: 237 return Float64(float64(o)) 238 case *int: 239 if o == nil { 240 return Null() 241 } 242 return Float64(float64(*o)) 243 case uint: 244 return Float64(float64(o)) 245 case *uint: 246 if o == nil { 247 return Null() 248 } 249 return Float64(float64(*o)) 250 case int32: 251 return Float64(float64(o)) 252 case *int32: 253 if o == nil { 254 return Null() 255 } 256 return Float64(float64(*o)) 257 case uint32: 258 return Float64(float64(o)) 259 case *uint32: 260 if o == nil { 261 return Null() 262 } 263 return Float64(float64(*o)) 264 case float32: 265 return Float64(float64(o)) 266 case *float32: 267 if o == nil { 268 return Null() 269 } 270 return Float64(float64(*o)) 271 case float64: 272 return Float64(o) 273 case *float64: 274 if o == nil { 275 return Null() 276 } 277 return Float64(*o) 278 case string: 279 return String(o) 280 case *string: 281 if o == nil { 282 return Null() 283 } 284 return String(*o) 285 case []any: 286 return copyArbitraryValueArray(o) 287 case *[]any: 288 if o == nil { 289 return Null() 290 } 291 return copyArbitraryValueArray(*o) 292 case []Value: 293 return ArrayOf(o...) 294 case *[]Value: 295 if o == nil { 296 return Null() 297 } 298 return ArrayOf((*o)...) 299 case map[string]any: 300 return copyArbitraryValueMap(o) 301 case *map[string]any: 302 if o == nil { 303 return Null() 304 } 305 return copyArbitraryValueMap(*o) 306 case map[string]Value: 307 return CopyObject(o) 308 case *map[string]Value: 309 if o == nil { 310 return Null() 311 } 312 return CopyObject(*o) 313 case json.RawMessage: 314 return Raw(o) 315 case *json.RawMessage: 316 if o == nil { 317 return Null() 318 } 319 return Raw(*o) 320 default: 321 return FromJSONMarshal(anyValue) 322 } 323 } 324 325 func copyArbitraryValueArray(o []any) Value { 326 return Value{valueType: ArrayType, arrayValue: CopyArbitraryValueArray(o)} 327 } 328 329 func copyArbitraryValueMap(o map[string]any) Value { 330 return Value{valueType: ObjectType, objectValue: CopyArbitraryValueMap(o)} 331 } 332 333 // Type returns the ValueType of the Value. 334 func (v Value) Type() ValueType { 335 return v.valueType 336 } 337 338 // IsNull returns true if the Value is a null. 339 func (v Value) IsNull() bool { 340 return v.valueType == NullType || (v.valueType == RawType && v.parseIfRaw().IsNull()) 341 } 342 343 // IsDefined returns true if the Value is anything other than null. 344 // 345 // This is exactly equivalent to !v.IsNull(), but is provided as a separate method for consistency 346 // with other types that have an IsDefined() method. 347 func (v Value) IsDefined() bool { 348 return !v.IsNull() 349 } 350 351 // IsBool returns true if the Value is a boolean. 352 func (v Value) IsBool() bool { 353 return v.valueType == BoolType || (v.valueType == RawType && v.parseIfRaw().IsBool()) 354 } 355 356 // IsNumber returns true if the Value is numeric. 357 func (v Value) IsNumber() bool { 358 return v.valueType == NumberType || (v.valueType == RawType && v.parseIfRaw().IsNumber()) 359 } 360 361 // IsInt returns true if the Value is an integer. 362 // 363 // JSON does not have separate types for integer and floating-point values; they are both just numbers. 364 // IsInt returns true if and only if the actual numeric value has no fractional component, so 365 // Int(2).IsInt() and Float64(2.0).IsInt() are both true. 366 func (v Value) IsInt() bool { 367 return (v.valueType == NumberType && v.numberValue == float64(int(v.numberValue))) || 368 (v.valueType == RawType && v.parseIfRaw().IsInt()) 369 } 370 371 // IsString returns true if the Value is a string. 372 func (v Value) IsString() bool { 373 return v.valueType == StringType || (v.valueType == RawType && v.parseIfRaw().IsString()) 374 } 375 376 // BoolValue returns the Value as a boolean. 377 // 378 // If the Value is not a boolean, it returns false. 379 func (v Value) BoolValue() bool { 380 switch v.valueType { 381 case BoolType: 382 return v.boolValue 383 case RawType: 384 return v.parseIfRaw().BoolValue() 385 default: 386 return false 387 } 388 } 389 390 // IntValue returns the value as an int. 391 // 392 // If the Value is not numeric, it returns zero. If the value is a number but not an integer, it is 393 // rounded toward zero (truncated). 394 func (v Value) IntValue() int { 395 return int(v.Float64Value()) 396 } 397 398 // Float64Value returns the value as a float64. 399 // 400 // If the Value is not numeric, it returns zero. 401 func (v Value) Float64Value() float64 { 402 switch v.valueType { 403 case NumberType: 404 return v.numberValue 405 case RawType: 406 return v.parseIfRaw().Float64Value() 407 default: 408 return 0 409 } 410 } 411 412 // StringValue returns the value as a string. 413 // 414 // If the value is not a string, it returns an empty string. 415 // 416 // This is different from [String], which returns a string representation of any value type, 417 // including any necessary JSON delimiters. 418 func (v Value) StringValue() string { 419 switch v.valueType { 420 case StringType: 421 return v.stringValue 422 case RawType: 423 return v.parseIfRaw().StringValue() 424 default: 425 return "" 426 } 427 } 428 429 // AsOptionalString converts the value to the OptionalString type, which contains either a string 430 // value or nothing if the original value was not a string. 431 func (v Value) AsOptionalString() OptionalString { 432 switch v.valueType { 433 case StringType: 434 return NewOptionalString(v.stringValue) 435 case RawType: 436 return v.parseIfRaw().AsOptionalString() 437 default: 438 return OptionalString{} 439 } 440 } 441 442 // AsRaw returns the value as a [json.RawMessage]. 443 // 444 // If the value was originally created with [Raw], it returns a copy of the original value. For all 445 // other values, it converts the value to its JSON representation and returns that representation. 446 // 447 // Note that the [Raw] constructor does not do any syntax validation, so if you create a Value from 448 // a malformed string such as ldvalue.Raw(json.RawMessage("{{{")), you will get back the same string 449 // from AsRaw(). 450 func (v Value) AsRaw() json.RawMessage { 451 if v.valueType == RawType { 452 return v.rawValue 453 } 454 bytes, err := json.Marshal(v) 455 if err == nil { 456 return json.RawMessage(bytes) 457 } 458 return nil 459 } 460 461 // AsArbitraryValue returns the value in its simplest Go representation, typed as "any". 462 // 463 // This is nil for a null value; for primitive types, it is bool, float64, or string (all numbers 464 // are represented as float64 because that is Go's default when parsing from JSON). For unparsed 465 // JSON data created with [Raw], it returns a [json.RawMessage]. 466 // 467 // Arrays and objects are represented as []any and map[string]any. They are deep-copied, which 468 // preserves immutability of the Value but may be an expensive operation. To examine array and 469 // object values without copying the whole data structure, use getter methods: [Value.Count], 470 // [Value.Keys], [Value.GetByIndex], [Value.TryGetByIndex], [Value.GetByKey], [Value.TryGetByKey]. 471 func (v Value) AsArbitraryValue() any { 472 switch v.valueType { 473 case NullType: 474 return nil 475 case BoolType: 476 return v.boolValue 477 case NumberType: 478 return v.numberValue 479 case StringType: 480 return v.stringValue 481 case ArrayType: 482 return v.arrayValue.AsArbitraryValueSlice() 483 case ObjectType: 484 return v.objectValue.AsArbitraryValueMap() 485 case RawType: 486 return v.AsRaw() 487 default: 488 return nil 489 } 490 } 491 492 // String converts the value to a string representation, equivalent to [Value.JSONString]. 493 // 494 // This is different from [Value.StringValue], which returns the actual string for a string value or an empty 495 // string for anything else. For instance, Int(2).StringValue() returns "2" and String("x").StringValue() 496 // returns "\"x\"", whereas Int(2).AsString() returns "" and String("x").AsString() returns 497 // "x". 498 // 499 // This method is provided because it is common to use the [fmt.Stringer] interface as a quick way to 500 // summarize the contents of a value. The simplest way to do so in this case is to use the JSON 501 // representation. 502 func (v Value) String() string { 503 return v.JSONString() 504 } 505 506 // Equal tests whether this Value is equal to another, in both type and value. 507 // 508 // For arrays and objects, this is a deep equality test. This method behaves the same as 509 // [reflect.DeepEqual], but is slightly more efficient. 510 // 511 // Unparsed JSON values created with [Raw] will be parsed in order to do this comparison. 512 func (v Value) Equal(other Value) bool { 513 if v.valueType == RawType || other.valueType == RawType { 514 return v.parseIfRaw().Equal(other.parseIfRaw()) 515 } 516 if v.valueType == other.valueType { 517 switch v.valueType { 518 case NullType: 519 return true 520 case BoolType: 521 return v.boolValue == other.boolValue 522 case NumberType: 523 return v.numberValue == other.numberValue 524 case StringType, RawType: 525 return v.stringValue == other.stringValue 526 case ArrayType: 527 return v.arrayValue.Equal(other.arrayValue) 528 case ObjectType: 529 return v.objectValue.Equal(other.objectValue) 530 } 531 } 532 return false 533 } 534 535 // AsPointer returns either a pointer to a copy of this Value, or nil if it is a null value. 536 // 537 // This may be desirable if you are serializing a struct that contains a Value, and you want 538 // that field to be completely omitted if the Value is null; since the "omitempty" tag only 539 // works for pointers, you can declare the field as a *Value like so: 540 // 541 // type MyJsonStruct struct { 542 // AnOptionalField *Value `json:"anOptionalField,omitempty"` 543 // } 544 // s := MyJsonStruct{AnOptionalField: someValue.AsPointer()} 545 func (v Value) AsPointer() *Value { 546 if v.IsNull() { 547 return nil 548 } 549 return &v 550 } 551 552 func (v Value) parseIfRaw() Value { 553 if v.valueType != RawType { 554 return v 555 } 556 return Parse(v.rawValue) 557 } 558