1313import org .elasticsearch .common .geo .GeoPoint ;
1414import org .elasticsearch .common .settings .Settings ;
1515import org .elasticsearch .core .Booleans ;
16+ import org .elasticsearch .datageneration .FieldType ;
1617import org .elasticsearch .datageneration .matchers .MatchResult ;
1718import org .elasticsearch .index .mapper .DateFieldMapper ;
19+ import org .elasticsearch .index .mapper .NumberFieldMapper ;
1820import org .elasticsearch .xcontent .XContentBuilder ;
21+ import org .elasticsearch .xcontent .XContentParserConfiguration ;
22+ import org .elasticsearch .xcontent .XContentType ;
1923
2024import java .math .BigInteger ;
2125import java .time .Instant ;
@@ -353,6 +357,10 @@ Object convert(Object value, Object nullValue) {
353357 }
354358
355359 class NumberMatcher extends GenericMappingAwareMatcher {
360+
361+ private final FieldType fieldType ;
362+ private final NumberFieldMapper .NumberType numberType ;
363+
356364 NumberMatcher (
357365 String fieldType ,
358366 XContentBuilder actualMappings ,
@@ -361,6 +369,8 @@ class NumberMatcher extends GenericMappingAwareMatcher {
361369 Settings .Builder expectedSettings
362370 ) {
363371 super (fieldType , actualMappings , actualSettings , expectedMappings , expectedSettings );
372+ this .fieldType = FieldType .tryParse (fieldType );
373+ this .numberType = NumberFieldMapper .NumberType .valueOf (this .fieldType .name ());
364374 }
365375
366376 @ Override
@@ -373,6 +383,32 @@ Object convert(Object value, Object nullValue) {
373383 return nullValue ;
374384 }
375385
386+ // Attempt to coerce string values into numbers
387+ if (value instanceof String s ) {
388+ try (var parser = XContentType .JSON .xContent ().createParser (XContentParserConfiguration .EMPTY , "\" " + s + "\" " )) {
389+ parser .nextToken ();
390+ return numberType .parse (parser , true );
391+ } catch (Exception e ) {
392+ // malformed string
393+ return value ;
394+ }
395+ }
396+
397+ // When a number mapping is coerced, the expected value will come from the above parser and will have the correct java type.
398+ // Whereas, if it fits, the actual value will be in an Integer or a Double. To correctly treat expected and actual values as
399+ // equal the actual value must be cast to the appropriate type.
400+ if (value instanceof Integer v ) {
401+ return switch (fieldType ) {
402+ case LONG -> v .longValue ();
403+ case SHORT -> v .shortValue ();
404+ case BYTE -> v .byteValue ();
405+ default -> value ;
406+ };
407+ }
408+ if (value instanceof Double v ) {
409+ return fieldType == FieldType .FLOAT ? v .floatValue () : value ;
410+ }
411+
376412 return value ;
377413 }
378414 }
0 commit comments