@@ -234,16 +234,19 @@ Jtd.Result validateInteger(JsonValue instance, String type, boolean verboseError
234234 if (instance instanceof JsonNumber num ) {
235235 Number value = num .toNumber ();
236236
237- // Check if the number is not integral (has fractional part )
238- if (value instanceof Double d && d != Math . floor ( d )) {
237+ // Check for fractional component first (applies to all Number types )
238+ if (hasFractionalComponent ( value )) {
239239 return Jtd .Result .failure (Jtd .Error .EXPECTED_INTEGER .message ());
240240 }
241241
242- // Handle BigDecimal - check if it has fractional component (not just scale > 0)
243- // RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
244- // Values like 3.0 or 3.000 are valid integers despite positive scale, but 3.1 is not
245- if (value instanceof java .math .BigDecimal bd && bd .remainder (java .math .BigDecimal .ONE ).signum () != 0 ) {
246- return Jtd .Result .failure (Jtd .Error .EXPECTED_INTEGER .message ());
242+ // Handle precision loss for large Double values
243+ if (value instanceof Double d ) {
244+ if (d > Long .MAX_VALUE || d < Long .MIN_VALUE ) {
245+ String error = verboseErrors
246+ ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
247+ : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
248+ return Jtd .Result .failure (error );
249+ }
247250 }
248251
249252 // Convert to long for range checking
@@ -277,156 +280,64 @@ Jtd.Result validateInteger(JsonValue instance, String type, boolean verboseError
277280 return Jtd .Result .failure (error );
278281 }
279282
283+ private boolean hasFractionalComponent (Number value ) {
284+ if (value == null ) return false ;
285+ if (value instanceof Double d ) {
286+ return d != Math .floor (d );
287+ }
288+ if (value instanceof Float f ) {
289+ return f != Math .floor (f );
290+ }
291+ if (value instanceof java .math .BigDecimal bd ) {
292+ return bd .remainder (java .math .BigDecimal .ONE ).signum () != 0 ;
293+ }
294+ // Long, Integer, Short, Byte are always integers
295+ return false ;
296+ }
297+
280298 boolean validateIntegerWithFrame (Frame frame , String type , java .util .List <String > errors , boolean verboseErrors ) {
281299 JsonValue instance = frame .instance ();
282300 if (instance instanceof JsonNumber num ) {
283301 Number value = num .toNumber ();
284302
285- // Check if the number is not integral (has fractional part )
286- if (value instanceof Double d && d != Math . floor ( d )) {
303+ // Check for fractional component first (applies to all Number types )
304+ if (hasFractionalComponent ( value )) {
287305 String error = Jtd .Error .EXPECTED_INTEGER .message ();
288306 errors .add (Jtd .enrichedError (error , frame , instance ));
289307 return false ;
290308 }
291309
292- // Handle BigDecimal - check if it has fractional component (not just scale > 0)
293- // RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
294- // Values like 3.0 or 3.000 are valid integers despite positive scale, but 3.1 is not
295- if (value instanceof java .math .BigDecimal bd && bd .remainder (java .math .BigDecimal .ONE ).signum () != 0 ) {
296- String error = Jtd .Error .EXPECTED_INTEGER .message ();
297- errors .add (Jtd .enrichedError (error , frame , instance ));
298- return false ;
310+ // Handle precision loss for large Double values
311+ if (value instanceof Double d ) {
312+ if (d > Long .MAX_VALUE || d < Long .MIN_VALUE ) {
313+ String error = verboseErrors
314+ ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
315+ : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
316+ errors .add (Jtd .enrichedError (error , frame , instance ));
317+ return false ;
318+ }
299319 }
300320
301321 // Now check if the value is within range for the specific integer type
302- if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte ) {
303- long longValue = value .longValue ();
304- return switch (type ) {
305- case "int8" -> {
306- if (longValue >= -128 && longValue <= 127 ) yield true ;
307- String error = verboseErrors
308- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
309- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
310- errors .add (Jtd .enrichedError (error , frame , instance ));
311- yield false ;
312- }
313- case "uint8" -> {
314- if (longValue >= 0 && longValue <= 255 ) yield true ;
315- String error = verboseErrors
316- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
317- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
318- errors .add (Jtd .enrichedError (error , frame , instance ));
319- yield false ;
320- }
321- case "int16" -> {
322- if (longValue >= -32768 && longValue <= 32767 ) yield true ;
323- String error = verboseErrors
324- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
325- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
326- errors .add (Jtd .enrichedError (error , frame , instance ));
327- yield false ;
328- }
329- case "uint16" -> {
330- if (longValue >= 0 && longValue <= 65535 ) yield true ;
331- String error = verboseErrors
332- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
333- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
334- errors .add (Jtd .enrichedError (error , frame , instance ));
335- yield false ;
336- }
337- case "int32" -> {
338- if (longValue >= Integer .MIN_VALUE && longValue <= Integer .MAX_VALUE ) yield true ;
339- String error = verboseErrors
340- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
341- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
342- errors .add (Jtd .enrichedError (error , frame , instance ));
343- yield false ;
344- }
345- case "uint32" -> {
346- if (longValue >= 0 && longValue <= 4294967295L ) yield true ;
347- String error = verboseErrors
348- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
349- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
350- errors .add (Jtd .enrichedError (error , frame , instance ));
351- yield false ;
352- }
353- default -> true ;
354- };
355- }
322+ // Convert to long for range checking (works for all Number types)
323+ long longValue = value .longValue ();
324+ boolean inRange = switch (type ) {
325+ case "int8" -> longValue >= -128 && longValue <= 127 ;
326+ case "uint8" -> longValue >= 0 && longValue <= 255 ;
327+ case "int16" -> longValue >= -32768 && longValue <= 32767 ;
328+ case "uint16" -> longValue >= 0 && longValue <= 65535 ;
329+ case "int32" -> longValue >= Integer .MIN_VALUE && longValue <= Integer .MAX_VALUE ;
330+ case "uint32" -> longValue >= 0 && longValue <= 4294967295L ;
331+ default -> true ;
332+ };
356333
357- // For BigDecimal and other number types, check range
358- if (value instanceof java .math .BigDecimal bd ) {
359- return switch (type ) {
360- case "int8" -> {
361- try {
362- int intValue = bd .intValueExact ();
363- if (intValue >= -128 && intValue <= 127 ) yield true ;
364- } catch (ArithmeticException ignore ) {}
365- String error = verboseErrors
366- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
367- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
368- errors .add (Jtd .enrichedError (error , frame , instance ));
369- yield false ;
370- }
371- case "uint8" -> {
372- try {
373- int intValue = bd .intValueExact ();
374- if (intValue >= 0 && intValue <= 255 ) yield true ;
375- } catch (ArithmeticException ignore ) {}
376- String error = verboseErrors
377- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
378- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
379- errors .add (Jtd .enrichedError (error , frame , instance ));
380- yield false ;
381- }
382- case "int16" -> {
383- try {
384- int intValue = bd .intValueExact ();
385- if (intValue >= -32768 && intValue <= 32767 ) yield true ;
386- } catch (ArithmeticException ignore ) {}
387- String error = verboseErrors
388- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
389- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
390- errors .add (Jtd .enrichedError (error , frame , instance ));
391- yield false ;
392- }
393- case "uint16" -> {
394- try {
395- int intValue = bd .intValueExact ();
396- if (intValue >= 0 && intValue <= 65535 ) yield true ;
397- } catch (ArithmeticException ignore ) {}
398- String error = verboseErrors
399- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
400- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
401- errors .add (Jtd .enrichedError (error , frame , instance ));
402- yield false ;
403- }
404- case "int32" -> {
405- try {
406- int intValue = bd .intValueExact ();
407- yield true ;
408- } catch (ArithmeticException ignore ) {}
409- String error = verboseErrors
410- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
411- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
412- errors .add (Jtd .enrichedError (error , frame , instance ));
413- yield false ;
414- }
415- case "uint32" -> {
416- try {
417- long longValue = bd .longValueExact ();
418- if (longValue >= 0 && longValue <= 4294967295L ) yield true ;
419- } catch (ArithmeticException ignore ) {}
420- String error = verboseErrors
421- ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
422- : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
423- errors .add (Jtd .enrichedError (error , frame , instance ));
424- yield false ;
425- }
426- default -> true ;
427- };
334+ if (!inRange ) {
335+ String error = verboseErrors
336+ ? Jtd .Error .EXPECTED_NUMERIC_TYPE .message (instance , type , "out of range" )
337+ : Jtd .Error .EXPECTED_NUMERIC_TYPE .message (type , "out of range" );
338+ errors .add (Jtd .enrichedError (error , frame , instance ));
339+ return false ;
428340 }
429-
430341 return true ;
431342 }
432343 String error = verboseErrors
0 commit comments