Skip to content

Commit daf46ed

Browse files
committed
fix integers
1 parent ed4c0bf commit daf46ed

File tree

2 files changed

+332
-142
lines changed

2 files changed

+332
-142
lines changed

json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java

Lines changed: 53 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)