@@ -115,49 +115,93 @@ func contextMatchesCondition(ec *EngineEvaluationContext, segmentCondition *Cond
115115 return false
116116}
117117
118- // matchInOperator handles the IN operator for segment conditions, supporting both StringArray and comma-separated strings.
118+ // matchInOperator handles the IN operator for segment conditions, supporting arrays and comma-separated strings.
119119func matchInOperator (segmentCondition * Condition , contextValue ContextValue ) bool {
120120 if contextValue == nil {
121121 return false
122122 }
123123
124- traitValue := ToString (contextValue )
125-
126124 if segmentCondition .Value == nil {
127125 return false
128126 }
129127
130- // First try to use []string if available
128+ // Handle []string array
131129 if strArray , ok := segmentCondition .Value .([]string ); ok {
130+ traitValue := ToString (contextValue )
132131 return slices .Contains (strArray , traitValue )
133132 }
134133
135- // Convert []interface{} to []string (happens during JSON unmarshaling)
134+ // Handle []interface{} array (from JSON unmarshaling) - supports mixed types
136135 if ifaceArray , ok := segmentCondition .Value .([]interface {}); ok {
137- for _ , v := range ifaceArray {
138- if str , ok := v .(string ); ok && str == traitValue {
139- return true
140- }
141- }
142- return false
136+ return matchInArrayInterface (ifaceArray , contextValue )
143137 }
144138
145- // Fall back to string - try JSON parsing first, then comma-separated
139+ // Handle string value ( JSON-encoded or comma-separated)
146140 if strValue , ok := segmentCondition .Value .(string ); ok {
147141 // Try to parse as JSON array first
148- var jsonArray []string
142+ var jsonArray []interface {}
149143 if err := json .Unmarshal ([]byte (strValue ), & jsonArray ); err == nil {
150- return slices . Contains (jsonArray , traitValue )
144+ return matchInArrayInterface (jsonArray , contextValue )
151145 }
152146
153147 // Fall back to comma-separated string
154148 values := strings .Split (strValue , "," )
155- return slices .Contains (values , traitValue )
149+ // Try numeric comparison for each value
150+ for _ , v := range values {
151+ if compareValues (contextValue , strings .TrimSpace (v )) {
152+ return true
153+ }
154+ }
155+ return false
156156 }
157157
158158 return false
159159}
160160
161+ // matchInArrayInterface checks if contextValue matches any element in the array.
162+ func matchInArrayInterface (array []interface {}, contextValue ContextValue ) bool {
163+ for _ , v := range array {
164+ if compareValues (contextValue , v ) {
165+ return true
166+ }
167+ }
168+ return false
169+ }
170+
171+ // compareValues compares two values with type-aware comparison.
172+ // Supports int, float and string comparisons.
173+ func compareValues (v1 , v2 interface {}) bool {
174+ // Direct equality check first
175+ if v1 == v2 {
176+ return true
177+ }
178+
179+ // Try numeric comparison - convert both to float64
180+ f1 , ok1 := toFloat64 (v1 )
181+ f2 , ok2 := toFloat64 (v2 )
182+ if ok1 && ok2 {
183+ return f1 == f2
184+ }
185+
186+ // Fall back to string comparison
187+ return ToString (v1 ) == ToString (v2 )
188+ }
189+
190+ // toFloat64 attempts to convert a value to float64.
191+ func toFloat64 (v interface {}) (float64 , bool ) {
192+ switch val := v .(type ) {
193+ case float64 :
194+ return val , true
195+ case int64 :
196+ return float64 (val ), true
197+ case string :
198+ if f , err := strconv .ParseFloat (val , 64 ); err == nil {
199+ return f , true
200+ }
201+ }
202+ return 0 , false
203+ }
204+
161205func getContextValue (ec * EngineEvaluationContext , property string ) ContextValue {
162206 if strings .HasPrefix (property , "$." ) {
163207 value := getContextValueGetter (property )(ec )
0 commit comments