هذه المكتبة حاليًا في وضع الصيانة وتم استبدالها بـ erosb/json-sKema.
هذا المستودع ولن يرى أي ميزات جديدة. وهو يوفر دعمًا قويًا لإصدارات Draft-04 و Draft-06 و Draft-07 من مواصفات مخطط JSON.
يتم دعم أحدث مسودة 2020-12 فقط بواسطة erosb/json-sKema.
هذا المشروع هو تطبيق لمواصفات JSON Schema Draft v4 و Draft v6 و Draft v7. يستخدم واجهة برمجة التطبيقات org.json (التي أنشأها دوجلاس كروكفورد) لتمثيل بيانات JSON.
لنفترض أنك تعرف بالفعل ما هو مخطط JSON، وتريد استخدامه في تطبيق Java للتحقق من صحة بيانات JSON. ولكن - كما قد تكون اكتشفت بالفعل - يوجد أيضًا تطبيق Java آخر لمواصفات مخطط JSON. لذا إليك بعض النصائح حول أي منها يجب استخدامه:
أضف التبعية التالية إلى pom.xml
الخاص بك:
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema</ artifactId >
< version >1.14.4</ version >
</ dependency >
ملاحظة حول الإصدارات الأقدم : لا يمكن العثور على الإصدارات بين 1.6.0
و 1.9.1
إلا على JitPack بإحداثيات com.github.everit-org.json-schema:org.everit.json.schema
. الإصدارات 1.0.0
... 1.5.1
متاحة على Maven Central ضمن إحداثيات org.everit.json:org.everit.json.schema
.
كانت هناك محاولتان لجعل المكتبة تعمل على Java 6/7.
تم تطوير منفذ Java6 الإصدار 1.9.2 بواسطة @mindbender1 ويمكن الوصول إليه من خلال Maven Central بالإحداثيات التالية:
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema-jdk6</ artifactId >
< version >1.9.2</ version >
</ dependency >
Backports للإصدارات القديمة:
com.doctusoft:json-schema-java7:1.4.1
com.github.rdruilhe.json-schema:org.everit.json.schema:1.1.1
import org . everit . json . schema . Schema ;
import org . everit . json . schema . loader . SchemaLoader ;
import org . json . JSONObject ;
import org . json . JSONTokener ;
// ...
try ( InputStream inputStream = getClass (). getResourceAsStream ( "/path/to/your/schema.json" )) {
JSONObject rawSchema = new JSONObject ( new JSONTokener ( inputStream ));
Schema schema = SchemaLoader . load ( rawSchema );
schema . validate ( new JSONObject ( "{ " hello " : " world " }" )); // throws a ValidationException if this object is invalid
}
يحتوي مخطط JSON حاليًا على 4 إصدارات رئيسية، المسودة 3، المسودة 4، المسودة 6 والمسودة 7. تنفذ هذه المكتبة الإصدارات الثلاثة الأحدث، ويمكنك إلقاء نظرة سريعة على الاختلافات هنا وهنا. نظرًا لوجود عدد من الاختلافات بين الإصدارين - والمسودة 6 غير متوافقة مع المسودة 4 - فمن الجيد معرفة الإصدار الذي ستستخدمه.
أفضل طريقة للإشارة إلى إصدار مخطط JSON الذي تريد استخدامه هي تضمين عنوان URL للمخطط التعريفي الخاص به في جذر المستند باستخدام مفتاح "$schema"
. هذا تدوين شائع، يتم تسهيله بواسطة المكتبة لتحديد الإصدار الذي يجب استخدامه.
مرجع سريع:
"$schema": "http://json-schema.org/draft-04/schema"
في جذر المخطط، فسيتم استخدام المسودة 4"$schema": "http://json-schema.org/draft-06/schema"
في جذر المخطط، فسيتم استخدام المسودة 6"$schema": "http://json-schema.org/draft-07/schema"
في جذر المخطط، فسيتم استخدام المسودة 7إذا كنت تريد تحديد إصدار المخطط التعريفي بشكل صريح، فيمكنك تغيير الإعداد الافتراضي من المسودة 4 إلى المسودة 6/7 عن طريق تكوين أداة التحميل بهذه الطريقة:
SchemaLoader loader = SchemaLoader . builder ()
. schemaJson ( yourSchemaJSON )
. draftV6Support () // or draftV7Support()
. build ();
Schema schema = loader . load (). build ();
بدءًا من الإصدار 1.1.0
، يقوم المدقق بجمع كل انتهاكات المخطط (بدلاً من الفشل فورًا في الانتهاكات الأولى). تتم الإشارة إلى كل فشل بواسطة مؤشر JSON، يشير من جذر المستند إلى الجزء المخالف. إذا تم اكتشاف أكثر من انتهاك للمخطط، فسيتم طرح ValidationException
على العناصر الرئيسية الأكثر شيوعًا للانتهاكات، ويمكن الحصول على كل انتهاكات منفصلة باستخدام طريقة ValidationException#getCausingExceptions()
.
لتوضيح المفاهيم المذكورة أعلاه، دعونا نرى مثالا. لنفكر في المخطط التالي:
{
"type" : " object " ,
"properties" : {
"rectangle" : { "$ref" : " #/definitions/Rectangle " }
},
"definitions" : {
"size" : {
"type" : " number " ,
"minimum" : 0
},
"Rectangle" : {
"type" : " object " ,
"properties" : {
"a" : { "$ref" : " #/definitions/size " },
"b" : { "$ref" : " #/definitions/size " }
}
}
}
}
يحتوي مستند JSON التالي على انتهاك واحد فقط للمخطط (نظرًا لأن "a" لا يمكن أن يكون سالبًا):
{
"rectangle" : {
"a" : -5 ,
"b" : 5
}
}
في هذه الحالة، سيشير ValidationException
الذي تم طرحه إلى #/rectangle/a
ولن يحتوي على استثناءات فرعية:
try {
schema . validate ( rectangleSingleFailure );
} catch ( ValidationException e ) {
// prints #/rectangle/a: -5.0 is not higher or equal to 0
System . out . println ( e . getMessage ());
}
الآن - لتوضيح الطريقة التي يتم بها التعامل مع الانتهاكات المتعددة - دعنا نفكر في مستند JSON التالي، حيث تنتهك الخاصيتين "a" و"b" المخطط أعلاه:
{
"rectangle" : {
"a" : -5 ,
"b" : " asd "
}
}
في هذه الحالة، سيشير ValidationException
الذي تم طرحه إلى #/rectangle
، وله استثناءان فرعيان، يشيران إلى #/rectangle/a
و #/rectangle/b
:
try {
schema . validate ( rectangleMultipleFailures );
} catch ( ValidationException e ) {
System . out . println ( e . getMessage ());
e . getCausingExceptions (). stream ()
. map ( ValidationException :: getMessage )
. forEach ( System . out :: println );
}
سيؤدي هذا إلى طباعة الإخراج التالي:
#/rectangle: 2 schema violations found
#/rectangle/a: -5.0 is not higher or equal to 0
#/rectangle/b: expected type: Number, found: String
منذ الإصدار 1.4.0
، من الممكن طباعة مثيلات ValidationException
كتقارير فشل بتنسيق JSON. تقوم طريقة ValidationException#toJSON()
بإرجاع نسخة JSONObject
بالمفاتيح التالية:
"message"
: رسالة الاستثناء الملائمة للمبرمجين (وصف فشل التحقق من الصحة)"keyword"
: الكلمة الأساسية لمخطط JSON التي تم انتهاكها"pointerToViolation"
: مؤشر JSON يشير إلى المسار من جذر مستند الإدخال إلى الجزء الخاص به والذي تسبب في فشل التحقق من الصحة"schemaLocation"
: مؤشر JSON يشير إلى المسار من جذر مخطط JSON إلى الكلمة الأساسية المخالفة"causingExceptions"
: مصفوفة (ربما فارغة) من الاستثناءات الفرعية. يتم تمثيل كل استثناء فرعي ككائن JSON، بنفس البنية الموضحة في هذه القائمة. انظر المزيد أعلاه حول التسبب في الاستثناءات. يرجى الأخذ في الاعتبار أن تقرير الفشل الكامل عبارة عن بنية شجرة هرمية : يمكن الحصول على الأسباب الفرعية للسبب باستخدام #getCausingExceptions()
.
يمكن أن يخدم ValidationListener
s غرض حل الغموض حول كيفية مطابقة (أو عدم تطابق) مثيل JSON مع المخطط. يمكنك إرفاق تطبيق ValidationListener
بالمدقق لتلقي إشعارات الأحداث حول نتائج النجاح/الفشل المتوسطة.
مثال:
import org . everit . json . schema . Validator ;
...
Validator validator = Validator . builder ()
. withListener ( new YourValidationListenerImplementation ())
. build ();
validator . performValidation ( schema , input );
الأحداث المدعومة حاليا:
"$ref"
"allOf"
/ "anyOf"
/ "oneOf"
"allOf"
/ "anyOf"
/ "oneOf"
في المطابقة"if"
"if"
في المطابقة"then"
"then"
في المطابقة"else"
"else"
في المطابقة راجع javadoc لواجهة org.everit.json.schema.event.ValidationListener
لمزيد من التفاصيل. تحتوي فئات الأحداث المحددة أيضًا على تطبيقات #toJSON()
و #toString()
المناسبة حتى تتمكن من طباعتها بتنسيق قابل للتحليل بسهولة.
بشكل افتراضي، يتم الإبلاغ عن أخطاء التحقق من الصحة في وضع التجميع (راجع فصل "التحقيق في حالات الفشل"). يعد ذلك مناسبًا للحصول على تقرير تفصيلي عن الأخطاء، ولكن في بعض الظروف يكون من المناسب إيقاف التحقق من الصحة عند العثور على فشل دون التحقق من بقية مستند JSON. لتبديل وضع التحقق من الصحة الذي يفشل بسرعة
Validator
بشكل صريح لمخططك بدلاً من استدعاء Schema#validate(input)
failEarly()
الخاصة بـ ValidatorBuilder
مثال:
import org . everit . json . schema . Validator ;
...
Validator validator = Validator . builder ()
. failEarly ()
. build ();
validator . performValidation ( schema , input );
ملاحظة: فئة Validator
غير قابلة للتغيير وآمنة لسلسلة العمليات، لذلك لا يتعين عليك إنشاء فئة جديدة لكل عملية تحقق، يكفي تكوينها مرة واحدة فقط.
في بعض الحالات، عند التحقق من صحة الأرقام أو القيم المنطقية، يكون من المنطقي قبول قيم السلسلة القابلة للتحليل كقيم أولية، لأن أي معالجة متتالية ستقوم أيضًا بتحليل هذه القيم الحرفية تلقائيًا إلى قيم رقمية ومنطقية مناسبة. كما أن القيم الأولية غير النصية هي تافهة للتحويل إلى سلاسل، فلماذا لا نسمح بأي من أوليات json كسلاسل؟
على سبيل المثال، لنأخذ هذا المخطط:
{
"properties" : {
"booleanProp" : {
"type" : " boolean "
},
"integerProp" : {
"type" : " integer "
},
"nullProp" : {
"type" : " null "
},
"numberProp" : {
"type" : " number "
},
"stringProp" : {
"type" : " string "
}
}
}
فشل التحقق من صحة مستند JSON التالي، على الرغم من إمكانية تحويل جميع السلاسل بسهولة إلى قيم مناسبة:
{
"numberProp" : " 12.34 " ,
"integerProp" : " 12 " ,
"booleanProp" : " true " ,
"nullProp" : " null " ,
"stringProp" : 12.34
}
في هذه الحالة، إذا كنت تريد أن يقوم المثيل أعلاه بتمرير التحقق من الصحة مقابل المخطط، فستحتاج إلى استخدام تكوين التحقق البدائي المتساهل قيد التشغيل. مثال:
import org . everit . json . schema .*;
...
Validator validator = Validator . builder ()
. primitiveValidationStrategry ( PrimitiveValidationStrategy . LENIENT )
. build ();
validator . performValidation ( schema , input );
ملحوظة: في وضع التحليل المتساهل، سيتم قبول جميع القيم المنطقية المنطقية المحتملة البالغ عددها 22 كقيم منطقية.
تحدد مواصفات مخطط JSON الكلمة الأساسية "الافتراضية" للإشارة إلى القيم الافتراضية، على الرغم من أنها لا تنص صراحةً على كيفية تأثيرها على عملية التحقق من الصحة. افتراضيًا، لا تقوم هذه المكتبة بتعيين القيم الافتراضية، ولكن إذا كنت بحاجة إلى هذه الميزة، فيمكنك تشغيلها باستخدام طريقة SchemaLoaderBuilder#useDefaults(boolean)
، قبل تحميل المخطط:
{
"properties" : {
"prop" : {
"type" : " number " ,
"default" : 1
}
}
}
JSONObject input = new JSONObject ( "{}" );
System . out . println ( input . get ( "prop" )); // prints null
Schema schema = SchemaLoader . builder ()
. useDefaults ( true )
. schemaJson ( rawSchema )
. build ()
. load (). build ();
schema . validate ( input );
System . out . println ( input . get ( "prop" )); // prints 1
إذا كانت هناك بعض الخصائص المفقودة من input
والتي لها قيم "default"
في المخطط، فسيتم تعيينها بواسطة المدقق أثناء التحقق من الصحة.
لدعم الكلمة الأساسية "regex"
لمخطط JSON، تقدم المكتبة تطبيقين محتملين:
java.util.regex
على الرغم من أن مكتبة RE2J توفر أداءً أفضل بكثير من java.util.regex
، إلا أنها غير متوافقة تمامًا مع الصيغة المدعومة بواسطة java.util
أو ECMA 262. لذا يوصى بـ RE2J إذا كنت مهتمًا بالأداء وكانت حدوده مقبولة.
يمكن تنشيط تطبيق RE2J من خلال استدعاء SchemaLoaderBuilder#regexpFactory()
:
SchemaLoader loader = SchemaLoader . builder ()
. regexpFactory ( new RE2JRegexpFactory ())
// ...
. build ();
ملحوظات:
pom.xml
الخاص بك حتى لا يزيد حجم القطعة الأثرية الخاصة بك دون داعjava.util
، وفي 1.8.0 تم استخدام تطبيق RE2J، وفي 1.9.0 جعلناه قابلاً للتكوين، بسبب بعض التراجعات المبلغ عنها. تدعم المكتبة الكلمات الرئيسية readOnly
writeOnly
التي ظهرت لأول مرة في المسودة 7. إذا كنت تريد استخدام هذه الميزة، فقبل التحقق من الصحة، يتعين عليك إخبار المدقق إذا حدث التحقق في سياق القراءة أو الكتابة. مثال:
مخطط.json:
{
"properties" : {
"id" : {
"type" : " number " ,
"readOnly" : true
}
}
}
مقتطف رمز التحقق:
Validator validator = Validator . builder ()
. readWriteContext ( ReadWriteContext . WRITE )
. build ();
validator . performValidation ( schema , new JSONObject ( "{ " id " :42}" ));
في هذه الحالة، أخبرنا المدقق أن التحقق من الصحة يحدث في سياق WRITE
، وفي كائن JSON المُدخل، تظهر الخاصية "id"
، والتي تم وضع علامة "readOnly"
عليها في المخطط، وبالتالي فإن هذا الاستدعاء سيطرح ValidationException
.
بدءًا من الإصدار 1.2.0
تدعم المكتبة الكلمة الأساسية "format"
(والتي تعد جزءًا اختياريًا من المواصفات).
تختلف التنسيقات المدعومة وفقًا لإصدار مواصفات المخطط الذي تستخدمه (نظرًا لأنه تم تقديم التنسيقات القياسية في إصدارات مختلفة في مواصفات التحقق من الصحة).
فيما يلي جدول التوافق للتنسيقات القياسية المدعومة:
المسودة 4 | المسودة 6 | المسودة 7 | |
---|---|---|---|
التاريخ والوقت | ✅ | ✅ | ✅ |
بريد إلكتروني | ✅ | ✅ | ✅ |
اسم المضيف | ✅ | ✅ | ✅ |
IPv4 | ✅ | ✅ | ✅ |
IPv6 | ✅ | ✅ | ✅ |
أوري | ✅ | ✅ | ✅ |
مرجع uri | ✅ | ✅ | |
uri-template | ✅ | ✅ | |
مؤشر json | ✅ | ✅ | |
تاريخ | ✅ | ||
وقت | ✅ | ||
regex | ✅ | ||
مؤشر json النسبي | ✅ |
تدعم المكتبة أيضًا إضافة أدوات التحقق من التنسيق المخصص. لاستخدام أداة التحقق المخصصة بشكل أساسي عليك القيام بذلك
org.everit.json.schema.FormatValidator
org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder
قبل تحميل المخطط الفعليلنفترض أن المهمة هي إنشاء مدقق مخصص يقبل السلاسل ذات عدد زوجي من الأحرف.
سيبدو FormatValidator
المخصص كما يلي:
public class EvenCharNumValidator implements FormatValidator {
@ Override
public Optional < String > validate ( final String subject ) {
if ( subject . length () % 2 == 0 ) {
return Optional . empty ();
} else {
return Optional . of ( String . format ( "the length of string [%s] is odd" , subject ));
}
}
}
لربط EvenCharNumValidator
بقيمة "format"
(على سبيل المثال "evenlength"
)، يجب عليك ربط نسخة أداة التحقق من الصحة بالكلمة الأساسية في تكوين أداة تحميل المخطط:
JSONObject rawSchema = new JSONObject ( new JSONTokener ( inputStream ));
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( rawSchema ) // rawSchema is the JSON representation of the schema utilizing the "evenlength" non-standard format
. addFormatValidator ( "evenlength" , new EvenCharNumValidator ()) // the EvenCharNumValidator gets bound to the "evenlength" keyword
. build ();
Schema schema = schemaLoader . load (). build (); // the schema is created using the above created configuration
schema . validate ( jsonDocument ); // the document validation happens here
في مستند مخطط JSON، من الممكن استخدام عناوين URI النسبية للإشارة إلى الأنواع المحددة مسبقًا. يتم التعبير عن هذه المراجع باستخدام الكلمات الرئيسية "$ref"
و "$id"
. على الرغم من أن المواصفات تصف تغيير نطاق الدقة وإلغاء الإسناد بالتفصيل، إلا أنها لا تشرح السلوك المتوقع عندما يكون أول "$ref"
أو "$id"
عبارة عن URI نسبي.
في حالة هذا التنفيذ، من الممكن تحديد URI مطلق بشكل صريح ليكون بمثابة URI الأساسي (نطاق الحل) باستخدام طريقة الإنشاء المناسبة:
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( jsonSchema )
. resolutionScope ( "http://example.org/" ) // setting the default resolution scope
. build ();
مع نمو مخططاتك، ستحتاج إلى تقسيمها إلى ملفات مصدر متعددة وتوصيلها بمراجع "$ref"
. إذا كنت تريد تخزين المخططات على مسار الفصل (بدلاً من تقديمها عبر HTTP على سبيل المثال)، فإن الطريقة الموصى بها هي استخدام بروتوكول classpath:
لجعل المخططات تشير إلى بعضها البعض. لجعل مسار classpath:
البروتوكول يعمل:
SchemaClient
المدمج في المكتبة، على سبيل المثال: SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaClient ( SchemaClient . classPathAwareClient ())
. schemaJson ( jsonSchema )
. resolutionScope ( "classpath://my/schemas/directory/" ) // setting the default resolution scope
. build ();
بالنظر إلى هذا التكوين، سيتم حل المراجع التالية بشكل صحيح في jsonSchema
:
{
"properties" : {
"sameDir" : { "$ref" : " sameDirSchema.json " },
"absPath" : { "$ref" : " classpath://somewhere/else/otherschema.json " },
"httpPath" : { "$ref" : " http://example.org/http-works-as-usual " },
}
}
وسيتم البحث عن sameDirSchema.json
في /my/schemas/directory/sameDirSchema.json
على مسار الفصل.
في بعض الأحيان يكون من المفيد العمل مع المخططات المحملة مسبقًا، والتي نخصص لها عنوان URI عشوائيًا (ربما uuid) بدلاً من تحميل المخطط من خلال عنوان URL. يمكن القيام بذلك عن طريق تعيين المخططات إلى URI باستخدام طريقة #registerSchemaByURI()
الخاصة بمحمل المخطط. مثال:
SchemaLoader schemaLoader = SchemaLoader . builder ()
. registerSchemaByURI ( new URI ( "urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63" ), aJSONObject )
. registerSchemaByURI ( new URI ( "http://example.org" ), otherJSONObject )
. schemaJson ( jsonSchema )
. resolutionScope ( "classpath://my/schemas/directory/" )
. build ();
ملحوظات:
JSONObject
أو Boolean
(نوع المعلمة الرسمي هو Object
فقط لأن هذين الكائنين ليس لهما أي فئة فائقة مشتركة أخرى).SchemaClient
الخاص بك يعمل أيضًا، أو يمكنك حتى الاستفادة من معالجة البروتوكول القابل للتوسيع لحزمة java.net
) يمكن استبعاد بعض التبعيات من المكتبة، وتظل قابلة للاستخدام، مع بعض القيود:
com.damnhandy:handy-uri-templates
، فيجب ألا يستخدم مخططك تنسيق "uri-template"
commons-validator:commons-validator
، فيجب ألا يستخدم مخططك التنسيقات التالية: "email"
و "ipv4"
و "ipv6"
و "hostname"
حسب نسخة المكتبة:
يتوفر javadoc الذي تم إنشاؤه للإصدارات 1.0.0 - 1.5.1 على javadoc.io
بالنسبة للإصدارات ما بين (1.6.0 - 1.9.1) لم يتم نشرها في أي مكان.