From e4497d27388214bad4abe994f5dd0720d609e459 Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Tue, 17 May 2022 18:35:12 +0800 Subject: [PATCH] support json schema #239 --- .../benchmark/schema/JSONSchemaBenchmark.java | 10 +- .../com/alibaba/fastjson2/JSONObject.java | 43 +- .../com/alibaba/fastjson2/JSONSchema.java | 398 ++++++++++++------ .../reader/FieldReaderFloatValueMethod.java | 8 + .../com/alibaba/fastjson2/JSONSchemaTest.java | 10 + 5 files changed, 337 insertions(+), 132 deletions(-) diff --git a/benchmark/src/main/java/com/alibaba/fastjson2/benchmark/schema/JSONSchemaBenchmark.java b/benchmark/src/main/java/com/alibaba/fastjson2/benchmark/schema/JSONSchemaBenchmark.java index c9b75bd351..f870c30cf6 100644 --- a/benchmark/src/main/java/com/alibaba/fastjson2/benchmark/schema/JSONSchemaBenchmark.java +++ b/benchmark/src/main/java/com/alibaba/fastjson2/benchmark/schema/JSONSchemaBenchmark.java @@ -12,6 +12,7 @@ public class JSONSchemaBenchmark { final static JSONSchema SCHEMA_DATE = JSONObject.of("type", "string", "format", "date").to(JSONSchema::of); final static JSONSchema SCHEMA_TIME = JSONObject.of("type", "string", "format", "time").to(JSONSchema::of); final static JSONSchema SCHEMA_NUMBER = JSONObject.of("type", "number", "minimum", 10).to(JSONSchema::of); + final static JSONSchema SCHEMA_INTEGER = JSONObject.of("type", "integer", "minimum", 10).to(JSONSchema::of); @Benchmark public void format_uuid(Blackhole bh) { @@ -43,12 +44,17 @@ public void format_time(Blackhole bh) { public static void format_perf() { long start = System.currentTimeMillis(); - for (int i = 0; i < 1000 * 1000 * 10; ++i) { + for (int i = 0; i < 1000 * 1000 * 100; ++i) { // SCHEMA_UUID.isValid("a7f41390-39a9-4ca6-a13b-88cf07a41108"); // SCHEMA_DATETIME.isValid("2017-07-21 12:13:14"); // 123 // SCHEMA_DATE.isValid("2017-07-21"); // 48 // SCHEMA_TIME.isValid("12:13:14"); // - SCHEMA_NUMBER.isValid(9); // +// SCHEMA_NUMBER.isValid(9); // 42 +// SCHEMA_NUMBER.isValid(11); // 302 120 +// SCHEMA_NUMBER.isValid(11D); // + SCHEMA_NUMBER.isValid(9D); // +// SCHEMA_INTEGER.isValid(9); // 87 +// SCHEMA_INTEGER.isValid(11); // } long millis = System.currentTimeMillis() - start; System.out.println("millis : " + millis); diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONObject.java b/core/src/main/java/com/alibaba/fastjson2/JSONObject.java index 7db98598a1..76e3a11275 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONObject.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONObject.java @@ -155,9 +155,7 @@ public boolean containsKey(Object key) { */ @SuppressWarnings("unchecked") public Object getOrDefault(String key, Object defaultValue) { - return super.getOrDefault( - key, defaultValue - ); + return super.getOrDefault(key, defaultValue); } /** @@ -515,6 +513,43 @@ public long getLongValue(String key) { throw new JSONException("Can not cast '" + value.getClass() + "' to long value"); } + /** + * Returns a long value of the associated keys in this {@link JSONObject}. + * + * @param key the key whose associated value is to be returned + * @param defaultValue the default mapping of the key + * @return long + * @throws NumberFormatException If the value of get is {@link String} and it contains no parsable long + * @throws JSONException Unsupported type conversion to long value + */ + public long getLongValue(String key, long defaultValue) { + Object value = super.get(key); + + if (value == null) { + return defaultValue; + } + + if (value instanceof Number) { + return ((Number) value).longValue(); + } + + if (value instanceof String) { + String str = (String) value; + + if (str.isEmpty() || "null".equalsIgnoreCase(str)) { + return defaultValue; + } + + if (str.indexOf('.') != -1) { + return (long) Double.parseDouble(str); + } + + return Long.parseLong(str); + } + + throw new JSONException("Can not cast '" + value.getClass() + "' to long value"); + } + /** * Returns the {@link Integer} of the associated keys in this {@link JSONObject}. * @@ -615,7 +650,7 @@ public int getIntValue(String key, int defaultValue) { String str = (String) value; if (str.isEmpty() || "null".equalsIgnoreCase(str)) { - return 0; + return defaultValue; } if (str.indexOf('.') != -1) { diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONSchema.java b/core/src/main/java/com/alibaba/fastjson2/JSONSchema.java index 2b82ab56ff..caa83c80d2 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONSchema.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONSchema.java @@ -181,6 +181,26 @@ public boolean isValid(long value) { .isSuccess(); } + public boolean isValid(double value) { + return validate(value) + .isSuccess(); + } + + public boolean isValid(Double value) { + return validate(value) + .isSuccess(); + } + + public boolean isValid(float value) { + return validate(value) + .isSuccess(); + } + + public boolean isValid(Float value) { + return validate(value) + .isSuccess(); + } + public boolean isValid(Integer value) { return validate(value) .isSuccess(); @@ -195,6 +215,18 @@ public ValidateResult validate(long value) { return validate((Object) Long.valueOf(value)); } + public ValidateResult validate(double value) { + return validate((Object) Double.valueOf(value)); + } + + public ValidateResult validate(Float value) { + return validate((Object) value); + } + + public ValidateResult validate(Double value) { + return validate((Object) value); + } + public ValidateResult validate(Integer value) { return validate((Object) value); } @@ -227,6 +259,22 @@ public void assertValidate(Long value) { throw new JSONSchemaValidException(result.getMessage()); } + public void assertValidate(Double value) { + ValidateResult result = validate(value); + if (result.isSuccess()) { + return; + } + throw new JSONSchemaValidException(result.getMessage()); + } + + public void assertValidate(Float value) { + ValidateResult result = validate(value); + if (result.isSuccess()) { + return; + } + throw new JSONSchemaValidException(result.getMessage()); + } + public void assertValidate(long value) { ValidateResult result = validate(value); if (result.isSuccess()) { @@ -235,6 +283,14 @@ public void assertValidate(long value) { throw new JSONSchemaValidException(result.getMessage()); } + public void assertValidate(double value) { + ValidateResult result = validate(value); + if (result.isSuccess()) { + return; + } + throw new JSONSchemaValidException(result.getMessage()); + } + public enum Type { Null, Boolean, @@ -774,35 +830,44 @@ public int hashCode() { } static final class IntegerSchema extends JSONSchema { - final Long minimum; - final Long exclusiveMinimum; - final Long maximum; - final Long exclusiveMaximum; - final Long multipleOf; + final long minimum; + final boolean exclusiveMinimum; + + final long maximum; + final boolean exclusiveMaximum; + + final long multipleOf; IntegerSchema(JSONObject input) { super(input); Object exclusiveMinimum = input.get("exclusiveMinimum"); - Long minimum = input.getLong("minimum"); + + long minimum = input.getLongValue("minimum", Long.MIN_VALUE); if (exclusiveMinimum == Boolean.TRUE) { - this.minimum = null; - this.exclusiveMinimum = minimum; + this.exclusiveMinimum = true; + this.minimum = minimum; + } else if (exclusiveMinimum instanceof Number) { + this.exclusiveMinimum = true; + this.minimum = input.getLongValue("exclusiveMinimum"); } else { this.minimum = minimum; - this.exclusiveMinimum = input.getLong("exclusiveMinimum");; + this.exclusiveMinimum = false; } - Long maximum = input.getLong("maximum"); + long maximum = input.getLongValue("maximum", Long.MIN_VALUE); Object exclusiveMaximum = input.get("exclusiveMaximum"); if (exclusiveMaximum == Boolean.TRUE) { - this.maximum = null; - this.exclusiveMaximum = maximum; + this.exclusiveMaximum = true; + this.maximum = maximum; + } else if (exclusiveMaximum instanceof Number) { + this.exclusiveMaximum = true; + this.maximum = input.getLongValue("exclusiveMaximum"); } else { + this.exclusiveMaximum = false; this.maximum = maximum; - this.exclusiveMaximum = input.getLong("exclusiveMaximum"); } - this.multipleOf = input.getLong("multipleOf"); + this.multipleOf = input.getLongValue("multipleOf", 0); } @Override @@ -825,37 +890,23 @@ public ValidateResult validate(Object value) { || valueClass == AtomicInteger.class || valueClass == AtomicLong.class ) { - if (minimum != null) { - long longValue = ((Number) value).longValue(); - if (longValue < minimum.longValue()) { - return new MinimumFail(minimum , value, false); - } - } - - if (exclusiveMinimum != null) { + if (minimum != Long.MIN_VALUE) { long longValue = ((Number) value).longValue(); - if (longValue <= exclusiveMinimum.longValue()) { - return new MinimumFail(exclusiveMinimum , value, true); + if (exclusiveMinimum ? longValue <= minimum : longValue < minimum) { + return new MinimumFail(minimum , value, exclusiveMinimum); } } - if (maximum != null) { + if (maximum != Long.MIN_VALUE) { long longValue = ((Number) value).longValue(); - if (longValue > maximum.longValue()) { - return new MaximumFail(maximum , value, false); + if (exclusiveMaximum ? longValue >= maximum : longValue > maximum) { + return new MaximumFail(maximum , value, exclusiveMaximum); } } - if (exclusiveMaximum != null) { + if (multipleOf != 0) { long longValue = ((Number) value).longValue(); - if (longValue >= exclusiveMaximum.longValue()) { - return new MaximumFail(exclusiveMaximum , value, true); - } - } - - if (multipleOf != null) { - long longValue = ((Number) value).longValue(); - if (longValue % multipleOf.longValue() != 0) { + if (longValue % multipleOf != 0) { return new MultipleOfFail(multipleOf, (Number) value); } } @@ -867,32 +918,20 @@ public ValidateResult validate(Object value) { @Override public ValidateResult validate(long longValue) { - if (minimum != null) { - if (longValue < minimum.longValue()) { - return new MinimumFail(minimum , longValue, false); - } - } - - if (exclusiveMinimum != null) { - if (longValue <= exclusiveMinimum.longValue()) { - return new MinimumFail(exclusiveMinimum , longValue, true); - } - } - - if (maximum != null) { - if (longValue > maximum.longValue()) { - return new MaximumFail(maximum , longValue, false); + if (minimum != Long.MIN_VALUE) { + if (exclusiveMinimum ? longValue <= minimum : longValue < minimum) { + return new MinimumFail(minimum , longValue, exclusiveMinimum); } } - if (exclusiveMaximum != null) { - if (longValue >= exclusiveMaximum.longValue()) { - return new MaximumFail(exclusiveMaximum , longValue, true); + if (maximum != Long.MIN_VALUE) { + if (exclusiveMaximum ? longValue >= maximum : longValue > maximum) { + return new MaximumFail(maximum , longValue, exclusiveMaximum); } } - if (multipleOf != null) { - if (longValue % multipleOf.longValue() != 0) { + if (multipleOf != 0) { + if (longValue % multipleOf != 0) { return new MultipleOfFail(multipleOf, longValue); } } @@ -906,32 +945,20 @@ public ValidateResult validate(Long value) { } long longValue = value.longValue(); - if (minimum != null) { - if (longValue < minimum.longValue()) { - return new MinimumFail(minimum , value, false); - } - } - - if (exclusiveMinimum != null) { - if (longValue <= exclusiveMinimum.longValue()) { - return new MinimumFail(exclusiveMinimum , value, true); + if (minimum != Long.MIN_VALUE) { + if (exclusiveMinimum ? longValue <= minimum : longValue < minimum) { + return new MinimumFail(minimum , value, exclusiveMinimum); } } - if (maximum != null) { - if (longValue > maximum.longValue()) { - return new MaximumFail(maximum , value, false); + if (maximum != Long.MIN_VALUE) { + if (exclusiveMaximum ? longValue >= maximum : longValue > maximum) { + return new MaximumFail(maximum , value, exclusiveMaximum); } } - if (exclusiveMaximum != null) { - if (longValue >= exclusiveMaximum.longValue()) { - return new MaximumFail(exclusiveMaximum , value, true); - } - } - - if (multipleOf != null) { - if (longValue % multipleOf.longValue() != 0) { + if (multipleOf != 0) { + if (longValue % multipleOf != 0) { return new MultipleOfFail(multipleOf, longValue); } } @@ -945,32 +972,20 @@ public ValidateResult validate(Integer value) { } long longValue = value.longValue(); - if (minimum != null) { - if (longValue < minimum.longValue()) { - return new MinimumFail(minimum , value, false); + if (minimum != Long.MIN_VALUE) { + if (exclusiveMinimum ? longValue <= minimum : longValue < minimum) { + return new MinimumFail(minimum , value, exclusiveMinimum); } } - if (exclusiveMinimum != null) { - if (longValue <= exclusiveMinimum.longValue()) { - return new MinimumFail(exclusiveMinimum , value, true); + if (maximum != Long.MIN_VALUE) { + if (exclusiveMaximum ? longValue >= maximum : longValue > maximum) { + return new MaximumFail(maximum , value, exclusiveMaximum); } } - if (maximum != null) { - if (longValue > maximum.longValue()) { - return new MaximumFail(maximum , value, false); - } - } - - if (exclusiveMaximum != null) { - if (longValue >= exclusiveMaximum.longValue()) { - return new MaximumFail(exclusiveMaximum , value, true); - } - } - - if (multipleOf != null) { - if (longValue % multipleOf.longValue() != 0) { + if (multipleOf != 0) { + if (longValue % multipleOf != 0) { return new MultipleOfFail(multipleOf, longValue); } } @@ -1000,9 +1015,13 @@ public int hashCode() { static final class NumberSchema extends JSONSchema { final BigDecimal minimum; - final BigDecimal exclusiveMinimum; + final long minimumLongValue; + final boolean exclusiveMinimum; + final BigDecimal maximum; - final BigDecimal exclusiveMaximum; + final long maximumLongValue; + final boolean exclusiveMaximum; + final BigInteger multipleOf; NumberSchema(JSONObject input) { @@ -1011,22 +1030,41 @@ static final class NumberSchema extends JSONSchema { Object exclusiveMinimum = input.get("exclusiveMinimum"); BigDecimal minimum = input.getBigDecimal("minimum"); if (exclusiveMinimum == Boolean.TRUE) { - this.minimum = null; - this.exclusiveMinimum = minimum; + this.minimum = minimum; + this.exclusiveMinimum = true; + } else if (exclusiveMinimum instanceof Number) { + this.minimum = input.getBigDecimal("exclusiveMinimum"); + this.exclusiveMinimum = true; } else { this.minimum = minimum; - this.exclusiveMinimum = input.getBigDecimal("exclusiveMinimum");; + this.exclusiveMinimum = false; + } + + if (this.minimum == null || !this.minimum.equals(BigDecimal.valueOf(this.minimum.longValue()))) { + minimumLongValue = Long.MIN_VALUE; + } else { + minimumLongValue = this.minimum.longValue(); } BigDecimal maximum = input.getBigDecimal("maximum"); Object exclusiveMaximum = input.get("exclusiveMaximum"); if (exclusiveMaximum == Boolean.TRUE) { - this.maximum = null; - this.exclusiveMaximum = maximum; + this.maximum = maximum; + this.exclusiveMaximum = true; + } else if (exclusiveMaximum instanceof Number) { + this.maximum = input.getBigDecimal("exclusiveMaximum"); + this.exclusiveMaximum = true; } else { this.maximum = maximum; - this.exclusiveMaximum = input.getBigDecimal("exclusiveMaximum"); + this.exclusiveMaximum = false; + } + + if (this.maximum == null || !this.maximum.equals(BigDecimal.valueOf(this.maximum.longValue()))) { + maximumLongValue = Long.MIN_VALUE; + } else { + maximumLongValue = this.maximum.longValue(); } + this.multipleOf = input.getBigInteger("multipleOf"); } @@ -1044,12 +1082,17 @@ public ValidateResult validate(Object value) { if (value instanceof Number) { Number number = (Number) value; - BigDecimal decimalValue; + if (number instanceof Byte || number instanceof Short || number instanceof Integer || number instanceof Long) { - decimalValue = BigDecimal.valueOf(number.longValue()); - } else if (number instanceof Float || number instanceof Double) { - decimalValue = BigDecimal.valueOf((double) number.doubleValue()); - } else if (number instanceof BigInteger) { + return validate(number.longValue()); + } + + if (number instanceof Float || number instanceof Double) { + return validate(number.doubleValue()); + } + + BigDecimal decimalValue; + if (number instanceof BigInteger) { decimalValue = new BigDecimal((BigInteger) number); } else if (number instanceof BigDecimal) { decimalValue = (BigDecimal) number; @@ -1058,33 +1101,25 @@ public ValidateResult validate(Object value) { } if (minimum != null) { - if (minimum.compareTo(decimalValue) > 0) { - return new MinimumFail(minimum, number, false); - } - } - - if (exclusiveMinimum != null) { - if (exclusiveMinimum.compareTo(decimalValue) >= 0) { - return new MinimumFail(exclusiveMinimum, number, true); + if (exclusiveMinimum + ? minimum.compareTo(decimalValue) >= 0 + : minimum.compareTo(decimalValue) > 0) { + return new MinimumFail(minimum, decimalValue, exclusiveMinimum); } } if (maximum != null) { - if (maximum.compareTo(decimalValue) < 0) { - return new MaximumFail(maximum , minimum, false); - } - } - - if (exclusiveMaximum != null) { - if (exclusiveMaximum.compareTo(decimalValue) <= 0) { - return new MaximumFail(exclusiveMaximum , minimum, true); + if (exclusiveMaximum + ? maximum.compareTo(decimalValue) <= 0 + : maximum.compareTo(decimalValue) < 0) { + return new MaximumFail(maximum, decimalValue, exclusiveMaximum); } } if (multipleOf != null) { BigInteger bigInteger = decimalValue.toBigInteger(); if (!decimalValue.equals(new BigDecimal(bigInteger)) || !bigInteger.mod(multipleOf).equals(BigInteger.ZERO)) { - return new MultipleOfFail(multipleOf, number); + return new MultipleOfFail(multipleOf, decimalValue); } } return SUCCESS; @@ -1093,6 +1128,117 @@ public ValidateResult validate(Object value) { return new TypeNotMatchFail(Type.Number, value.getClass()); } + @Override + public ValidateResult validate(Integer value) { + if (value == null) { + return FAIL_INPUT_NULL; + } + + return validate(value.longValue()); + } + + public ValidateResult validate(Float value) { + if (value == null) { + return FAIL_INPUT_NULL; + } + + return validate(value.doubleValue()); + } + + public ValidateResult validate(Double value) { + if (value == null) { + return FAIL_INPUT_NULL; + } + + return validate(value.doubleValue()); + } + + @Override + public ValidateResult validate(Long value) { + if (value == null) { + return FAIL_INPUT_NULL; + } + + return validate(value.longValue()); + } + + @Override + public ValidateResult validate(long value) { + BigDecimal decimalValue = BigDecimal.valueOf(value); + + if (minimum != null) { + if (minimumLongValue != Long.MIN_VALUE) { + if (exclusiveMinimum ? value <= minimumLongValue : value < minimumLongValue) { + return new MinimumFail(minimum, decimalValue, exclusiveMinimum); + } + } else { + if (exclusiveMinimum + ? minimum.compareTo(decimalValue) >= 0 + : minimum.compareTo(decimalValue) > 0) { + return new MinimumFail(minimum, decimalValue, exclusiveMaximum); + } + } + } + + if (maximum != null) { + if (maximumLongValue != Long.MIN_VALUE) { + if (exclusiveMaximum ? value >= maximumLongValue : value > maximumLongValue) { + return new MaximumFail(maximum, minimum, exclusiveMinimum); + } + } else if (exclusiveMaximum + ? maximum.compareTo(decimalValue) <= 0 + : maximum.compareTo(decimalValue) < 0) { + return new MaximumFail(maximum, minimum, exclusiveMaximum); + } + } + + if (multipleOf != null) { + BigInteger bigInteger = decimalValue.toBigInteger(); + if (!decimalValue.equals(new BigDecimal(bigInteger)) || !bigInteger.mod(multipleOf).equals(BigInteger.ZERO)) { + return new MultipleOfFail(multipleOf, decimalValue); + } + } + return SUCCESS; + } + + @Override + public ValidateResult validate(double value) { + + if (minimum != null) { + if (minimumLongValue != Long.MIN_VALUE) { + if (exclusiveMinimum ? value <= minimumLongValue : value < minimumLongValue) { + return new MinimumFail(minimum, value, exclusiveMinimum); + } + } else { + double minimumDoubleValue = minimum.doubleValue(); + if (exclusiveMinimum ? value <= minimumDoubleValue : value < minimumDoubleValue) { + return new MinimumFail(minimum, value, exclusiveMinimum); + } + } + } + + if (maximum != null) { + if (maximumLongValue != Long.MIN_VALUE) { + if (exclusiveMaximum ? value >= maximumLongValue : value > maximumLongValue) { + return new MaximumFail(maximum, value, exclusiveMinimum); + } + } else { + double maximumDoubleValue = maximum.doubleValue(); + if (exclusiveMaximum ? value >= maximumDoubleValue : value > maximumDoubleValue) { + return new MaximumFail(maximum, value, exclusiveMinimum); + } + } + } + + if (multipleOf != null) { + long multipleOfLongValue = multipleOf.longValue(); + if (value % multipleOfLongValue != 0) { + return new MultipleOfFail(multipleOf, value); + } + } + return SUCCESS; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloatValueMethod.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloatValueMethod.java index 57871e1025..57152c1bc2 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloatValueMethod.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloatValueMethod.java @@ -34,6 +34,10 @@ public void accept(T object, Object value) { value = 0F; } + if (schema != null) { + schema.assertValidate(value); + } + try { method.invoke(object, value); } catch (Exception e) { @@ -43,6 +47,10 @@ public void accept(T object, Object value) { @Override public void accept(T object, int value) { + if (schema != null) { + schema.assertValidate(value); + } + try { method.invoke(object, (float) value); } catch (Exception e) { diff --git a/core/src/test/java/com/alibaba/fastjson2/JSONSchemaTest.java b/core/src/test/java/com/alibaba/fastjson2/JSONSchemaTest.java index 6e97b2f1d3..c37ed4be1c 100644 --- a/core/src/test/java/com/alibaba/fastjson2/JSONSchemaTest.java +++ b/core/src/test/java/com/alibaba/fastjson2/JSONSchemaTest.java @@ -183,6 +183,12 @@ public void testString_format_time() { jsonSchema.isValid("1970-02-01 12:13:14")); assertFalse(jsonSchema.isValid("(888)555-1212 ext. 532")); assertFalse(jsonSchema.isValid("(800)FLOWERS")); + assertFalse( + jsonSchema.isValid(1F)); + assertFalse( + jsonSchema.isValid(Float.valueOf(1))); + assertFalse( + jsonSchema.isValid(Double.valueOf(1))); } @Test @@ -439,6 +445,10 @@ public void testNumber_exclusiveMaximum() { 9)); assertFalse(jsonSchema.isValid(10)); assertFalse(jsonSchema.isValid(11)); + assertFalse(jsonSchema.isValid(11D)); + assertFalse(jsonSchema.isValid(Double.valueOf(11))); + assertFalse(jsonSchema.isValid(11F)); + assertFalse(jsonSchema.isValid(Float.valueOf(11))); } @Test