Initial commit.

This commit contains the third major design of a C++ library for JSON Schema validation.

It is definitely not what I would consider production-ready, but I do think that the overall design of the library is robust.
This commit is contained in:
Tristan Penman 2013-10-30 07:51:11 +11:00
commit 4c9864de73
237 changed files with 80055 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin
xcode/valijson.xcodeproj/project.xcworkspace
xcode/valijson.xcodeproj/project.xcworkspace/xcuserdata
xcode/valijson.xcodeproj/xcuserdata
doc/html

1869
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2013, Tristan Penman
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

57
Makefile Normal file
View File

@ -0,0 +1,57 @@
CPPFLAGS = \
-ggdb \
-O0 \
-Iinclude \
-Ithirdparty/gtest-1.6.0 \
-Ithirdparty/gtest-1.6.0/src \
-Ithirdparty/gtest-1.6.0/include \
-Ithirdparty/jsoncpp-0.5.0/include \
-Ithirdparty/rapidjson-0.1/include \
-I/usr/local/include \
-DBOOST_NO_CXX11_SMART_PTR \
-DGTEST_USE_OWN_TR1_TUPLE \
-L/usr/local/lib -lboost_regex-mt
TEST_SRCS = tests/test_adapter_comparison.cpp \
tests/test_dereference_callback.cpp \
tests/test_jsoncpp_adapter.cpp \
tests/test_property_tree_adapter.cpp \
tests/test_rapidjson_adapter.cpp \
tests/test_uri_resolution.cpp \
tests/test_validation_errors.cpp \
tests/test_validator.cpp
JSONCPP_SRCS = \
thirdparty/jsoncpp-0.5.0/src/lib_json/json_reader.cpp \
thirdparty/jsoncpp-0.5.0/src/lib_json/json_value.cpp \
thirdparty/jsoncpp-0.5.0/src/lib_json/json_writer.cpp
GTEST_SRCS = \
thirdparty/gtest-1.6.0/src/gtest-death-test.cc \
thirdparty/gtest-1.6.0/src/gtest-filepath.cc \
thirdparty/gtest-1.6.0/src/gtest-port.cc \
thirdparty/gtest-1.6.0/src/gtest-printers.cc \
thirdparty/gtest-1.6.0/src/gtest-test-part.cc \
thirdparty/gtest-1.6.0/src/gtest-typed-test.cc \
thirdparty/gtest-1.6.0/src/gtest.cc \
thirdparty/gtest-1.6.0/src/gtest_main.cc
.PHONY: check
check: bin bin/test_suite
cd bin && ./test_suite
examples: bin bin/custom_schema bin/external_schema
bin:
mkdir -p bin
bin/custom_schema:
g++ $(CPPFLAGS) examples/custom_schema.cpp -o bin/custom_schema
bin/external_schema:
g++ $(CPPFLAGS) examples/external_schema.cpp -o bin/external_schema
bin/test_suite:
g++ $(CPPFLAGS) $(TEST_SRCS) $(JSONCPP_SRCS) $(GTEST_SRCS) -o bin/test_suite

80
README.md Normal file
View File

@ -0,0 +1,80 @@
Valijson
========
Overview
--------
Valijson is a header-only JSON Schema Validation library for C++.
Valijson provides a simple validation API that allows you load schemas, and
validate documents created by one of several supported parser libraries.
License
-------
Valijson is licensed under the Simplified BSD License. See the LICENSE file
for more information.
Supported parsers
-----------------
Valijson supports JsonCpp, RapidJson and Boost Property Trees. It has been
tested against the following versions of these libraries:
- jsoncpp 0.5.0
- rapidjson 0.1
- boost::property_tree 1.54
Dependencies
------------
Required:
- boost 1.54 (earlier versions may work, but have not been tested)
Optional
- gtest 1.6.0 (for building unit tests)
- jsoncpp 0.5.0 (for use of the JsonCppAdapter class)
- rapidjson 0.1 (for use of the RapidJsonAdapter class)
JSON Schema Support
-------------------
Valijson supports most of the constraints defined in Drafts 3 and 4 of the JSON
Schema specification.
The exceptions for Draft 3 are:
- disallow
- divisibleBy
- extends
- format (optional)
- readonly
- ref
- refRemote
The exceptions for Draft 4 are:
- definitions
- format (optional)
- multipleOf
- ref
- refRemote
Support for JSON References is in development.
Build instructions
------------------
A rudimentary Makefile has been included. Running 'make examples' will build
the example programs in the 'examples' directory. Running 'make check' will
build and run the gtest-based unit tests. Executables will be placed in 'bin'.
An Xcode 5 project has also been provided, in the 'xcode' directory. Note that
in order to run the test suite, you may need to configure the working directory
for the 'test_suite' scheme.
The Xcode project has been configured so that /usr/local/include is in the
include path, and /usr/local/lib is in the library path. These are the
locations that homebrew installed Boost on my test system.
Doxygen documentation can be built by running 'doxygen' from the project root
directory. Generated documentation will be placed in 'doc/html'. Other relevant
documentation such as schemas and specifications have been included in the 'doc'
directory.

174
doc/schema/draft-03.json Normal file
View File

@ -0,0 +1,174 @@
{
"$schema" : "http://json-schema.org/draft-03/schema#",
"id" : "http://json-schema.org/draft-03/schema#",
"type" : "object",
"properties" : {
"type" : {
"type" : ["string", "array"],
"items" : {
"type" : ["string", {"$ref" : "#"}]
},
"uniqueItems" : true,
"default" : "any"
},
"properties" : {
"type" : "object",
"additionalProperties" : {"$ref" : "#"},
"default" : {}
},
"patternProperties" : {
"type" : "object",
"additionalProperties" : {"$ref" : "#"},
"default" : {}
},
"additionalProperties" : {
"type" : [{"$ref" : "#"}, "boolean"],
"default" : {}
},
"items" : {
"type" : [{"$ref" : "#"}, "array"],
"items" : {"$ref" : "#"},
"default" : {}
},
"additionalItems" : {
"type" : [{"$ref" : "#"}, "boolean"],
"default" : {}
},
"required" : {
"type" : "boolean",
"default" : false
},
"dependencies" : {
"type" : "object",
"additionalProperties" : {
"type" : ["string", "array", {"$ref" : "#"}],
"items" : {
"type" : "string"
}
},
"default" : {}
},
"minimum" : {
"type" : "number"
},
"maximum" : {
"type" : "number"
},
"exclusiveMinimum" : {
"type" : "boolean",
"default" : false
},
"exclusiveMaximum" : {
"type" : "boolean",
"default" : false
},
"minItems" : {
"type" : "integer",
"minimum" : 0,
"default" : 0
},
"maxItems" : {
"type" : "integer",
"minimum" : 0
},
"uniqueItems" : {
"type" : "boolean",
"default" : false
},
"pattern" : {
"type" : "string",
"format" : "regex"
},
"minLength" : {
"type" : "integer",
"minimum" : 0,
"default" : 0
},
"maxLength" : {
"type" : "integer"
},
"enum" : {
"type" : "array",
"minItems" : 1,
"uniqueItems" : true
},
"default" : {
"type" : "any"
},
"title" : {
"type" : "string"
},
"description" : {
"type" : "string"
},
"format" : {
"type" : "string"
},
"divisibleBy" : {
"type" : "number",
"minimum" : 0,
"exclusiveMinimum" : true,
"default" : 1
},
"disallow" : {
"type" : ["string", "array"],
"items" : {
"type" : ["string", {"$ref" : "#"}]
},
"uniqueItems" : true
},
"extends" : {
"type" : [{"$ref" : "#"}, "array"],
"items" : {"$ref" : "#"},
"default" : {}
},
"id" : {
"type" : "string",
"format" : "uri"
},
"$ref" : {
"type" : "string",
"format" : "uri"
},
"$schema" : {
"type" : "string",
"format" : "uri"
}
},
"dependencies" : {
"exclusiveMinimum" : "minimum",
"exclusiveMaximum" : "maximum"
},
"default" : {}
}

150
doc/schema/draft-04.json Normal file
View File

@ -0,0 +1,150 @@
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uri"
},
"$schema": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,280 @@
Internet Engineering Task Force P. Bryan, Ed.
Internet-Draft Salesforce.com
Intended status: Informational K. Zyp
Expires: March 20, 2013 SitePen (USA)
September 16, 2012
JSON Reference
draft-pbryan-zyp-json-ref-03
Abstract
JSON Reference allows a JSON value to reference another value in a
JSON document.
Status of this Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute
working documents as Internet-Drafts. The list of current Internet-
Drafts is at http://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on March 20, 2013.
Copyright Notice
Copyright (c) 2012 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
carefully, as they describe your rights and restrictions with respect
to this document. Code Components extracted from this document must
include Simplified BSD License text as described in Section 4.e of
the Trust Legal Provisions and are provided without warranty as
described in the Simplified BSD License.
Bryan & Zyp Expires March 20, 2013 [Page 1]
Internet-Draft JSON Reference September 2012
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
2. Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . 3
3. Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4. Resolution . . . . . . . . . . . . . . . . . . . . . . . . . . 3
5. Error Handling . . . . . . . . . . . . . . . . . . . . . . . . 4
6. IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 4
7. Security Considerations . . . . . . . . . . . . . . . . . . . . 4
8. Normative References . . . . . . . . . . . . . . . . . . . . . 4
Appendix A. Acknowledgements . . . . . . . . . . . . . . . . . . . 4
Appendix B. Examples . . . . . . . . . . . . . . . . . . . . . . . 5
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 5
Bryan & Zyp Expires March 20, 2013 [Page 2]
Internet-Draft JSON Reference September 2012
1. Introduction
This specification defines a JSON [RFC4627] structure which allows a
JSON value to reference another value in a JSON document. This
provides the basis for transclusion in JSON: the use of a target
resource as an effective substitute for the reference.
2. Conventions
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in [RFC2119].
3. Syntax
A JSON Reference is a JSON object, which contains a member named
"$ref", which has a JSON string value. Example:
{ "$ref": "http://example.com/example.json#/foo/bar" }
If a JSON value does not have these characteristics, then it SHOULD
NOT be interpreted as a JSON Reference.
The "$ref" string value contains a URI [RFC3986], which identifies
the location of the JSON value being referenced. It is an error
condition if the string value does not conform to URI syntax rules.
Any members other than "$ref" in a JSON Reference object SHALL be
ignored.
4. Resolution
Resolution of a JSON Reference object SHOULD yield the referenced
JSON value. Implementations MAY choose to replace the reference with
the referenced value.
If the URI contained in the JSON Reference value is a relative URI,
then the base URI resolution MUST be calculated according to
[RFC3986], section 5.2. Resolution is performed relative to the
referring document.
If a URI contains a fragment identifier, then the fragment should be
resolved per the fragment resolution mechansim of the referrant
document. If the representation of the referrant document is JSON,
then the fragment identifier SHOULD be interpreted as a
[JSON-Pointer].
Bryan & Zyp Expires March 20, 2013 [Page 3]
Internet-Draft JSON Reference September 2012
5. Error Handling
In the event of an error condition, evaluation of the JSON Reference
SHOULD fail to complete.
6. IANA Considerations
This draft includes no request to IANA.
7. Security Considerations
A JSON Reference is not guaranteed to resolve to a JSON value.
Implementations of this specification SHOULD take appropriate
precautions.
Documents containing JSON References can be structured to resolve
cyclically. Implementations SHOULD include appropriate checks to
prevent such structures from resulting in infinite recursion or
iteration.
8. Normative References
[JSON-Pointer]
Bryan, P., Zyp, K., and M. Nottingham, "JSON Pointer",
draft-ietf-appsawg-json-pointer-04 (work in progress),
September 2012.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
Resource Identifier (URI): Generic Syntax", STD 66,
RFC 3986, January 2005.
[RFC4627] Crockford, D., "The application/json Media Type for
JavaScript Object Notation (JSON)", RFC 4627, July 2006.
Appendix A. Acknowledgements
The following individuals contributed ideas, feedback and wording to
this specification:
Bob Aman, Francis Galiegue.
Bryan & Zyp Expires March 20, 2013 [Page 4]
Internet-Draft JSON Reference September 2012
Appendix B. Examples
TBD.
Authors' Addresses
Paul C. Bryan (editor)
Salesforce.com
Phone: +1 604 783 1481
Email: pbryan@anode.ca
Kris Zyp
SitePen (USA)
Phone: +1 650 968 8787
Email: kris@sitepen.com
Bryan & Zyp Expires March 20, 2013 [Page 5]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,784 @@
Internet Engineering Task Force F. Galiegue, Ed.
Internet-Draft
Intended status: Informational K. Zyp, Ed.
Expires: August 4, 2013 SitePen (USA)
G. Court
January 31, 2013
JSON Schema: core definitions and terminology
draft-zyp-json-schema-04
Abstract
JSON Schema defines the media type "application/schema+json", a JSON
based format for defining the structure of JSON data. JSON Schema
provides a contract for what JSON data is required for a given
application and how to interact with it. JSON Schema is intended to
define validation, documentation, hyperlink navigation, and
interaction control of JSON data.
Status of This Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute
working documents as Internet-Drafts. The list of current Internet-
Drafts is at http://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on August 4, 2013.
Copyright Notice
Copyright (c) 2013 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
carefully, as they describe your rights and restrictions with respect
to this document. Code Components extracted from this document must
Galiegue, et al. Expires August 4, 2013 [Page 1]
Internet-Draft JSON Schema January 2013
include Simplified BSD License text as described in Section 4.e of
the Trust Legal Provisions and are provided without warranty as
described in the Simplified BSD License.
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
2. Conventions and Terminology . . . . . . . . . . . . . . . . . 3
3. Core terminology . . . . . . . . . . . . . . . . . . . . . . . 3
3.1. Property, item . . . . . . . . . . . . . . . . . . . . . . 3
3.2. JSON Schema, keywords . . . . . . . . . . . . . . . . . . 3
3.3. Empty schema . . . . . . . . . . . . . . . . . . . . . . . 3
3.4. Root schema, subschema . . . . . . . . . . . . . . . . . . 4
3.5. JSON Schema primitive types . . . . . . . . . . . . . . . 4
3.6. JSON value equality . . . . . . . . . . . . . . . . . . . 5
3.7. Instance . . . . . . . . . . . . . . . . . . . . . . . . . 5
4. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.1. Validation . . . . . . . . . . . . . . . . . . . . . . . . 5
4.2. Hypermedia and linking . . . . . . . . . . . . . . . . . . 6
5. General considerations . . . . . . . . . . . . . . . . . . . . 6
5.1. Applicability to all JSON values . . . . . . . . . . . . . 6
5.2. Programming language independence . . . . . . . . . . . . 6
5.3. JSON Schema and HTTP . . . . . . . . . . . . . . . . . . . 6
5.4. JSON Schema and other protocols . . . . . . . . . . . . . 6
5.5. Mathematical integers . . . . . . . . . . . . . . . . . . 7
5.6. Extending JSON Schema . . . . . . . . . . . . . . . . . . 7
5.7. Security considerations . . . . . . . . . . . . . . . . . 7
6. The "$schema" keyword . . . . . . . . . . . . . . . . . . . . 7
6.1. Purpose . . . . . . . . . . . . . . . . . . . . . . . . . 7
6.2. Customization . . . . . . . . . . . . . . . . . . . . . . 8
7. URI resolution scopes and dereferencing . . . . . . . . . . . 8
7.1. Definition . . . . . . . . . . . . . . . . . . . . . . . . 8
7.2. URI resolution scope alteration with the "id" keyword . . 8
7.2.1. Valid values . . . . . . . . . . . . . . . . . . . . . 8
7.2.2. Usage . . . . . . . . . . . . . . . . . . . . . . . . 9
7.2.3. Canonical dereferencing and inline dereferencing . . . 10
7.2.4. Inline dereferencing and fragments . . . . . . . . . . 11
7.3. Interoperability considerations . . . . . . . . . . . . . 11
8. Recommended correlation mechanisms for use with the HTTP
protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
8.1. Correlation by means of the "Content-Type" header . . . . 11
8.2. Correlation by means of the "Link" header . . . . . . . . 12
9. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 12
10. References . . . . . . . . . . . . . . . . . . . . . . . . . . 12
10.1. Normative References . . . . . . . . . . . . . . . . . . . 12
10.2. Informative References . . . . . . . . . . . . . . . . . . 12
Appendix A. ChangeLog . . . . . . . . . . . . . . . . . . . . . . 13
Galiegue, et al. Expires August 4, 2013 [Page 2]
Internet-Draft JSON Schema January 2013
1. Introduction
JSON Schema is a JSON media type for defining the structure of JSON
data. JSON Schema provides a contract for what JSON data is required
for a given application and how to interact with it. JSON Schema is
intended to define validation, documentation, hyperlink navigation,
and interaction control of JSON data.
This specification defines JSON Schema core terminology and
mechanisms; related specifications build upon this specification and
define different applications of JSON Schema.
2. Conventions and Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
The terms "JSON", "JSON text", "JSON value", "member", "element",
"object", "array", "number", "string", "boolean", "true", "false",
and "null" in this document are to be interpreted as defined in RFC
4627 [RFC4627].
3. Core terminology
3.1. Property, item
When refering to a JSON Object, as defined by [RFC4627], the terms
"member" and "property" may be used interchangeably.
When refering to a JSON Array, as defined by [RFC4627], the terms
"element" and "item" may be used interchangeably.
3.2. JSON Schema, keywords
A JSON Schema is a JSON document, and that document MUST be an
object. Object members (or properties) defined by JSON Schema (this
specification, or related specifications) are called keywords, or
schema keywords.
A JSON Schema MAY contain properties which are not schema keywords.
3.3. Empty schema
An empty schema is a JSON Schema with no properties, or with
properties which are not schema keywords.
Galiegue, et al. Expires August 4, 2013 [Page 3]
Internet-Draft JSON Schema January 2013
3.4. Root schema, subschema
This example of a JSON Schema has no subschemas:
{
"title": "root"
}
JSON Schemas can also be nested, as in this example:
{
"title": "root",
"otherSchema": {
"title": "nested",
"anotherSchema": {
"title": "alsoNested"
}
}
}
In this example, "nested" and "alsoNested" are subschemas, and "root"
is a root schema.
3.5. JSON Schema primitive types
JSON Schema defines seven primitive types for JSON values:
array A JSON array.
boolean A JSON boolean.
integer A JSON number without a fraction or exponent part.
number Any JSON number. Number includes integer.
null The JSON null value.
object A JSON object.
string A JSON string.
Galiegue, et al. Expires August 4, 2013 [Page 4]
Internet-Draft JSON Schema January 2013
3.6. JSON value equality
Two JSON values are said to be equal if and only if:
both are nulls; or
both are booleans, and have the same value; or
both are strings, and have the same value; or
both are numbers, and have the same mathematical value; or
both are arrays, and:
have the same number of items; and
items at the same index are equal according to this definition;
or
both are objects, and:
have the same set of property names; and
values for a same property name are equal according to this
definition.
3.7. Instance
An instance is any JSON value. An instance may be described by one
or more schemas.
An instance may also be referred to as "JSON instance", or "JSON
data".
4. Overview
This document proposes a new media type "application/schema+json" to
identify JSON Schema for describing JSON data. JSON Schemas are
themselves written in JSON. This, and related specifications, define
keywords allowing to describe this data in terms of allowable values,
textual descriptions and interpreting relations with other resources.
The following sections are a summary of features defined by related
specifications.
4.1. Validation
JSON Schema allows applications to validate instances, either non
interactively or interactively. For instance, an application may
Galiegue, et al. Expires August 4, 2013 [Page 5]
Internet-Draft JSON Schema January 2013
collect JSON data and check that this data matches a given set of
constraints; another application may use a JSON Schema to build an
interactive interface in order to collect user input according to
constraints described by JSON Schema.
4.2. Hypermedia and linking
JSON Schema provides a method for extracting link relations from
instances to other resources, as well as describing interpretations
of instances as multimedia data. This allows JSON data to be
interpreted as rich hypermedia documents, placed in the context of a
larger set of related resources.
5. General considerations
5.1. Applicability to all JSON values
It is acknowledged that an instance may be any valid JSON value as
defined by [RFC4627]. As such, JSON Schema does not mandate that an
instance be of a particular type: JSON Schema can describe any JSON
value, including null.
5.2. Programming language independence
JSON Schema is programming language agnostic. The only limitations
are the ones expressed by [RFC4627] and those of the host programming
language.
5.3. JSON Schema and HTTP
This specification acknowledges the role of HTTP [RFC2616] as the
dominant protocol in use on the Internet, and the wealth of official
specifications related to it.
This specification uses a subset of these specifications to recommend
a set of mechanisms, usable by this protocol, to associate JSON
instances to one or more schemas.
5.4. JSON Schema and other protocols
JSON Schema does not define any semantics for the client-server
interface for any other protocols than HTTP. These semantics are
application dependent, or subject to agreement between the parties
involved in the use of JSON Schema for their own needs.
Galiegue, et al. Expires August 4, 2013 [Page 6]
Internet-Draft JSON Schema January 2013
5.5. Mathematical integers
It is acknowledged by this specification that some programming
languages, and their associated parsers, use different internal
representations for floating point numbers and integers, while others
do not.
As a consequence, for interoperability reasons, JSON values used in
the context of JSON Schema, whether that JSON be a JSON Schema or an
instance, SHOULD ensure that mathematical integers be represented as
integers as defined by this specification.
5.6. Extending JSON Schema
Implementations MAY choose to define additional keywords to JSON
Schema. Save for explicit agreement, schema authors SHALL NOT expect
these additional keywords to be supported by peer implementations.
Implementations SHOULD ignore keywords they do not support.
5.7. Security considerations
Both schemas and instances are JSON values. As such, all security
considerations defined in RFC 4627 [RFC4627] apply.
6. The "$schema" keyword
6.1. Purpose
The "$schema" keyword is both used as a JSON Schema version
identifier and the location of a resource which is itself a JSON
Schema, which describes any schema written for this particular
version.
This keyword MUST be located at the root of a JSON Schema. The value
of this keyword MUST be a URI [RFC3986] and a valid JSON Reference
[json-reference]; this URI MUST be both absolute and normalized. The
resource located at this URI MUST successfully describe itself. It
is RECOMMENDED that schema authors include this keyword in their
schemas.
The following values are predefined:
http://json-schema.org/schema# JSON Schema written against the
current version of the specification.
Galiegue, et al. Expires August 4, 2013 [Page 7]
Internet-Draft JSON Schema January 2013
http://json-schema.org/hyper-schema# JSON Schema written against the
current version of the specification.
http://json-schema.org/draft-04/schema# JSON Schema written against
this version.
http://json-schema.org/draft-04/hyper-schema# JSON Schema
hyperschema written against this version.
http://json-schema.org/draft-03/schema# JSON Schema written against
JSON Schema, draft v3 [json-schema-03].
http://json-schema.org/draft-03/hyper-schema# JSON Schema
hyperschema written against JSON Schema, draft v3
[json-schema-03].
6.2. Customization
When extending JSON Schema with custom keywords, schema authors
SHOULD define a custom URI for "$schema". This custom URI MUST NOT
be one of the predefined values.
7. URI resolution scopes and dereferencing
7.1. Definition
JSON Schema uses JSON Reference [json-reference] as a mechanism for
schema addressing. It extends this specification in two ways:
JSON Schema offers facilities to alter the base URI against which
a reference must resolve by the means of the "id" keyword;
it defines a specific dereferencing mechanism extending JSON
Reference to accept arbitrary fragment parts.
Altering the URI within a schema is called defining a new resolution
scope. The initial resolution scope of a schema is the URI of the
schema itself, if any, or the empty URI if the schema was not loaded
from a URI.
7.2. URI resolution scope alteration with the "id" keyword
7.2.1. Valid values
The value for this keyword MUST be a string, and MUST be a valid URI.
This URI MUST be normalized, and SHOULD NOT be an empty fragment (#)
or the empty URI.
Galiegue, et al. Expires August 4, 2013 [Page 8]
Internet-Draft JSON Schema January 2013
7.2.2. Usage
The "id" keyword (or "id", for short) is used to alter the resolution
scope. When an id is encountered, an implementation MUST resolve
this id against the most immediate parent scope. The resolved URI
will be the new resolution scope for this subschema and all its
children, until another id is encountered.
When using "id" to alter resolution scopes, schema authors SHOULD
ensure that resolution scopes are unique within the schema.
This schema will be taken as an example:
{
"id": "http://x.y.z/rootschema.json#",
"schema1": {
"id": "#foo"
},
"schema2": {
"id": "otherschema.json",
"nested": {
"id": "#bar"
},
"alsonested": {
"id": "t/inner.json#a"
}
},
"schema3": {
"id": "some://where.else/completely#"
}
}
Subschemas at the following URI-encoded JSON Pointer [json-pointer]s
(starting from the root schema) define the following resolution
scopes:
# (document root) http://x.y.z/rootschema.json#
#/schema1 http://x.y.z/rootschema.json#foo
#/schema2 http://x.y.z/otherschema.json#
#/schema2/nested http://x.y.z/otherschema.json#bar
Galiegue, et al. Expires August 4, 2013 [Page 9]
Internet-Draft JSON Schema January 2013
#/schema2/alsonested http://x.y.z/t/inner.json#a
#/schema3 some://where.else/completely#
7.2.3. Canonical dereferencing and inline dereferencing
When resolving a URI against a resolution scope, an implementation
may choose two modes of operation:
canonical dereferencing The implementation dereferences all resolved
URIs.
inline dereferencing The implementation chooses to dereference URIs
within the schema.
Implementations MUST support canonical dereferencing, and MAY support
inline dereferencing.
For example, consider this schema:
{
"id": "http://my.site/myschema#",
"definitions": {
"schema1": {
"id": "schema1",
"type": "integer"
},
"schema2", {
"type": "array",
"items": { "$ref": "schema1" }
}
}
}
When an implementation encounters the "schema1" reference, it
resolves it against the most immediate parent scope, leading to URI
"http://my.site/schema1#". The way to process this URI will differ
according to the chosen dereferencing mode:
if canonical dereferencing is used, the implementation will
dereference this URI, and fetch the content at this URI;
if inline dereferencing is used, the implementation will notice
that URI scope "http://my.site/schema1#" is already defined within
the schema, and choose to use the appropriate subschema.
Galiegue, et al. Expires August 4, 2013 [Page 10]
Internet-Draft JSON Schema January 2013
7.2.4. Inline dereferencing and fragments
When using inline dereferencing, a resolution scope may lead to a URI
which has a non empty fragment part which is not a JSON Pointer, as
in this example:
{
"id": "http://some.site/schema#",
"not": { "$ref": "#inner" },
"definitions": {
"schema1": {
"id": "#inner",
"type": "boolean"
}
}
}
An implementation choosing to support inline dereferencing SHOULD be
able to use this kind of reference. Implementations choosing to use
canonical dereferencing, however, are not required to support it.
7.3. Interoperability considerations
Inline dereferencing can produce canonical URIs which differ from the
canonical URI of the root schema. Schema authors SHOULD ensure that
implementations using canonical dereferencing obtain the same content
as implementations using inline dereferencing.
Extended JSON References using fragments which are not JSON Pointers
are not dereferenceable by implementations choosing not to support
inline dereferencing. This kind of reference is defined for
backwards compatibility, and SHOULD NOT be used in new schemas.
8. Recommended correlation mechanisms for use with the HTTP protocol
It is acknowledged by this specification that the majority of
interactive JSON Schema processing will be over HTTP. This section
therefore gives recommendations for materializing an instance/schema
correlation using mechanisms currently available for this protocol.
An instance is said to be described by one (or more) schema(s).
8.1. Correlation by means of the "Content-Type" header
It is RECOMMENDED that a MIME type parameter by the name of "profile"
be appended to the "Content-Type" header of the instance being
processed. If present, the value of this parameter MUST be a valid
Galiegue, et al. Expires August 4, 2013 [Page 11]
Internet-Draft JSON Schema January 2013
URI, and this URI SHOULD resolve to a valid JSON Schema. The MIME
type MUST be "application/json", or any other subtype.
An example of such a header would be:
Content-Type: application/my-media-type+json;
profile=http://example.com/my-hyper-schema#
8.2. Correlation by means of the "Link" header
When using the "Link" header, the relation type used MUST be
"describedBy", as defined by RFC 5988, section 5.3 [RFC5988]. The
target URI of the "Link" header MUST be a valid JSON Schema.
An example of such a header would be:
Link: <http://example.com/my-hyper-schema#>; rel="describedBy"
9. IANA Considerations
The proposed MIME media type for JSON Schema is defined as follows:
type name: application;
subtype name: schema+json.
10. References
10.1. Normative References
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
10.2. Informative References
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
Masinter, L., Leach, P., and T. Berners-Lee,
"Hypertext Transfer Protocol -- HTTP/1.1",
RFC 2616, June 1999.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter,
"Uniform Resource Identifier (URI): Generic
Syntax", STD 66, RFC 3986, January 2005.
Galiegue, et al. Expires August 4, 2013 [Page 12]
Internet-Draft JSON Schema January 2013
[RFC4627] Crockford, D., "The application/json Media Type for
JavaScript Object Notation (JSON)", RFC 4627,
July 2006.
[RFC5988] Nottingham, M., "Web Linking", RFC 5988,
October 2010.
[json-reference] Bryan, P. and K. Zyp, "JSON Reference (work in
progress)", September 2012, <http://tools.ietf.org/
html/draft-pbryan-zyp-json-ref-03>.
[json-pointer] Bryan, P. and K. Zyp, "JSON Pointer (work in
progress)", September 2012, <http://tools.ietf.org/
html/draft-ietf-appsawg-json-pointer-07>.
[json-schema-03] Court, G. and K. Zyp, "JSON Schema, draft 3",
September 2012, <http://tools.ietf.org/html/
draft-zyp-json-schema-03>.
Appendix A. ChangeLog
draft-00
* Initial draft.
* Salvaged from draft v3.
* Mandate the use of JSON Reference, JSON Pointer.
* Define the role of "id". Define URI resolution scope.
* Add interoperability considerations.
Authors' Addresses
Francis Galiegue (editor)
EMail: fgaliegue@gmail.com
Galiegue, et al. Expires August 4, 2013 [Page 13]
Internet-Draft JSON Schema January 2013
Kris Zyp (editor)
SitePen (USA)
530 Lytton Avenue
Palo Alto, CA 94301
USA
Phone: +1 650 968 8787
EMail: kris@sitepen.com
Gary Court
Calgary, AB
Canada
EMail: gary.court@gmail.com
Galiegue, et al. Expires August 4, 2013 [Page 14]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,563 @@
Network Working Group D. Crockford
Request for Comments: 4627 JSON.org
Category: Informational July 2006
The application/json Media Type for JavaScript Object Notation (JSON)
Status of This Memo
This memo provides information for the Internet community. It does
not specify an Internet standard of any kind. Distribution of this
memo is unlimited.
Copyright Notice
Copyright (C) The Internet Society (2006).
Abstract
JavaScript Object Notation (JSON) is a lightweight, text-based,
language-independent data interchange format. It was derived from
the ECMAScript Programming Language Standard. JSON defines a small
set of formatting rules for the portable representation of structured
data.
1. Introduction
JavaScript Object Notation (JSON) is a text format for the
serialization of structured data. It is derived from the object
literals of JavaScript, as defined in the ECMAScript Programming
Language Standard, Third Edition [ECMA].
JSON can represent four primitive types (strings, numbers, booleans,
and null) and two structured types (objects and arrays).
A string is a sequence of zero or more Unicode characters [UNICODE].
An object is an unordered collection of zero or more name/value
pairs, where a name is a string and a value is a string, number,
boolean, null, object, or array.
An array is an ordered sequence of zero or more values.
The terms "object" and "array" come from the conventions of
JavaScript.
JSON's design goals were for it to be minimal, portable, textual, and
a subset of JavaScript.
Crockford Informational [Page 1]
RFC 4627 JSON July 2006
1.1. Conventions Used in This Document
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in [RFC2119].
The grammatical rules in this document are to be interpreted as
described in [RFC4234].
2. JSON Grammar
A JSON text is a sequence of tokens. The set of tokens includes six
structural characters, strings, numbers, and three literal names.
A JSON text is a serialized object or array.
JSON-text = object / array
These are the six structural characters:
begin-array = ws %x5B ws ; [ left square bracket
begin-object = ws %x7B ws ; { left curly bracket
end-array = ws %x5D ws ; ] right square bracket
end-object = ws %x7D ws ; } right curly bracket
name-separator = ws %x3A ws ; : colon
value-separator = ws %x2C ws ; , comma
Insignificant whitespace is allowed before or after any of the six
structural characters.
ws = *(
%x20 / ; Space
%x09 / ; Horizontal tab
%x0A / ; Line feed or New line
%x0D ; Carriage return
)
2.1. Values
A JSON value MUST be an object, array, number, or string, or one of
the following three literal names:
false null true
Crockford Informational [Page 2]
RFC 4627 JSON July 2006
The literal names MUST be lowercase. No other literal names are
allowed.
value = false / null / true / object / array / number / string
false = %x66.61.6c.73.65 ; false
null = %x6e.75.6c.6c ; null
true = %x74.72.75.65 ; true
2.2. Objects
An object structure is represented as a pair of curly brackets
surrounding zero or more name/value pairs (or members). A name is a
string. A single colon comes after each name, separating the name
from the value. A single comma separates a value from a following
name. The names within an object SHOULD be unique.
object = begin-object [ member *( value-separator member ) ]
end-object
member = string name-separator value
2.3. Arrays
An array structure is represented as square brackets surrounding zero
or more values (or elements). Elements are separated by commas.
array = begin-array [ value *( value-separator value ) ] end-array
2.4. Numbers
The representation of numbers is similar to that used in most
programming languages. A number contains an integer component that
may be prefixed with an optional minus sign, which may be followed by
a fraction part and/or an exponent part.
Octal and hex forms are not allowed. Leading zeros are not allowed.
A fraction part is a decimal point followed by one or more digits.
An exponent part begins with the letter E in upper or lowercase,
which may be followed by a plus or minus sign. The E and optional
sign are followed by one or more digits.
Numeric values that cannot be represented as sequences of digits
(such as Infinity and NaN) are not permitted.
Crockford Informational [Page 3]
RFC 4627 JSON July 2006
number = [ minus ] int [ frac ] [ exp ]
decimal-point = %x2E ; .
digit1-9 = %x31-39 ; 1-9
e = %x65 / %x45 ; e E
exp = e [ minus / plus ] 1*DIGIT
frac = decimal-point 1*DIGIT
int = zero / ( digit1-9 *DIGIT )
minus = %x2D ; -
plus = %x2B ; +
zero = %x30 ; 0
2.5. Strings
The representation of strings is similar to conventions used in the C
family of programming languages. A string begins and ends with
quotation marks. All Unicode characters may be placed within the
quotation marks except for the characters that must be escaped:
quotation mark, reverse solidus, and the control characters (U+0000
through U+001F).
Any character may be escaped. If the character is in the Basic
Multilingual Plane (U+0000 through U+FFFF), then it may be
represented as a six-character sequence: a reverse solidus, followed
by the lowercase letter u, followed by four hexadecimal digits that
encode the character's code point. The hexadecimal letters A though
F can be upper or lowercase. So, for example, a string containing
only a single reverse solidus character may be represented as
"\u005C".
Alternatively, there are two-character sequence escape
representations of some popular characters. So, for example, a
string containing only a single reverse solidus character may be
represented more compactly as "\\".
To escape an extended character that is not in the Basic Multilingual
Plane, the character is represented as a twelve-character sequence,
encoding the UTF-16 surrogate pair. So, for example, a string
containing only the G clef character (U+1D11E) may be represented as
"\uD834\uDD1E".
Crockford Informational [Page 4]
RFC 4627 JSON July 2006
string = quotation-mark *char quotation-mark
char = unescaped /
escape (
%x22 / ; " quotation mark U+0022
%x5C / ; \ reverse solidus U+005C
%x2F / ; / solidus U+002F
%x62 / ; b backspace U+0008
%x66 / ; f form feed U+000C
%x6E / ; n line feed U+000A
%x72 / ; r carriage return U+000D
%x74 / ; t tab U+0009
%x75 4HEXDIG ) ; uXXXX U+XXXX
escape = %x5C ; \
quotation-mark = %x22 ; "
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
3. Encoding
JSON text SHALL be encoded in Unicode. The default encoding is
UTF-8.
Since the first two characters of a JSON text will always be ASCII
characters [RFC0020], it is possible to determine whether an octet
stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
at the pattern of nulls in the first four octets.
00 00 00 xx UTF-32BE
00 xx 00 xx UTF-16BE
xx 00 00 00 UTF-32LE
xx 00 xx 00 UTF-16LE
xx xx xx xx UTF-8
4. Parsers
A JSON parser transforms a JSON text into another representation. A
JSON parser MUST accept all texts that conform to the JSON grammar.
A JSON parser MAY accept non-JSON forms or extensions.
An implementation may set limits on the size of texts that it
accepts. An implementation may set limits on the maximum depth of
nesting. An implementation may set limits on the range of numbers.
An implementation may set limits on the length and character contents
of strings.
Crockford Informational [Page 5]
RFC 4627 JSON July 2006
5. Generators
A JSON generator produces JSON text. The resulting text MUST
strictly conform to the JSON grammar.
6. IANA Considerations
The MIME media type for JSON text is application/json.
Type name: application
Subtype name: json
Required parameters: n/a
Optional parameters: n/a
Encoding considerations: 8bit if UTF-8; binary if UTF-16 or UTF-32
JSON may be represented using UTF-8, UTF-16, or UTF-32. When JSON
is written in UTF-8, JSON is 8bit compatible. When JSON is
written in UTF-16 or UTF-32, the binary content-transfer-encoding
must be used.
Security considerations:
Generally there are security issues with scripting languages. JSON
is a subset of JavaScript, but it is a safe subset that excludes
assignment and invocation.
A JSON text can be safely passed into JavaScript's eval() function
(which compiles and executes a string) if all the characters not
enclosed in strings are in the set of characters that form JSON
tokens. This can be quickly determined in JavaScript with two
regular expressions and calls to the test and replace methods.
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
Interoperability considerations: n/a
Published specification: RFC 4627
Crockford Informational [Page 6]
RFC 4627 JSON July 2006
Applications that use this media type:
JSON has been used to exchange data between applications written
in all of these programming languages: ActionScript, C, C#,
ColdFusion, Common Lisp, E, Erlang, Java, JavaScript, Lua,
Objective CAML, Perl, PHP, Python, Rebol, Ruby, and Scheme.
Additional information:
Magic number(s): n/a
File extension(s): .json
Macintosh file type code(s): TEXT
Person & email address to contact for further information:
Douglas Crockford
douglas@crockford.com
Intended usage: COMMON
Restrictions on usage: none
Author:
Douglas Crockford
douglas@crockford.com
Change controller:
Douglas Crockford
douglas@crockford.com
7. Security Considerations
See Security Considerations in Section 6.
8. Examples
This is a JSON object:
{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "http://www.example.com/image/481989943",
"Height": 125,
"Width": "100"
},
"IDs": [116, 943, 234, 38793]
Crockford Informational [Page 7]
RFC 4627 JSON July 2006
}
}
Its Image member is an object whose Thumbnail member is an object
and whose IDs member is an array of numbers.
This is a JSON array containing two objects:
[
{
"precision": "zip",
"Latitude": 37.7668,
"Longitude": -122.3959,
"Address": "",
"City": "SAN FRANCISCO",
"State": "CA",
"Zip": "94107",
"Country": "US"
},
{
"precision": "zip",
"Latitude": 37.371991,
"Longitude": -122.026020,
"Address": "",
"City": "SUNNYVALE",
"State": "CA",
"Zip": "94085",
"Country": "US"
}
]
9. References
9.1. Normative References
[ECMA] European Computer Manufacturers Association, "ECMAScript
Language Specification 3rd Edition", December 1999,
<http://www.ecma-international.org/publications/files/
ecma-st/ECMA-262.pdf>.
[RFC0020] Cerf, V., "ASCII format for network interchange", RFC 20,
October 1969.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC4234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
Specifications: ABNF", RFC 4234, October 2005.
Crockford Informational [Page 8]
RFC 4627 JSON July 2006
[UNICODE] The Unicode Consortium, "The Unicode Standard Version 4.0",
2003, <http://www.unicode.org/versions/Unicode4.1.0/>.
Author's Address
Douglas Crockford
JSON.org
EMail: douglas@crockford.com
Crockford Informational [Page 9]
RFC 4627 JSON July 2006
Full Copyright Statement
Copyright (C) The Internet Society (2006).
This document is subject to the rights, licenses and restrictions
contained in BCP 78, and except as set forth therein, the authors
retain all their rights.
This document and the information contained herein are provided on an
"AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Intellectual Property
The IETF takes no position regarding the validity or scope of any
Intellectual Property Rights or other rights that might be claimed to
pertain to the implementation or use of the technology described in
this document or the extent to which any license under such rights
might or might not be available; nor does it represent that it has
made any independent effort to identify any such rights. Information
on the procedures with respect to rights in RFC documents can be
found in BCP 78 and BCP 79.
Copies of IPR disclosures made to the IETF Secretariat and any
assurances of licenses to be made available, or the result of an
attempt made to obtain a general license or permission for the use of
such proprietary rights by implementers or users of this
specification can be obtained from the IETF on-line IPR repository at
http://www.ietf.org/ipr.
The IETF invites any interested party to bring to its attention any
copyrights, patents or patent applications, or other proprietary
rights that may cover technology that may be required to implement
this standard. Please address the information to the IETF at
ietf-ipr@ietf.org.
Acknowledgement
Funding for the RFC Editor function is provided by the IETF
Administrative Support Activity (IASA).
Crockford Informational [Page 10]

View File

@ -0,0 +1,451 @@
Internet Engineering Task Force (IETF) P. Bryan, Ed.
Request for Comments: 6901 Salesforce.com
Category: Standards Track K. Zyp
ISSN: 2070-1721 SitePen (USA)
M. Nottingham, Ed.
Akamai
April 2013
JavaScript Object Notation (JSON) Pointer
Abstract
JSON Pointer defines a string syntax for identifying a specific value
within a JavaScript Object Notation (JSON) document.
Status of This Memo
This is an Internet Standards Track document.
This document is a product of the Internet Engineering Task Force
(IETF). It represents the consensus of the IETF community. It has
received public review and has been approved for publication by the
Internet Engineering Steering Group (IESG). Further information on
Internet Standards is available in Section 2 of RFC 5741.
Information about the current status of this document, any errata,
and how to provide feedback on it may be obtained at
http://www.rfc-editor.org/info/rfc6901.
Copyright Notice
Copyright (c) 2013 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
carefully, as they describe your rights and restrictions with respect
to this document. Code Components extracted from this document must
include Simplified BSD License text as described in Section 4.e of
the Trust Legal Provisions and are provided without warranty as
described in the Simplified BSD License.
Bryan, et al. Standards Track [Page 1]
RFC 6901 JSON Pointer April 2013
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
2. Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3. Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
4. Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . 3
5. JSON String Representation . . . . . . . . . . . . . . . . . . 4
6. URI Fragment Identifier Representation . . . . . . . . . . . . 5
7. Error Handling . . . . . . . . . . . . . . . . . . . . . . . . 6
8. Security Considerations . . . . . . . . . . . . . . . . . . . . 6
9. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 7
10. References . . . . . . . . . . . . . . . . . . . . . . . . . . 7
10.1. Normative References . . . . . . . . . . . . . . . . . . . 7
10.2. Informative References . . . . . . . . . . . . . . . . . . 7
1. Introduction
This specification defines JSON Pointer, a string syntax for
identifying a specific value within a JavaScript Object Notation
(JSON) document [RFC4627]. JSON Pointer is intended to be easily
expressed in JSON string values as well as Uniform Resource
Identifier (URI) [RFC3986] fragment identifiers.
2. Conventions
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in [RFC2119].
This specification expresses normative syntax rules using Augmented
Backus-Naur Form (ABNF) [RFC5234] notation.
3. Syntax
A JSON Pointer is a Unicode string (see [RFC4627], Section 3)
containing a sequence of zero or more reference tokens, each prefixed
by a '/' (%x2F) character.
Because the characters '~' (%x7E) and '/' (%x2F) have special
meanings in JSON Pointer, '~' needs to be encoded as '~0' and '/'
needs to be encoded as '~1' when these characters appear in a
reference token.
Bryan, et al. Standards Track [Page 2]
RFC 6901 JSON Pointer April 2013
The ABNF syntax of a JSON Pointer is:
json-pointer = *( "/" reference-token )
reference-token = *( unescaped / escaped )
unescaped = %x00-2E / %x30-7D / %x7F-10FFFF
; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
escaped = "~" ( "0" / "1" )
; representing '~' and '/', respectively
It is an error condition if a JSON Pointer value does not conform to
this syntax (see Section 7).
Note that JSON Pointers are specified in characters, not as bytes.
4. Evaluation
Evaluation of a JSON Pointer begins with a reference to the root
value of a JSON document and completes with a reference to some value
within the document. Each reference token in the JSON Pointer is
evaluated sequentially.
Evaluation of each reference token begins by decoding any escaped
character sequence. This is performed by first transforming any
occurrence of the sequence '~1' to '/', and then transforming any
occurrence of the sequence '~0' to '~'. By performing the
substitutions in this order, an implementation avoids the error of
turning '~01' first into '~1' and then into '/', which would be
incorrect (the string '~01' correctly becomes '~1' after
transformation).
The reference token then modifies which value is referenced according
to the following scheme:
o If the currently referenced value is a JSON object, the new
referenced value is the object member with the name identified by
the reference token. The member name is equal to the token if it
has the same number of Unicode characters as the token and their
code points are byte-by-byte equal. No Unicode character
normalization is performed. If a referenced member name is not
unique in an object, the member that is referenced is undefined,
and evaluation fails (see below).
Bryan, et al. Standards Track [Page 3]
RFC 6901 JSON Pointer April 2013
o If the currently referenced value is a JSON array, the reference
token MUST contain either:
* characters comprised of digits (see ABNF below; note that
leading zeros are not allowed) that represent an unsigned
base-10 integer value, making the new referenced value the
array element with the zero-based index identified by the
token, or
* exactly the single character "-", making the new referenced
value the (nonexistent) member after the last array element.
The ABNF syntax for array indices is:
array-index = %x30 / ( %x31-39 *(%x30-39) )
; "0", or digits without a leading "0"
Implementations will evaluate each reference token against the
document's contents and will raise an error condition if it fails to
resolve a concrete value for any of the JSON pointer's reference
tokens. For example, if an array is referenced with a non-numeric
token, an error condition will be raised. See Section 7 for details.
Note that the use of the "-" character to index an array will always
result in such an error condition because by definition it refers to
a nonexistent array element. Thus, applications of JSON Pointer need
to specify how that character is to be handled, if it is to be
useful.
Any error condition for which a specific action is not defined by the
JSON Pointer application results in termination of evaluation.
5. JSON String Representation
A JSON Pointer can be represented in a JSON string value. Per
[RFC4627], Section 2.5, all instances of quotation mark '"' (%x22),
reverse solidus '\' (%x5C), and control (%x00-1F) characters MUST be
escaped.
Note that before processing a JSON string as a JSON Pointer,
backslash escape sequences must be unescaped.
Bryan, et al. Standards Track [Page 4]
RFC 6901 JSON Pointer April 2013
For example, given the JSON document
{
"foo": ["bar", "baz"],
"": 0,
"a/b": 1,
"c%d": 2,
"e^f": 3,
"g|h": 4,
"i\\j": 5,
"k\"l": 6,
" ": 7,
"m~n": 8
}
The following JSON strings evaluate to the accompanying values:
"" // the whole document
"/foo" ["bar", "baz"]
"/foo/0" "bar"
"/" 0
"/a~1b" 1
"/c%d" 2
"/e^f" 3
"/g|h" 4
"/i\\j" 5
"/k\"l" 6
"/ " 7
"/m~0n" 8
6. URI Fragment Identifier Representation
A JSON Pointer can be represented in a URI fragment identifier by
encoding it into octets using UTF-8 [RFC3629], while percent-encoding
those characters not allowed by the fragment rule in [RFC3986].
Note that a given media type needs to specify JSON Pointer as its
fragment identifier syntax explicitly (usually, in its registration
[RFC6838]). That is, just because a document is JSON does not imply
that JSON Pointer can be used as its fragment identifier syntax. In
particular, the fragment identifier syntax for application/json is
not JSON Pointer.
Bryan, et al. Standards Track [Page 5]
RFC 6901 JSON Pointer April 2013
Given the same example document as above, the following URI fragment
identifiers evaluate to the accompanying values:
# // the whole document
#/foo ["bar", "baz"]
#/foo/0 "bar"
#/ 0
#/a~1b 1
#/c%25d 2
#/e%5Ef 3
#/g%7Ch 4
#/i%5Cj 5
#/k%22l 6
#/%20 7
#/m~0n 8
7. Error Handling
In the event of an error condition, evaluation of the JSON Pointer
fails to complete.
Error conditions include, but are not limited to:
o Invalid pointer syntax
o A pointer that references a nonexistent value
This specification does not define how errors are handled. An
application of JSON Pointer SHOULD specify the impact and handling of
each type of error.
For example, some applications might stop pointer processing upon an
error, while others may attempt to recover from missing values by
inserting default ones.
8. Security Considerations
A given JSON Pointer is not guaranteed to reference an actual JSON
value. Therefore, applications using JSON Pointer should anticipate
this situation by defining how a pointer that does not resolve ought
to be handled.
Note that JSON pointers can contain the NUL (Unicode U+0000)
character. Care is needed not to misinterpret this character in
programming languages that use NUL to mark the end of a string.
Bryan, et al. Standards Track [Page 6]
RFC 6901 JSON Pointer April 2013
9. Acknowledgements
The following individuals contributed ideas, feedback, and wording to
this specification:
Mike Acar, Carsten Bormann, Tim Bray, Jacob Davies, Martin J.
Duerst, Bjoern Hoehrmann, James H. Manger, Drew Perttula, and
Julian Reschke.
10. References
10.1. Normative References
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO
10646", STD 63, RFC 3629, November 2003.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
Resource Identifier (URI): Generic Syntax", STD 66,
RFC 3986, January 2005.
[RFC4627] Crockford, D., "The application/json Media Type for
JavaScript Object Notation (JSON)", RFC 4627, July 2006.
[RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
Specifications: ABNF", STD 68, RFC 5234, January 2008.
10.2. Informative References
[RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type
Specifications and Registration Procedures", BCP 13,
RFC 6838, January 2013.
Bryan, et al. Standards Track [Page 7]
RFC 6901 JSON Pointer April 2013
Authors' Addresses
Paul C. Bryan (editor)
Salesforce.com
Phone: +1 604 783 1481
EMail: pbryan@anode.ca
Kris Zyp
SitePen (USA)
Phone: +1 650 968 8787
EMail: kris@sitepen.com
Mark Nottingham (editor)
Akamai
EMail: mnot@mnot.net
Bryan, et al. Standards Track [Page 8]

189
examples/custom_schema.cpp Normal file
View File

@ -0,0 +1,189 @@
/**
* @file
*
* @brief Demonstrates validation against a manually constructed schema.
*
* This example demonstrates the construction and composition of a Schema object
* using manually created Constraint objects. The following Constraint classes
* are used:
* - EnumConstraint
* - MaxLengthConstraint
* - MinimumConstraint
* - MinLengthConstraint
* - PropertiesConstraint
* - RequiredConstraint
* - TypeConstraint
*
* The MinimumConstraint class provides support for the exclusiveMinimum and
* minimum keywords in JSON Schema. And the PropertiesConstraint class provides
* support for the properties, patternProperties, and additionalProperties
* keywords.
*
* This is the JSON Schema representation of the Schema that will be created:
*
* {
* "properties": {
* "category": {
* "enum": [
* "album",
* "book",
* "other",
* "video"
* ]
* },
* "description": {
* "type": "string"
* },
* "price": {
* "exclusiveMinimum": true,
* "minimum": 0.0,
* "type": "number"
* },
* "title": {
* "maxLength": 200,
* "minLength": 1,
* "type": "string"
* }
* },
* "required": [
* "category",
* "price",
* "title"
* ],
* "type": "object"
* }
*
*/
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <rapidjson/document.h>
#include <valijson/adapters/rapidjson_adapter.hpp>
#include <valijson/constraints/concrete_constraints.hpp>
#include <valijson/utils/rapidjson_utils.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validation_results.hpp>
#include <valijson/validator.hpp>
using std::cerr;
using std::endl;
using valijson::Schema;
using valijson::SchemaParser;
using valijson::Validator;
using valijson::ValidationResults;
using valijson::adapters::RapidJsonAdapter;
using valijson::adapters::RapidJsonFrozenValue;
using valijson::constraints::EnumConstraint;
using valijson::constraints::MaxLengthConstraint;
using valijson::constraints::MinimumConstraint;
using valijson::constraints::MinLengthConstraint;
using valijson::constraints::PropertiesConstraint;
using valijson::constraints::RequiredConstraint;
using valijson::constraints::TypeConstraint;
void addPropertiesConstraint(Schema &schema)
{
PropertiesConstraint::PropertySchemaMap propertySchemaMap;
PropertiesConstraint::PropertySchemaMap patternPropertiesSchemaMap;
{
// Create a child schema for the 'category' property that requires one
// of several possible values.
Schema &propertySchema = propertySchemaMap["category"];
EnumConstraint::Values enumConstraintValues;
enumConstraintValues.push_back(new RapidJsonFrozenValue("album"));
enumConstraintValues.push_back(new RapidJsonFrozenValue("book"));
enumConstraintValues.push_back(new RapidJsonFrozenValue("other"));
enumConstraintValues.push_back(new RapidJsonFrozenValue("video"));
propertySchema.addConstraint(new EnumConstraint(enumConstraintValues));
}
{
// Create a child schema for the 'description' property that requires
// a string, but does not enforce any length constraints.
Schema &propertySchema = propertySchemaMap["description"];
propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kString));
}
{
// Create a child schema for the 'price' property, that requires a
// number with a value greater than zero.
Schema &propertySchema = propertySchemaMap["price"];
propertySchema.addConstraint(new MinimumConstraint(0.0, true));
propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kNumber));
}
{
// Create a child schema for the 'title' property that requires a string
// that is between 1 and 200 characters in length.
Schema &propertySchema = propertySchemaMap["title"];
propertySchema.addConstraint(new MaxLengthConstraint(200));
propertySchema.addConstraint(new MinLengthConstraint(1));
propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kString));
}
// Add a PropertiesConstraint to the schema, with the properties defined
// above, no pattern properties, and with additional property schemas
// prohibited.
schema.addConstraint(new PropertiesConstraint(
propertySchemaMap, patternPropertiesSchemaMap));
}
void addRequiredConstraint(Schema &schema)
{
// Add a RequiredConstraint to the schema, specifying that the category,
// price, and title properties must be present.
RequiredConstraint::RequiredProperties requiredProperties;
requiredProperties.insert("category");
requiredProperties.insert("price");
requiredProperties.insert("title");
schema.addConstraint(new RequiredConstraint(requiredProperties));
}
void addTypeConstraint(Schema &schema)
{
// Add a TypeConstraint to the schema, specifying that the root of the
// document must be an object.
schema.addConstraint(new TypeConstraint(TypeConstraint::kObject));
}
int main(int argc, char *argv[])
{
// Load the document that is to be validated
rapidjson::Document targetDocument;
if (!valijson::utils::loadDocument(argv[1], targetDocument)) {
cerr << "Failed to load target document." << endl;
return 1;
}
// Populate a schema
Schema schema;
addPropertiesConstraint(schema);
addRequiredConstraint(schema);
addTypeConstraint(schema);
// Perform validation
Validator validator(schema);
ValidationResults results;
RapidJsonAdapter targetDocumentAdapter(targetDocument);
if (!validator.validate(targetDocumentAdapter, &results)) {
std::cerr << "Validation failed." << endl;
ValidationResults::Error error;
unsigned int errorNum = 1;
while (results.popError(error)) {
cerr << "Error #" << errorNum << std::endl
<< " context: " << error.context << endl
<< " desc: " << error.description << endl;
++errorNum;
}
return 1;
}
return 0;
}

View File

@ -0,0 +1,76 @@
/**
* @file
*
* @brief Demonstrates validation against a schema loaded from a file.
*
*/
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <rapidjson/document.h>
#include <valijson/adapters/rapidjson_adapter.hpp>
#include <valijson/utils/rapidjson_utils.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validation_results.hpp>
#include <valijson/validator.hpp>
using std::cerr;
using std::endl;
using valijson::Schema;
using valijson::SchemaParser;
using valijson::Validator;
using valijson::ValidationResults;
using valijson::adapters::RapidJsonAdapter;
int main(int argc, char *argv[])
{
// Load the document containing the schema
rapidjson::Document schemaDocument;
if (!valijson::utils::loadDocument(argv[1], schemaDocument)) {
cerr << "Failed to load schema document." << endl;
return 1;
}
// Load the document that is to be validated
rapidjson::Document targetDocument;
if (!valijson::utils::loadDocument(argv[2], targetDocument)) {
cerr << "Failed to load target document." << endl;
return 1;
}
// Parse the json schema into an internal schema format
Schema schema;
SchemaParser parser;
RapidJsonAdapter schemaDocumentAdapter(schemaDocument);
try {
parser.populateSchema(schemaDocumentAdapter, schema);
} catch (...) {
cerr << "Failed to parse schema." << endl;
return 1;
}
// Perform validation
Validator validator(schema);
validator.setStrict(false);
ValidationResults results;
RapidJsonAdapter targetDocumentAdapter(targetDocument);
if (!validator.validate(targetDocumentAdapter, &results)) {
std::cerr << "Validation failed." << endl;
ValidationResults::Error error;
unsigned int errorNum = 1;
while (results.popError(error)) {
cerr << "Error #" << errorNum << std::endl
<< " context: " << error.context << endl
<< " desc: " << error.description << endl;
++errorNum;
}
return 1;
}
return 0;
}

View File

@ -0,0 +1,475 @@
#ifndef __VALIJSON_ADAPTERS_ADAPTER_HPP
#define __VALIJSON_ADAPTERS_ADAPTER_HPP
#include <boost/function.hpp>
namespace valijson {
namespace adapters {
class FrozenValue;
/**
* @brief An interface that encapsulates access to the JSON values provided
* by a JSON parser implementation.
*
* This interface allows JSON processing code to be parser-agnostic. It provides
* functions to access the plain old datatypes (PODs) that are described in the
* JSON specification, and callback-based access to the contents of arrays and
* objects.
*
* The interface also defines a set of functions that allow for type-casting and
* type-comparison based on value rather than on type.
*/
class Adapter
{
public:
/// Typedef for callback function supplied to applyToArray.
typedef boost::function<bool (const Adapter &)>
ArrayValueCallback;
/// Typedef for callback function supplied to applyToObject.
typedef boost::function<bool (const std::string &, const Adapter &)>
ObjectMemberCallback;
/**
* @brief Virtual destructor defined to ensure deletion via base-class
* pointers is safe.
*/
virtual ~Adapter() { };
/**
* @brief Apply a callback function to each value in an array.
*
* The callback function is invoked for each element in the array, until
* it has been applied to all values, or it returns false.
*
* @param fn Callback function to invoke
*
* @returns true if Adapter contains an array and all values are equal,
* false otherwise.
*/
virtual bool applyToArray(ArrayValueCallback fn) const = 0;
/**
* @brief Apply a callback function to each member in an object.
*
* The callback function shall be invoked for each member in the object,
* until it has been applied to all values, or it returns false.
*
* @param fn Callback function to invoke
*
* @returns true if Adapter contains an object, and callback function
* returns true for each member in the object, false otherwise.
*/
virtual bool applyToObject(ObjectMemberCallback fn) const = 0;
/**
* @brief Return the boolean representation of the contained value.
*
* This function shall return a boolean value if the Adapter contains either
* an actual boolean value, or one of the strings 'true' or 'false'.
* The string comparison is case sensitive.
*
* An exception shall be thrown if the value cannot be cast to a boolean.
*
* @returns Boolean representation of contained value.
*/
virtual bool asBool() const = 0;
/**
* @brief Retrieve the boolean representation of the contained value.
*
* This function shall retrieve a boolean value if the Adapter contains
* either an actual boolean value, or one of the strings 'true' or 'false'.
* The string comparison is case sensitive.
*
* The retrieved value is returned via reference.
*
* @param result reference to a bool to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asBool(bool &result) const = 0;
/**
* @brief Return the double representation of the contained value.
*
* This function shall return a double value if the Adapter contains either
* an actual double, an integer, or a string that contains a valid
* representation of a numeric value (according to the C++ Std Library).
*
* An exception shall be thrown if the value cannot be cast to a double.
*
* @returns Double representation of contained value.
*/
virtual double asDouble() const = 0;
/**
* @brief Retrieve the double representation of the contained value.
*
* This function shall retrieve a double value if the Adapter contains either
* an actual double, an integer, or a string that contains a valid
* representation of a numeric value (according to the C++ Std Library).
*
* The retrieved value is returned via reference.
*
* @param result reference to a double to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asDouble(double &result) const = 0;
/**
* @brief Return the int64_t representation of the contained value.
*
* This function shall return an int64_t value if the Adapter contains either
* an actual integer, or a string that contains a valid representation of an
* integer value (according to the C++ Std Library).
*
* An exception shall be thrown if the value cannot be cast to an int64_t.
*
* @returns int64_t representation of contained value.
*/
virtual int64_t asInteger() const = 0;
/**
* @brief Retrieve the int64_t representation of the contained value.
*
* This function shall retrieve an int64_t value if the Adapter contains
* either an actual integer, or a string that contains a valid
* representation of an integer value (according to the C++ Std Library).
*
* The retrieved value is returned via reference.
*
* @param result reference to a int64_t to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asInteger(int64_t &result) const = 0;
/**
* @brief Return the string representation of the contained value.
*
* This function shall return a string value if the Adapter contains either
* an actual string, a literal value of another POD type, an empty array,
* an empty object, or null.
*
* An exception shall be thrown if the value cannot be cast to a string.
*
* @returns string representation of contained value.
*/
virtual std::string asString() const = 0;
/**
* @brief Retrieve the string representation of the contained value.
*
* This function shall retrieve a string value if the Adapter contains either
* an actual string, a literal value of another POD type, an empty array,
* an empty object, or null.
*
* The retrieved value is returned via reference.
*
* @param result reference to a string to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asString(std::string &result) const = 0;
/**
* @brief Compare the value held by this Adapter instance with the value
* held by another Adapter instance.
*
* @param other the other adapter instance
* @param strict flag to use strict type comparison
*
* @returns true if values are equal, false otherwise
*/
virtual bool equalTo(const Adapter &other, bool strict) const = 0;
/**
* @brief Create a new FrozenValue instance that is equivalent to the
* value contained by the Adapter.
*
* @returns pointer to a new FrozenValue instance, belonging to the caller.
*/
virtual FrozenValue* freeze() const = 0;
/**
* @brief Return the number of elements in the array.
*
* Throws an exception if the value is not an array.
*
* @return number of elements if value is an array
*/
virtual size_t getArraySize() const = 0;
/**
* @brief Retrieve the number of elements in the array.
*
* This function shall return true or false to indicate whether or not the
* result value was set. If the contained value is not an array, the
* result value shall not be set. This applies even if the value could be
* cast to an empty array. The calling code is expected to handles those
* cases manually.
*
* @param result reference to size_t variable to set with result.
*
* @return true if value retrieved successfully, false otherwise.
*/
virtual bool getArraySize(size_t &result) const = 0;
/**
* @brief Return the contained boolean value.
*
* This function shall throw an exception if the contained value is not a
* boolean.
*
* @returns contained boolean value.
*/
virtual bool getBool() const = 0;
/**
* @brief Retrieve the contained boolean value.
*
* This function shall retrieve the boolean value contained by this Adapter,
* and store it in the result variable that was passed by reference.
*
* @param result reference to boolean variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getBool(bool &result) const = 0;
/**
* @brief Return the contained double value.
*
* This function shall throw an exception if the contained value is not a
* double.
*
* @returns contained double value.
*/
virtual double getDouble() const = 0;
/**
* @brief Retrieve the contained double value.
*
* This function shall retrieve the double value contained by this Adapter,
* and store it in the result variable that was passed by reference.
*
* @param result reference to double variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getDouble(double &result) const = 0;
/**
* @brief Return the contained integer value.
*
* This function shall throw an exception if the contained value is not a
* integer.
*
* @returns contained integer value.
*/
virtual int64_t getInteger() const = 0;
/**
* @brief Retrieve the contained integer value.
*
* This function shall retrieve the integer value contained by this Adapter,
* and store it in the result variable that was passed by reference.
*
* @param result reference to integer variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getInteger(int64_t &result) const = 0;
/**
* @brief Return the contained numeric value as a double.
*
* This function shall throw an exception if the contained value is not a
* integer or a double.
*
* @returns contained double or integral value.
*/
virtual double getNumber() const = 0;
/**
* @brief Retrieve the contained numeric value as a double.
*
* This function shall retrieve the double or integral value contained by
* this Adapter, and store it in the result variable that was passed by
* reference.
*
* @param result reference to double variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getNumber(double &result) const = 0;
/**
* @brief Return the number of members in the object.
*
* Throws an exception if the value is not an object.
*
* @return number of members if value is an object
*/
virtual size_t getObjectSize() const = 0;
/**
* @brief Retrieve the number of members in the object.
*
* This function shall return true or false to indicate whether or not the
* result value was set. If the contained value is not an object, the
* result value shall not be set. This applies even if the value could be
* cast to an empty object. The calling code is expected to handles those
* cases manually.
*
* @param result reference to size_t variable to set with result.
*
* @return true if value retrieved successfully, false otherwise.
*/
virtual bool getObjectSize(size_t &result) const = 0;
/**
* @brief Return the contained string value.
*
* This function shall throw an exception if the contained value is not a
* string - even if the value could be cast to a string. The asString()
* function should be used when casting is allowed.
*
* @returns string contained by this Adapter
*/
virtual std::string getString() const = 0;
/**
* @brief Retrieve the contained string value.
*
* This function shall retrieve the string value contained by this Adapter,
* and store it in result variable that is passed by reference.
*
* @param result reference to string to set with result
*
* @returns true if string was retrieved, false otherwise
*/
virtual bool getString(std::string &result) const = 0;
/**
* @brief Returns whether or not this Adapter supports strict types.
*
* This function shall return true if the Adapter implementation supports
* strict types, or false if the Adapter fails to store any part of the
* type information supported by the Adapter interface.
*
* For example, the PropertyTreeAdapter implementation stores POD values as
* strings, effectively discarding any other type information. If you were
* to call isDouble() on a double stored by this Adapter, the result would
* be false. The maybeDouble(), asDouble() and various related functions
* are provided to perform type checking based on value rather than on type.
*
* The BasicAdapter template class provides implementations for the type-
* casting functions so that Adapter implementations are semantically
* equivalent in their type-casting behaviour.
*
* @returns true if Adapter supports strict types, false otherwise
*/
virtual bool hasStrictTypes() const = 0;
/// Returns true if the contained value is definitely an array.
virtual bool isArray() const = 0;
/// Returns true if the contained value is definitely a boolean.
virtual bool isBool() const = 0;
/// Returns true if the contained value is definitely a double.
virtual bool isDouble() const = 0;
/// Returns true if the contained value is definitely an integer.
virtual bool isInteger() const = 0;
/// Returns true if the contained value is definitely a null.
virtual bool isNull() const = 0;
/// Returns true if the contained value is either a double or an integer.
virtual bool isNumber() const = 0;
/// Returns true if the contained value is definitely an object.
virtual bool isObject() const = 0;
/// Returns true if the contained value is definitely a string.
virtual bool isString() const = 0;
/**
* @brief Returns true if the contained value can be cast to an array.
*
* @returns true if the contained value is an array, an empty string, or an
* empty object.
*/
virtual bool maybeArray() const = 0;
/**
* @brief Returns true if the contained value can be cast to a boolean.
*
* @returns true if the contained value is a boolean, or one of the strings
* 'true' or 'false'. Note that numeric values are not to be cast
* to boolean values.
*/
virtual bool maybeBool() const = 0;
/**
* @brief Returns true if the contained value can be cast to a double.
*
* @returns true if the contained value is a double, an integer, or a string
* containing a double or integral value.
*/
virtual bool maybeDouble() const = 0;
/**
* @brief Returns true if the contained value can be cast to an integer.
*
* @returns true if the contained value is an integer, or a string
* containing an integral value.
*/
virtual bool maybeInteger() const = 0;
/**
* @brief Returns true if the contained value can be cast to a null.
*
* @returns true if the contained value is null or an empty string.
*/
virtual bool maybeNull() const = 0;
/**
* @brief Returns true if the contained value can be cast to an object.
*
* @returns true if the contained value is an object, an empty array or
* an empty string.
*/
virtual bool maybeObject() const = 0;
/**
* @brief Returns true if the contained value can be cast to a string.
*
* @returns true if the contained value is a non-null POD type, an empty
* array, or an empty object.
*/
virtual bool maybeString() const = 0;
};
/**
* @brief Template struct that should be specialised for each concrete Adapter
* class.
*
* @deprecated This is a bit of a hack, and I'd like to remove it.
*/
template<typename T>
struct AdapterTraits
{
};
} // namespace adapters
} // namespace valijson
#endif

View File

@ -0,0 +1,848 @@
#ifndef __VALIJSON_ADAPTERS_BASIC_ADAPTER_HPP
#define __VALIJSON_ADAPTERS_BASIC_ADAPTER_HPP
#include <cstdint>
#include <sstream>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <valijson/adapters/adapter.hpp>
namespace valijson {
namespace adapters {
/**
* @brief Template class that implements the expected semantics of an Adapter.
*
* Implementing all of the type-casting functionality for each Adapter is error
* prone and tedious, so this template class aims to minimise the duplication
* of code between various Adapter implementations. This template doesn't quite
* succeed in removing all duplication, but it has greatly simplified the
* implementation of a new Adapter by encapsulating the type-casting semantics
* and a lot of the trivial functionality associated with the Adapter interface.
*
* By inheriting from this template class, Adapter implementations will inherit
* the exception throwing behaviour that is expected by other parts of the
* Valijson library.
*
* @tparam AdapterType Self-referential name of the Adapter being
* specialised.
* @tparam ArrayType Name of the type that will be returned by the
* getArray() function. Instances of this type should
* provide begin(), end() and size() functions so
* that it is possible to iterate over the values in
* the array.
* @tparam ObjectMemberType Name of the type exposed when iterating over the
* contents of an object returned by getObject().
* @tparam ObjectType Name of the type that will be returned by the
* getObject() function. Instances of this type
* should provide begin(), end(), find() and size()
* functions so that it is possible to iterate over
* the members of the object.
* @tparam ValueType Name of the type that provides a consistent
* interface to a JSON value for a parser. For
* example, this type should provide the getDouble()
* and isDouble() functions. But it does not need to
* know how to cast values from one type to another -
* that functionality is provided by this template
* class.
*/
template<
typename AdapterType,
typename ArrayType,
typename ObjectMemberType,
typename ObjectType,
typename ValueType>
class BasicAdapter: public Adapter
{
protected:
/**
* @brief Functor for comparing two arrays.
*
* This functor is used to compare the elements in an array of the type
* ArrayType with individual values provided as generic Adapter objects.
* Comparison is performed by the () operator.
*
* The functor works by maintaining an iterator for the current position
* in an array. Each time the () operator is called, the value at this
* position is compared with the value passed as an argument to ().
* Immediately after the comparison, the iterator will be incremented.
*
* This functor is designed to be passed to the applyToArray() function
* of an Adapter object.
*/
class ArrayComparisonFunctor
{
public:
/**
* @brief Construct an ArrayComparisonFunctor for an array.
*
* @param array Array to compare values against
* @param strict Flag to use strict type comparison
*/
ArrayComparisonFunctor(const ArrayType &array, bool strict)
: itr(array.begin()),
end(array.end()),
strict(strict) { }
/**
* @brief Compare a value against the current element in the array.
*
* @param adapter Value to be compared with current element
*
* @returns true if values are equal, false otherwise.
*/
bool operator()(const Adapter &adapter)
{
if (itr == end) {
return false;
}
return AdapterType(*itr++).equalTo(adapter, strict);
}
private:
/// Iterator for current element in the array
typename ArrayType::const_iterator itr;
/// Iterator for one-past the last element of the array
typename ArrayType::const_iterator end;
/// Flag to use strict type comparison
const bool strict;
};
/**
* @brief Functor for comparing two objects
*
* This functor is used to compare the members of an object of the type
* ObjectType with key-value pairs belonging to another object.
*
* The functor works by maintaining a reference to an object provided via
* the constructor. When time the () operator is called with a key-value
* pair as arguments, the function will attempt to find the key in the
* base object. If found, the associated value will be compared with the
* value provided to the () operator.
*
* This functor is designed to be passed to the applyToObject() function
* of an Adapter object.
*/
class ObjectComparisonFunctor
{
public:
/**
* @brief Construct a new ObjectComparisonFunctor for an object.
*
* @param object object to use as comparison baseline
* @param strict flag to use strict type-checking
*/
ObjectComparisonFunctor(
const ObjectType &object, bool strict)
: object(object),
strict(strict) { }
/**
* @brief Find a key in the object and compare its value.
*
* @param key Key to find
* @param value Value to be compared against
*
* @returns true if key is found and values are equal, false otherwise.
*/
bool operator()(const std::string &key, const Adapter &value)
{
const typename ObjectType::const_iterator itr = object.find(key);
if (itr == object.end()) {
return false;
}
return (*itr).second.equalTo(value, strict);
}
private:
/// Object to be used as a comparison baseline
const ObjectType &object;
/// Flag to use strict type-checking
bool strict;
};
public:
/// Alias for ArrayType template parameter
typedef ArrayType Array;
/// Alias for ObjectMemberType template parameter
typedef ObjectMemberType ObjectMember;
/// Alias for ObjectType template parameter
typedef ObjectType Object;
/**
* @brief Construct an Adapter using the default value.
*
* This constructor relies on the default constructor of the ValueType
* class provided as a template argument.
*/
BasicAdapter() { }
/**
* @brief Construct an Adapter using a specified ValueType object.
*
* This constructor relies on the copy constructor of the ValueType
* class provided as template argument.
*/
BasicAdapter(const ValueType &value)
: value(value) { }
virtual bool applyToArray(ArrayValueCallback fn) const
{
if (!maybeArray()) {
return false;
}
// Due to the fact that the only way a value can be 'maybe an array' is
// if it is an empty string or empty object, we only need to go to
// effort of constructing an ArrayType instance if the value is
// definitely an array.
if (value.isArray()) {
const boost::optional<Array> array = value.getArrayOptional();
BOOST_FOREACH( const AdapterType element, *array ) {
if (!fn(element)) {
return false;
}
}
}
return true;
}
virtual bool applyToObject(ObjectMemberCallback fn) const
{
if (!maybeObject()) {
return false;
}
if (value.isObject()) {
const boost::optional<Object> object = value.getObjectOptional();
BOOST_FOREACH( const ObjectMemberType member, *object ) {
if (!fn(member.first, AdapterType(member.second))) {
return false;
}
}
}
return true;
}
/**
* @brief Return an ArrayType instance containing an array representation
* of the value held by this Adapter.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the elements in an array. The ArrayType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* If the contained value is either an empty object, or an empty string,
* then this function will cast the value to an empty array.
*
* @returns ArrayType instance containing an array representation of the
* value held by this Adapter.
*/
ArrayType asArray() const
{
if (value.isArray()) {
return *value.getArrayOptional();
} else if (value.isObject()) {
size_t objectSize;
if (value.getObjectSize(objectSize) && objectSize == 0) {
return ArrayType();
}
} else if (value.isString()) {
std::string stringValue;
if (value.getString(stringValue) && stringValue.empty()) {
return ArrayType();
}
}
throw std::runtime_error("JSON value cannot be cast to an array.");
}
virtual bool asBool() const
{
bool result;
if (asBool(result)) {
return result;
}
throw std::runtime_error("JSON value cannot be cast to a boolean.");
}
virtual bool asBool(bool &result) const
{
if (value.isBool()) {
return value.getBool(result);
} else if (value.isString()) {
std::string s;
if (value.getString(s)) {
if (s.compare("true") == 0) {
result = true;
return true;
} else if (s.compare("false") == 0) {
result = false;
return true;
}
}
}
return false;
}
virtual double asDouble() const
{
double result;
if (asDouble(result)) {
return result;
}
throw std::runtime_error("JSON value cannot be cast to a double.");
}
virtual bool asDouble(double &result) const
{
if (value.isDouble()) {
return value.getDouble(result);
} else if (value.isInteger()) {
int64_t i;
if (value.getInteger(i)) {
result = double(i);
return true;
}
} else if (value.isString()) {
std::string s;
if (value.getString(s)) {
std::istringstream i(s);
double x;
char c;
if (!(!(i >> x) || i.get(c))) {
result = x;
return true;
}
}
}
return false;
}
virtual int64_t asInteger() const
{
int64_t result;
if (asInteger(result)) {
return result;
}
throw std::runtime_error("JSON value cannot be cast as an integer.");
}
virtual bool asInteger(int64_t &result) const
{
if (value.isInteger()) {
return value.getInteger(result);
} else if (value.isString()) {
std::string s;
if (value.getString(s)) {
std::istringstream i(s);
int64_t x;
char c;
if (!(!(i >> x) || i.get(c))) {
result = x;
return true;
}
}
}
return false;
}
/**
* @brief Return an ObjectType instance containing an array representation
* of the value held by this Adapter.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the members of the object. The ObjectType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* @returns ObjectType instance containing an object representation of the
* value held by this Adapter.
*/
ObjectType asObject() const
{
if (value.isObject()) {
return *value.getObjectOptional();
} else if (value.isArray()) {
size_t arraySize;
if (value.getArraySize(arraySize) && arraySize == 0) {
return ObjectType();
}
} else if (value.isString()) {
std::string stringValue;
if (value.getString(stringValue) && stringValue.empty()) {
return ObjectType();
}
}
throw std::runtime_error("JSON value cannot be cast to an object.");
}
virtual std::string asString() const
{
std::string result;
if (asString(result)) {
return result;
}
throw std::runtime_error("JSON value cannot be cast to a string.");
}
virtual bool asString(std::string &result) const
{
if (value.isString()) {
return value.getString(result);
} else if (value.isNull()) {
result.clear();
return true;
} else if (value.isArray()) {
size_t arraySize;
if (value.getArraySize(arraySize) && arraySize == 0) {
result.clear();
return true;
}
} else if (value.isObject()) {
size_t objectSize;
if (value.getObjectSize(objectSize) && objectSize == 0) {
result.clear();
return true;
}
} else if (value.isBool()) {
bool boolValue;
if (value.getBool(boolValue)) {
result = boolValue ? "true" : "false";
return true;
}
} else if (value.isInteger()) {
int64_t integerValue;
if (value.getInteger(integerValue)) {
try {
result = boost::lexical_cast<std::string>(integerValue);
return true;
} catch (boost::bad_lexical_cast &) { }
}
} else if (value.isDouble()) {
double doubleValue;
if (value.getDouble(doubleValue)) {
try {
result = boost::lexical_cast<std::string>(doubleValue);
return true;
} catch (boost::bad_lexical_cast &) { }
}
}
return false;
}
virtual bool equalTo(const Adapter &other, bool strict) const
{
if (isNull() || (!strict && maybeNull())) {
return other.isNull() || (!strict && other.maybeNull());
} else if (isBool() || (!strict && maybeBool())) {
return (other.isBool() || (!strict && other.maybeBool())) &&
other.asBool() == asBool();
} else if (isNumber() && strict) {
return other.isNumber() && other.getNumber() == getNumber();
} else if (!strict && maybeDouble()) {
return (other.maybeDouble() &&
other.asDouble() == asDouble());
} else if (!strict && maybeInteger()) {
return (other.maybeInteger() &&
other.asInteger() == asInteger());
} else if (isString() || (!strict && maybeString())) {
return (other.isString() || (!strict && other.maybeString())) &&
other.asString() == asString();
} else if (isArray()) {
if (other.isArray() && getArraySize() == other.getArraySize()) {
const boost::optional<ArrayType> array = value.getArrayOptional();
if (array) {
ArrayComparisonFunctor fn(*array, strict);
return other.applyToArray(fn);
}
} else if (!strict && other.maybeArray() && getArraySize() == 0) {
return true;
}
} else if (isObject()) {
if (other.isObject() && other.getObjectSize() == getObjectSize()) {
const boost::optional<ObjectType> object = value.getObjectOptional();
if (object) {
ObjectComparisonFunctor fn(*object, strict);
return other.applyToObject(fn);
}
} else if (!strict && other.maybeObject() && getObjectSize() == 0) {
return true;
}
}
return false;
}
/**
* @brief Return an ArrayType instance representing the array contained
* by this Adapter instance.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the elements in an array. The ArrayType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* If the contained is not an array, this function will throw an exception.
*
* @returns ArrayType instance containing an array representation of the
* value held by this Adapter.
*/
ArrayType getArray() const
{
boost::optional<ArrayType> arrayValue = value.getArrayOptional();
if (arrayValue) {
return *arrayValue;
}
throw std::runtime_error("JSON value is not an array.");
}
virtual size_t getArraySize() const
{
size_t result;
if (value.getArraySize(result)) {
return result;
}
throw std::runtime_error("JSON value is not an array.");
}
virtual bool getArraySize(size_t &result) const
{
return value.getArraySize(result);
}
virtual bool getBool() const
{
bool result;
if (getBool(result)) {
return result;
}
throw std::runtime_error("JSON value is not a boolean.");
}
virtual bool getBool(bool &result) const
{
return value.getBool(result);
}
virtual double getDouble() const
{
double result;
if (getDouble(result)) {
return result;
}
throw std::runtime_error("JSON value is not a double.");
}
virtual bool getDouble(double &result) const
{
return value.getDouble(result);
}
virtual int64_t getInteger() const
{
int64_t result;
if (getInteger(result)) {
return result;
}
throw std::runtime_error("JSON value is not an integer.");
}
virtual bool getInteger(int64_t &result) const
{
return value.getInteger(result);
}
virtual double getNumber() const
{
double result;
if (getNumber(result)) {
return result;
}
throw std::runtime_error("JSON value is not a number.");
}
virtual bool getNumber(double &result) const
{
if (isDouble()) {
return getDouble(result);
} else if (isInteger()) {
int64_t integerResult;
if (getInteger(integerResult)) {
result = static_cast<double>(integerResult);
return true;
}
}
return false;
}
/**
* @brief Return an ObjectType instance representing the object contained
* by this Adapter instance.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the members of an object. The ObjectType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* If the contained is not an object, this function will throw an exception.
*
* @returns ObjectType instance containing an array representation of the
* value held by this Adapter.
*/
ObjectType getObject() const
{
boost::optional<ObjectType> objectValue = value.getObjectOptional();
if (objectValue) {
return *objectValue;
}
throw std::runtime_error("JSON value is not an object.");
}
virtual size_t getObjectSize() const
{
size_t result;
if (getObjectSize(result)) {
return result;
}
throw std::runtime_error("JSON value is not an object.");
}
virtual bool getObjectSize(size_t &result) const
{
return value.getObjectSize(result);
}
virtual std::string getString() const
{
std::string result;
if (getString(result)) {
return result;
}
throw std::runtime_error("JSON value is not a string.");
}
virtual bool getString(std::string &result) const
{
return value.getString(result);
}
virtual FrozenValue * freeze() const
{
return value.freeze();
}
virtual bool hasStrictTypes() const
{
return ValueType::hasStrictTypes();
}
virtual bool isArray() const
{
return value.isArray();
}
virtual bool isBool() const
{
return value.isBool();
}
virtual bool isDouble() const
{
return value.isDouble();
}
virtual bool isInteger() const
{
return value.isInteger();
}
virtual bool isNull() const
{
return value.isNull();
}
virtual bool isNumber() const
{
return value.isInteger() || value.isDouble();
}
virtual bool isObject() const
{
return value.isObject();
}
virtual bool isString() const
{
return value.isString();
}
virtual bool maybeArray() const
{
if (value.isArray()) {
return true;
} else if (value.isObject()) {
size_t objectSize;
if (value.getObjectSize(objectSize) && objectSize == 0) {
return true;
}
}
return false;
}
virtual bool maybeBool() const
{
if (value.isBool()) {
return true;
} else if (value.isString()) {
std::string stringValue;
if (value.getString(stringValue)) {
if (stringValue.compare("true") == 0 || stringValue.compare("false") == 0) {
return true;
}
}
}
return false;
}
virtual bool maybeDouble() const
{
if (value.isNumber()) {
return true;
} else if (value.isString()) {
std::string s;
if (value.getString(s)) {
std::istringstream i(s);
double x;
char c;
if (!(i >> x) || i.get(c)) {
return false;
}
return true;
}
}
return false;
}
virtual bool maybeInteger() const
{
if (value.isInteger()) {
return true;
} else if (value.isString()) {
std::string s;
if (value.getString(s)) {
std::istringstream i(s);
int64_t x;
char c;
if (!(i >> x) || i.get(c)) {
return false;
}
return true;
}
}
return false;
}
virtual bool maybeNull() const
{
if (value.isNull()) {
return true;
} else if (value.isString()) {
std::string stringValue;
if (value.getString(stringValue)) {
if (stringValue.empty()) {
return true;
}
}
}
return false;
}
virtual bool maybeObject() const
{
if (value.isObject()) {
return true;
} else if (value.isArray()) {
size_t arraySize;
if (value.getArraySize(arraySize) && arraySize == 0) {
return true;
}
}
return true;
}
virtual bool maybeString() const
{
if (value.isString() || value.isBool() || value.isInteger() ||
value.isDouble()) {
return true;
} else if (value.isObject()) {
size_t objectSize;
if (value.getObjectSize(objectSize) && objectSize == 0) {
return true;
}
} else if (value.isArray()) {
size_t arraySize;
if (value.getArraySize(arraySize) && arraySize == 0) {
return true;
}
}
return false;
}
private:
const ValueType value;
};
} // namespace adapters
} // namespace valijson
#endif

View File

@ -0,0 +1,68 @@
#ifndef __VALIJSON_ADAPTERS_FROZEN_VALUE_HPP
#define __VALIJSON_ADAPTERS_FROZEN_VALUE_HPP
#include <valijson/adapters/adapter.hpp>
namespace valijson {
namespace adapters {
/**
* @brief An interface that provides minimal access to a stored JSON value.
*
* The main reason that this interface exists is to support the 'enum'
* constraint. Each Adapter type is expected to provide an implementation of
* this interface. That class should be able to maintain its own copy of a
* JSON value, independent of the original document.
*
* This interface currently provides just the clone and equalTo functions, but
* could be expanded to include other functions declared in the Adapter
* interface.
*
* @todo it would be nice to better integrate this with the Adapter interface
*/
class FrozenValue
{
public:
/**
* @brief Virtual destructor defined to ensure deletion via base-class
* pointers is safe.
*/
virtual ~FrozenValue() { }
/**
* @brief Clone the stored value and return a pointer to a new FrozenValue
* object containing the value.
*/
virtual FrozenValue *clone() const = 0;
/**
* @brief Return true if the stored value is equal to the value contained
* by an Adapter instance.
*
* @param adapter Adapter to compare value against
* @param strict Flag to use strict type comparison
*
* @returns true if values are equal, false otherwise
*/
virtual bool equalTo(const Adapter &adapter, bool strict) const = 0;
};
/**
* @brief Implementation of new_clone for the FrozenValue interface, as
* required for the boost pointer containers.
*
* @param frozenValue reference to FrozenValue to clone
*
* @returns a pointer to a new FrozenValue, belonging to the caller
*/
inline FrozenValue * new_clone(const FrozenValue &frozenValue)
{
return frozenValue.clone();
}
} // namespace adapters
} // namespace valijson
#endif

View File

@ -0,0 +1,685 @@
/**
* @file
*
* @brief Adapter implementation for the JsonCpp parser library.
*
* Include this file in your program to enable support for JsonCpp.
*
* This file defines the following classes (not in this order):
* - JsonCppAdapter
* - JsonCppArray
* - JsonCppArrayValueIterator
* - JsonCppFrozenValue
* - JsonCppObject
* - JsonCppObjectMember
* - JsonCppObjectMemberIterator
* - JsonCppValue
*
* Due to the dependencies that exist between these classes, the ordering of
* class declarations and definitions may be a bit confusing. The best place to
* start is JsonCppAdapter. This class definition is actually very small,
* since most of the functionality is inherited from the BasicAdapter class.
* Most of the classes in this file are provided as template arguments to the
* inherited BasicAdapter class.
*/
#ifndef __VALIJSON_ADAPTERS_JSONCPP_ADAPTER_HPP
#define __VALIJSON_ADAPTERS_JSONCPP_ADAPTER_HPP
#include <cstdint>
#include <string>
#include <boost/bind.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <json/json.h>
#include <valijson/adapters/adapter.hpp>
#include <valijson/adapters/basic_adapter.hpp>
#include <valijson/adapters/frozen_value.hpp>
namespace valijson {
namespace adapters {
class JsonCppAdapter;
class JsonCppArrayValueIterator;
class JsonCppObjectMemberIterator;
typedef std::pair<std::string, JsonCppAdapter> JsonCppObjectMember;
/**
* @brief Light weight wrapper for a JsonCpp array value.
*
* This class is light weight wrapper for a JsonCpp array. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to the underlying
* JsonCpp value, assumed to be an array, so there is very little overhead
* associated with copy construction and passing by value.
*/
class JsonCppArray
{
public:
typedef JsonCppArrayValueIterator const_iterator;
typedef JsonCppArrayValueIterator iterator;
/// Construct a JsonCppArray referencing an empty array.
JsonCppArray()
: value(emptyArray()) { }
/**
* @brief Construct a JsonCppArray referencing a specific JsonCpp value.
*
* @param value reference to a JsonCpp value
*
* Note that this constructor will throw an exception if the value is not
* an array.
*/
JsonCppArray(const Json::Value &value)
: value(value)
{
if (!value.isArray()) {
throw std::runtime_error("Value is not an array.");
}
}
/**
* @brief Return an iterator for the first element of the array.
*
* The iterator return by this function is effectively the iterator
* returned by the underlying JsonCpp implementation.
*/
JsonCppArrayValueIterator begin() const;
/**
* @brief Return an iterator for one-past the last element of the array.
*
* The iterator return by this function is effectively the iterator
* returned by the underlying JsonCpp implementation.
*/
JsonCppArrayValueIterator end() const;
/// Return the number of elements in the array.
size_t size() const
{
return value.size();
}
private:
/**
* @brief Return a reference to a JsonCpp value that is an empty array.
*
* Note that the value returned by this function is a singleton.
*/
static const Json::Value & emptyArray()
{
static const Json::Value array(Json::arrayValue);
return array;
}
/// Reference to the contained array
const Json::Value &value;
};
/**
* @brief Light weight wrapper for a JsonCpp object.
*
* This class is light weight wrapper for a JsonCpp object. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to the underlying
* JsonCpp object, assumed to be an object, so there is very little overhead
* associated with copy construction and passing by value.
*/
class JsonCppObject
{
public:
typedef JsonCppObjectMemberIterator const_iterator;
typedef JsonCppObjectMemberIterator iterator;
/// Construct a JsonCppObject referencing an empty object singleton.
JsonCppObject()
: value(emptyObject()) { }
/**
* @brief Construct a JsonCppObject referencing a specific JsonCpp value.
*
* @param value reference to a JsonCpp value
*
* Note that this constructor will throw an exception if the value is not
* an object.
*/
JsonCppObject(const Json::Value &value)
: value(value)
{
if (!value.isObject()) {
throw std::runtime_error("Value is not an object.");
}
}
/**
* @brief Return an iterator for this first object member
*
* The iterator return by this function is effectively a wrapper around
* the iterator value returned by the underlying JsonCpp implementation.
*/
JsonCppObjectMemberIterator begin() const;
/**
* @brief Return an iterator for an invalid object member that indicates
* the end of the collection.
*
* The iterator return by this function is effectively a wrapper around
* the iterator value returned by the underlying JsonCpp implementation.
*/
JsonCppObjectMemberIterator end() const;
/**
* @brief Return an iterator for a member/property with the given name
*
* @param propertyName Property name
*
* @returns a valid iterator if found, or an invalid iterator if not found
*/
JsonCppObjectMemberIterator find(const std::string &propertyName) const;
/// Return the number of members in the object
size_t size() const
{
return value.size();
}
private:
/// Return a reference to an empty JsonCpp object
static const Json::Value & emptyObject()
{
static const Json::Value object(Json::objectValue);
return object;
}
/// Reference to the contained object
const Json::Value &value;
};
/**
* @brief Stores an independent copy of a JsonCpp value.
*
* This class allows a JsonCpp value to be stored independent of its original
* document. JsonCpp makes this easy to do, as it does not perform any
* custom memory management.
*
* @see FrozenValue
*/
class JsonCppFrozenValue: public FrozenValue
{
public:
/**
* @brief Make a copy of a JsonCpp value
*
* @param source the JsonCpp value to be copied
*/
JsonCppFrozenValue(const Json::Value &source)
: value(source) { }
virtual FrozenValue * clone() const
{
return new JsonCppFrozenValue(value);
}
virtual bool equalTo(const Adapter &other, bool strict) const;
private:
/// Stored JsonCpp value
Json::Value value;
};
/**
* @brief Light weight wrapper for a JsonCpp value.
*
* This class is passed as an argument to the BasicAdapter template class,
* and is used to provide access to a JsonCpp value. This class is responsible
* for the mechanics of actually reading a JsonCpp value, whereas the
* BasicAdapter class is responsible for the semantics of type comparisons
* and conversions.
*
* The functions that need to be provided by this class are defined implicitly
* by the implementation of the BasicAdapter template class.
*
* @see BasicAdapter
*/
class JsonCppValue
{
public:
/// Construct a wrapper for the empty object singleton
JsonCppValue()
: value(emptyObject()) { }
/// Construct a wrapper for a specific JsonCpp value
JsonCppValue(const Json::Value &value)
: value(value) { }
/**
* @brief Create a new JsonCppFrozenValue instance that contains the
* value referenced by this JsonCppValue instance.
*
* @returns pointer to a new JsonCppFrozenValue instance, belonging to the
* caller.
*/
FrozenValue * freeze() const
{
return new JsonCppFrozenValue(value);
}
/**
* @brief Optionally return a JsonCppArray instance.
*
* If the referenced JsonCpp value is an array, this function will return a
* boost::optional containing a JsonCppArray instance referencing the
* array.
*
* Otherwise it will return boost::none.
*/
boost::optional<JsonCppArray> getArrayOptional() const
{
if (value.isArray()) {
return boost::make_optional(JsonCppArray(value));
}
return boost::none;
}
/**
* @brief Retrieve the number of elements in the array
*
* If the referenced JsonCpp value is an array, this function will retrieve
* the number of elements in the array and store it in the output variable
* provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of elements was retrieved, false otherwise.
*/
bool getArraySize(size_t &result) const
{
if (value.isArray()) {
result = value.size();
return true;
}
return false;
}
bool getBool(bool &result) const
{
if (value.isBool()) {
result = value.asBool();
return true;
}
return false;
}
bool getDouble(double &result) const
{
if (value.isDouble()) {
result = value.asDouble();
return true;
}
return false;
}
bool getInteger(int64_t &result) const
{
if (value.isIntegral()) {
result = static_cast<int64_t>(value.asInt());
return true;
}
return false;
}
/**
* @brief Optionally return a JsonCppObject instance.
*
* If the referenced JsonCpp value is an object, this function will return a
* boost::optional containing a JsonCppObject instance referencing the
* object.
*
* Otherwise it will return boost::none.
*/
boost::optional<JsonCppObject> getObjectOptional() const
{
if (value.isObject()) {
return boost::make_optional(JsonCppObject(value));
}
return boost::none;
}
/**
* @brief Retrieve the number of members in the object
*
* If the referenced JsonCpp value is an object, this function will retrieve
* the number of members in the object and store it in the output variable
* provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of members was retrieved, false otherwise.
*/
bool getObjectSize(size_t &result) const
{
if (value.isObject()) {
result = value.size();
return true;
}
return false;
}
bool getString(std::string &result) const
{
if (value.isString()) {
result = value.asString();
return true;
}
return false;
}
static bool hasStrictTypes()
{
return true;
}
bool isArray() const
{
return value.isArray() && !value.isNull();
}
bool isBool() const
{
return value.isBool();
}
bool isDouble() const
{
return value.isDouble();
}
bool isInteger() const
{
return value.isIntegral() && !value.isBool();
}
bool isNull() const
{
return value.isNull();
}
bool isNumber() const
{
return value.isNumeric() && !value.isBool();
}
bool isObject() const
{
return value.isObject() && !value.isNull();
}
bool isString() const
{
return value.isString();
}
private:
/// Return a reference to an empty object singleton.
static const Json::Value &emptyObject()
{
static Json::Value object(Json::objectValue);
return object;
}
/// Reference to the contained JsonCpp value
const Json::Value &value;
};
/**
* @brief An implementation of the Adapter interface supporting JsonCpp.
*
* This class is defined in terms of the BasicAdapter template class, which
* helps to ensure that all of the Adapter implementations behave consistently.
*
* @see Adapter
* @see BasicAdapter
*/
class JsonCppAdapter:
public BasicAdapter<JsonCppAdapter,
JsonCppArray,
JsonCppObjectMember,
JsonCppObject,
JsonCppValue>
{
public:
/// Construct a JsonCppAdapter that contains an empty object
JsonCppAdapter()
: BasicAdapter() { }
/// Construct a JsonCppAdapter containing a specific JsonCpp value
JsonCppAdapter(const Json::Value &value)
: BasicAdapter(value) { }
};
/**
* @brief Class for iterating over values held in a JSON array.
*
* This class provides a JSON array iterator that dereferences as an instance of
* JsonCppAdapter representing a value stored in the array. It has been
* implemented using the boost iterator_facade template.
*
* @see JsonCppArray
*/
class JsonCppArrayValueIterator:
public boost::iterator_facade<
JsonCppArrayValueIterator, // name of derived type
JsonCppAdapter, // value type
boost::bidirectional_traversal_tag, // bi-directional iterator
JsonCppAdapter> // type returned when dereferenced
{
public:
/**
* @brief Construct a new JsonCppArrayValueIterator using an existing
* JsonCpp iterator.
*
* @param itr JsonCpp iterator to store
*/
JsonCppArrayValueIterator(const Json::Value::const_iterator &itr)
: itr(itr) { }
/// Returns a JsonCppAdapter that contains the value of the current element.
JsonCppAdapter dereference() const
{
return JsonCppAdapter(*itr);
}
/**
* @brief Compare this iterator against another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param rhs iterator to compare against
*
* @returns true if the iterators are equal, false otherwise.
*/
bool equal(const JsonCppArrayValueIterator &rhs) const
{
return itr == rhs.itr;
}
void increment()
{
itr++;
}
void decrement()
{
itr--;
}
void advance(std::ptrdiff_t n)
{
if (n > 0) {
while (n-- > 0) {
itr++;
}
} else {
while (n++ < 0) {
itr--;
}
}
}
private:
Json::Value::const_iterator itr;
};
/**
* @brief Class for iterating over the members belonging to a JSON object.
*
* This class provides a JSON object iterator that dereferences as an instance
* of JsonCppObjectMember representing one of the members of the object. It has
* been implemented using the boost iterator_facade template.
*
* @see JsonCppObject
* @see JsonCppObjectMember
*/
class JsonCppObjectMemberIterator:
public boost::iterator_facade<
JsonCppObjectMemberIterator, // name of derived type
JsonCppObjectMember, // value type
boost::bidirectional_traversal_tag, // bi-directional iterator
JsonCppObjectMember> // type returned when dereferenced
{
public:
/**
* @brief Construct an iterator from a JsonCpp iterator.
*
* @param itr JsonCpp iterator to store
*/
JsonCppObjectMemberIterator(const Json::ValueConstIterator &itr)
: itr(itr) { }
/**
* @brief Returns a JsonCppObjectMember that contains the key and value
* belonging to the object member identified by the iterator.
*/
JsonCppObjectMember dereference() const
{
return JsonCppObjectMember(itr.key().asString(), *itr);
}
/**
* @brief Compare this iterator with another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param rhs Iterator to compare with
*
* @returns true if the underlying iterators are equal, false otherwise
*/
bool equal(const JsonCppObjectMemberIterator &rhs) const
{
return itr == rhs.itr;
}
void increment()
{
itr++;
}
void decrement()
{
itr--;
}
private:
/// Iternal copy of the original JsonCpp iterator
Json::ValueConstIterator itr;
};
/// Specialisation of the AdapterTraits template struct for JsonCppAdapter.
template<>
struct AdapterTraits<valijson::adapters::JsonCppAdapter>
{
typedef Json::Value DocumentType;
static std::string adapterName()
{
return "JsonCppAdapter";
}
};
inline bool JsonCppFrozenValue::equalTo(const Adapter &other, bool strict) const
{
return JsonCppAdapter(value).equalTo(other, strict);
}
inline JsonCppArrayValueIterator JsonCppArray::begin() const
{
return value.begin();
}
inline JsonCppArrayValueIterator JsonCppArray::end() const
{
return value.end();
}
inline JsonCppObjectMemberIterator JsonCppObject::begin() const
{
return value.begin();
}
inline JsonCppObjectMemberIterator JsonCppObject::end() const
{
return value.end();
}
inline JsonCppObjectMemberIterator JsonCppObject::find(
const std::string &propertyName) const
{
if (value.isMember(propertyName)) {
Json::ValueConstIterator itr;
for ( itr = value.begin(); itr != value.end(); ++itr) {
if (itr.key() == propertyName) {
return itr;
}
}
}
return value.end();
}
} // namespace adapters
} // namespace valijson
#endif

View File

@ -0,0 +1,718 @@
/**
* @file
*
* @brief Adapter implementation for the Boost property tree library.
*
* Include this file in your program to enable support for boost property trees.
*
* This file defines the following classes (not in this order):
* - PropertyTreeAdapter
* - PropertyTreeArray
* - PropertyTreeArrayValueIterator
* - PropertyTreeFrozenValue
* - PropertyTreeObject
* - PropertyTreeObjectMember
* - PropertyTreeObjectMemberIterator
* - PropertyTreeValue
*
* Due to the dependencies that exist between these classes, the ordering of
* class declarations and definitions may be a bit confusing. The best place to
* start is PropertyTreeAdapter. This class definition is actually very small,
* since most of the functionality is inherited from the BasicAdapter class.
* Most of the classes in this file are provided as template arguments to the
* inherited BasicAdapter class.
*/
#ifndef __VALIJSON_ADAPTERS_PROPERTY_TREE_ADAPTER_HPP
#define __VALIJSON_ADAPTERS_PROPERTY_TREE_ADAPTER_HPP
#include <string>
#include <boost/bind.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/property_tree/ptree.hpp>
#include <valijson/adapters/adapter.hpp>
#include <valijson/adapters/basic_adapter.hpp>
#include <valijson/adapters/frozen_value.hpp>
namespace valijson {
namespace adapters {
class PropertyTreeAdapter;
class PropertyTreeArrayValueIterator;
class PropertyTreeObjectMemberIterator;
typedef std::pair<std::string, PropertyTreeAdapter> PropertyTreeObjectMember;
/**
* @brief Light weight wrapper for a Boost property tree that contains
* array-like data.
*
* This class is light weight wrapper for a Boost property tree. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to a Boost property
* tree that is assumed to contain unnamed key-value pairs. There is very little
* associated with copy construction and passing by value.
*/
class PropertyTreeArray
{
public:
typedef PropertyTreeArrayValueIterator const_iterator;
typedef PropertyTreeArrayValueIterator iterator;
/// Construct a PropertyTreeArra7 referencing an empty property tree
/// singleton.
PropertyTreeArray()
: array(emptyTree()) { }
/**
* @brief Construct PropertyTreeArray referencing a specific Boost
* property tree.
*
* @param array reference to a property tree containing an array
*
* It is assumed that this value contains array-like data, but this is not
* checked due to runtime cost.
*/
PropertyTreeArray(const boost::property_tree::ptree &array)
: array(array) { }
/// Return an iterator for the first element in the array.
PropertyTreeArrayValueIterator begin() const;
/// Return an iterator for one-past the last element of the array.
PropertyTreeArrayValueIterator end() const;
/// Return the number of elements in the array
size_t size() const
{
return array.size();
}
private:
/**
* @brief Return a reference to a property tree that looks like an
* empty array.
*
* Note that the value returned by this function is a singleton.
*/
static const boost::property_tree::ptree & emptyTree()
{
static const boost::property_tree::ptree tree;
return tree;
}
/// Reference to the contained value
const boost::property_tree::ptree &array;
};
/**
* @brief Light weight wrapper for a Boost property tree that contains
* object-like data.
*
* This class is light weight wrapper for a Boost property tree. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to the underlying
* property tree value, assumed to be object-like, so there is very little
* overhead associated with copy construction and passing by value.
*/
class PropertyTreeObject
{
public:
typedef PropertyTreeObjectMemberIterator const_iterator;
typedef PropertyTreeObjectMemberIterator iterator;
/// Construct a PropertyTreeObject referencing an empty property tree.
PropertyTreeObject()
: object(emptyTree()) { }
/**
* @brief Construct a PropertyTreeObject referencing a specific property
* tree.
*
* @param object reference to a property tree containing an object
*
* Note that the value of the property tree is not checked, due to the
* runtime cost of doing so.
*/
PropertyTreeObject(const boost::property_tree::ptree &object)
: object(object) { }
/**
* @brief Return an iterator for this first object member
*
* The iterator return by this function is effectively a wrapper around
* the iterator value returned by the underlying property tree
* implementation.
*/
PropertyTreeObjectMemberIterator begin() const;
/**
* @brief Return an iterator for an invalid object member that indicates
* the end of the collection.
*
* The iterator return by this function is effectively a wrapper around
* the pointer value returned by the underlying property tree
* implementation.
*/
PropertyTreeObjectMemberIterator end() const;
/**
* @brief Return an iterator for the object member with the specified
* property name.
*
* If an object member with the specified name does not exist, the iterator
* returned will be the same as the iterator returned by the end() function.
*
* @param property property name to search for
*/
PropertyTreeObjectMemberIterator find(const std::string &property) const;
/// Returns the number of members belonging to this object.
size_t size() const
{
return object.size();
}
private:
/**
* @brief Return a reference to an empty property tree.
*
* Note that the value returned by this function is a singleton.
*/
static const boost::property_tree::ptree & emptyTree()
{
static const boost::property_tree::ptree tree;
return tree;
}
/// Reference to the contained object
const boost::property_tree::ptree &object;
};
/**
* @brief Stores an independent copy of a Boost property tree.
*
* This class allows a property tree value to be stored independent of its
* original 'document'. Boost property trees make this easy to do, as they do
* not perform any custom memory management.
*
* @see FrozenValue
*/
class PropertyTreeFrozenValue: public FrozenValue
{
public:
/**
* @brief Make a copy of a Boost property tree POD value
*
* @param source string containing the POD vlaue
*/
PropertyTreeFrozenValue(const boost::property_tree::ptree::data_type &source)
: value(source) { }
/**
* @brief Make a copy of a Boost property tree object or array value
*
* @param source the property tree to be copied
*/
PropertyTreeFrozenValue(const boost::property_tree::ptree &source)
: value(source) { }
virtual FrozenValue * clone() const
{
return new PropertyTreeFrozenValue(value);
}
virtual bool equalTo(const Adapter &other, bool strict) const;
private:
/// Stored value
boost::property_tree::ptree value;
};
/**
* @brief Light weight wrapper for a Boost property tree.
*
* This class is passed as an argument to the BasicAdapter template class,
* and is used to provide access to a Boost property tree value. This class
* is responsible for the mechanics of actually reading a property tree, whereas
* BasicAdapter class is responsible for the semantics of type comparisons
* and conversions.
*
* The functions that need to be provided by this class are defined implicitly
* by the implementation of the BasicAdapter template class.
*
* @see BasicAdapter
*/
class PropertyTreeValue
{
public:
/// Construct a wrapper for an empty property tree
PropertyTreeValue()
: object(emptyTree()) { }
/**
* @brief Construct a PropertyTreeValue from a tree object
*
* This function will determine whether the tree object represents an array
* or an object by scanning the key names for any non-empty strings. In the
* case of an empty tree object, it is not possible to determine whether it
* is an array or an object, so it will be treated as an array by default.
* Empty arrays are considered equal to empty objects when compared using
* non-strict type comparison. Empty strings will also be stored as empty
* arrays.
*
* @param tree Tree object to be wrapped
*/
PropertyTreeValue(const boost::property_tree::ptree &tree)
{
if (tree.data().empty()) { // No string content
if (tree.size() == 0) { // No children
array = tree; // Treat as empty array
} else {
bool isArray = true;
boost::property_tree::ptree::const_iterator itr;
for (itr = tree.begin(); itr != tree.end(); itr++) {
if (!itr->first.empty()) {
isArray = false;
break;
}
}
if (isArray) {
array = tree;
} else {
object = tree;
}
}
} else {
value = tree.data();
}
}
/**
* @brief Create a new PropertyTreeFrozenValue instance that contains the
* value referenced by this PropertyTreeValue instance.
*
* @returns pointer to a new PropertyTreeFrozenValue instance, belonging to
* the caller.
*/
FrozenValue* freeze() const
{
if (array) {
return new PropertyTreeFrozenValue(*array);
} else if (object) {
return new PropertyTreeFrozenValue(*object);
} else {
return new PropertyTreeFrozenValue(*value);
}
}
/**
* @brief Return an instance of PropertyTreeArrayAdapter.
*
* If the referenced property tree value is an array, this function will
* return a boost::optional containing a PropertyTreeArray instance
* referencing the array.
*
* Otherwise it will return boost::none.
*/
boost::optional<PropertyTreeArray> getArrayOptional() const
{
if (array) {
return boost::make_optional(PropertyTreeArray(*array));
}
return boost::none;
}
/**
* @brief Retrieve the number of elements in the array
*
* If the referenced property tree value is an array, this function will
* retrieve the number of elements in the array and store it in the output
* variable provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of elements was retrieved, false otherwise.
*/
bool getArraySize(size_t &result) const
{
if (array) {
result = array->size();
return true;
}
return false;
}
bool getBool(bool &result) const
{
return false;
}
bool getDouble(double &result) const
{
return false;
}
bool getInteger(int64_t &result) const
{
return false;
}
/**
* @brief Optionally return a PropertyTreeObject instance.
*
* If the referenced property tree is an object, this function will return a
* boost::optional containing a PropertyTreeObject instance referencing the
* object.
*
* Otherwise it will return boost::none.
*/
boost::optional<PropertyTreeObject> getObjectOptional() const
{
if (object) {
return boost::make_optional(PropertyTreeObject(*object));
}
return boost::none;
}
/**
* @brief Retrieve the number of members in the object
*
* If the referenced property tree value is an object, this function will
* retrieve the number of members in the object and store it in the output
* variable provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of members was retrieved, false otherwise.
*/
bool getObjectSize(size_t &result) const
{
if (object) {
result = object->size();
return true;
}
return false;
}
bool getString(std::string &result) const
{
if (value) {
result = *value;
return true;
}
return false;
}
static bool hasStrictTypes()
{
return false;
}
bool isArray() const
{
return array != boost::none;
}
bool isBool() const
{
return false;
}
bool isDouble() const
{
return false;
}
bool isInteger() const
{
return false;
}
bool isNull() const
{
return false;
}
bool isNumber() const
{
return false;
}
bool isObject() const
{
return object != boost::none;
}
bool isString() const
{
return value != boost::none;
}
private:
static const boost::property_tree::ptree & emptyTree()
{
static const boost::property_tree::ptree tree;
return tree;
}
/// Reference used if the value is known to be an array
boost::optional<const boost::property_tree::ptree &> array;
/// Reference used if the value is known to be an object
boost::optional<const boost::property_tree::ptree &> object;
/// Reference used if the value is known to be a POD type
boost::optional<std::string> value;
};
/**
* @brief An implementation of the Adapter interface supporting the Boost
* property tree library.
*
* This class is defined in terms of the BasicAdapter template class, which
* helps to ensure that all of the Adapter implementations behave consistently.
*
* @see Adapter
* @see BasicAdapter
*/
class PropertyTreeAdapter:
public BasicAdapter<PropertyTreeAdapter,
PropertyTreeArray,
PropertyTreeObjectMember,
PropertyTreeObject,
PropertyTreeValue>
{
public:
/// Construct a PropertyTreeAdapter for an empty property tree
PropertyTreeAdapter()
: BasicAdapter() { }
/// Construct a PropertyTreeAdapter using a specific property tree
PropertyTreeAdapter(const boost::property_tree::ptree &value)
: BasicAdapter(value) { }
};
/**
* @brief Class for iterating over values held in a JSON array.
*
* This class provides a JSON array iterator that dereferences as an instance of
* PropertyTreeAdapter representing a value stored in the array. It has been
* implemented using the boost iterator_facade template.
*
* @see PropertyTreeArray
*/
class PropertyTreeArrayValueIterator:
public boost::iterator_facade<
PropertyTreeArrayValueIterator, // name of derived type
PropertyTreeAdapter, // value type
boost::bidirectional_traversal_tag, // bi-directional iterator
PropertyTreeAdapter> // type returned when dereferenced
{
public:
/**
* @brief Construct a new PropertyTreeArrayValueIterator using an existing
* property tree iterator.
*
* @param itr property tree iterator to store
*/
PropertyTreeArrayValueIterator(
const boost::property_tree::ptree::const_iterator &itr)
: itr(itr) { }
/// Returns a PropertyTreeAdapter that contains the value of the current
/// element.
PropertyTreeAdapter dereference() const
{
return PropertyTreeAdapter(itr->second);
}
/**
* @brief Compare this iterator against another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param rhs iterator to compare against
*
* @returns true if the iterators are equal, false otherwise.
*/
bool equal(const PropertyTreeArrayValueIterator &rhs) const
{
return itr == rhs.itr;
}
void increment()
{
itr++;
}
void decrement()
{
itr--;
}
void advance(std::ptrdiff_t n)
{
if (n > 0) {
while (n-- > 0) {
itr++;
}
} else {
while (n++ < 0) {
itr--;
}
}
}
private:
boost::property_tree::ptree::const_iterator itr;
};
/**
* @brief Class for iterating over the members belonging to a JSON object.
*
* This class provides a JSON object iterator that dereferences as an instance
* of PropertyTreeObjectMember representing one of the members of the object.
* It has been implemented using the boost iterator_facade template.
*
* @see PropertyTreeObject
* @see PropertyTreeObjectMember
*/
class PropertyTreeObjectMemberIterator:
public boost::iterator_facade<
PropertyTreeObjectMemberIterator, // name of derived type
PropertyTreeObjectMember, // value type
boost::bidirectional_traversal_tag, // bi-directional iterator
PropertyTreeObjectMember> // type returned when dereferenced
{
public:
/**
* @brief Construct an iterator from a PropertyTree iterator.
*
* @param itr PropertyTree iterator to store
*/
PropertyTreeObjectMemberIterator(
boost::property_tree::ptree::const_assoc_iterator itr)
: itr(itr) { }
/**
* @brief Returns a PropertyTreeObjectMember that contains the key and
* value belonging to the object member identified by the iterator.
*/
PropertyTreeObjectMember dereference() const
{
return PropertyTreeObjectMember(itr->first, itr->second);
}
/**
* @brief Compare this iterator with another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param rhs Iterator to compare with
*
* @returns true if the underlying iterators are equal, false otherwise
*/
bool equal(const PropertyTreeObjectMemberIterator &rhs) const
{
return itr == rhs.itr;
}
void increment()
{
itr++;
}
void decrement()
{
itr--;
}
private:
boost::property_tree::ptree::const_assoc_iterator itr;
};
/// Specialisation of the AdapterTraits template struct for PropertyTreeAdapter.
template<>
struct AdapterTraits<valijson::adapters::PropertyTreeAdapter>
{
typedef boost::property_tree::ptree DocumentType;
static std::string adapterName()
{
return "PropertyTreeAdapter";
}
};
inline bool PropertyTreeFrozenValue::equalTo(const Adapter &other, bool strict) const
{
return PropertyTreeAdapter(value).equalTo(other, strict);
}
inline PropertyTreeArrayValueIterator PropertyTreeArray::begin() const
{
return array.begin();
}
inline PropertyTreeArrayValueIterator PropertyTreeArray::end() const
{
return array.end();
}
inline PropertyTreeObjectMemberIterator PropertyTreeObject::begin() const
{
return object.ordered_begin();
}
inline PropertyTreeObjectMemberIterator PropertyTreeObject::end() const
{
return object.not_found();
}
inline PropertyTreeObjectMemberIterator PropertyTreeObject::find(
const std::string &propertyName) const
{
const boost::property_tree::ptree::const_assoc_iterator
itr = object.find(propertyName);
if (itr != object.not_found()) {
return itr;
}
return object.not_found();
}
} // namespace adapters
} // namespace valijson
#endif

View File

@ -0,0 +1,770 @@
/**
* @file
*
* @brief Adapter implementation for the RapidJson parser library.
*
* Include this file in your program to enable support for RapidJson.
*
* This file defines the following classes (not in this order):
* - RapidJsonAdapter
* - RapidJsonArray
* - RapidJsonArrayValueIterator
* - RapidJsonFrozenValue
* - RapidJsonObject
* - RapidJsonObjectMember
* - RapidJsonObjectMemberIterator
* - RapidJsonValue
*
* Due to the dependencies that exist between these classes, the ordering of
* class declarations and definitions may be a bit confusing. The best place to
* start is RapidJsonAdapter. This class definition is actually very small,
* since most of the functionality is inherited from the BasicAdapter class.
* Most of the classes in this file are provided as template arguments to the
* inherited BasicAdapter class.
*/
#ifndef __VALIJSON_ADAPTERS_RAPIDJSON_ADAPTER_HPP
#define __VALIJSON_ADAPTERS_RAPIDJSON_ADAPTER_HPP
#include <string>
#include <boost/bind.hpp>
#include <boost/optional.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <rapidjson/document.h>
#include <valijson/adapters/adapter.hpp>
#include <valijson/adapters/basic_adapter.hpp>
#include <valijson/adapters/frozen_value.hpp>
namespace valijson {
namespace adapters {
class RapidJsonAdapter;
class RapidJsonArrayValueIterator;
class RapidJsonObjectMemberIterator;
typedef std::pair<std::string, RapidJsonAdapter> RapidJsonObjectMember;
/**
* @brief Light weight wrapper for a RapidJson array value.
*
* This class is light weight wrapper for a RapidJson array. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to an underlying
* RapidJson value, assumed to be an array, so there is very little overhead
* associated with copy construction and passing by value.
*/
class RapidJsonArray
{
public:
typedef RapidJsonArrayValueIterator const_iterator;
typedef RapidJsonArrayValueIterator iterator;
/// Construct a RapidJsonArray referencing an empty array singleton.
RapidJsonArray()
: value(emptyArray()) { }
/**
* @brief Construct a RapidJsonArray referencing a specific RapidJson
* value.
*
* @param value reference to a RapidJson value
*
* Note that this constructor will throw an exception if the value is not
* an array.
*/
RapidJsonArray(const rapidjson::Value &value)
: value(value)
{
if (!value.IsArray()) {
throw std::runtime_error("Value is not an array.");
}
}
/// Return an iterator for the first element in the array.
RapidJsonArrayValueIterator begin() const;
/// Return an iterator for one-past the last element of the array.
RapidJsonArrayValueIterator end() const;
/// Return the number of elements in the array
size_t size() const
{
return value.Size();
}
private:
/**
* @brief Return a reference to a RapidJson value that is an empty array.
*
* Note that the value returned by this function is a singleton.
*/
static const rapidjson::Value & emptyArray()
{
static const rapidjson::Value array(rapidjson::kArrayType);
return array;
}
/// Reference to the contained value
const rapidjson::Value &value;
};
/**
* @brief Light weight wrapper for a RapidJson object.
*
* This class is light weight wrapper for a RapidJson object. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to the underlying
* RapidJson value, assumed to be an object, so there is very little overhead
* associated with copy construction and passing by value.
*/
class RapidJsonObject
{
public:
typedef RapidJsonObjectMemberIterator const_iterator;
typedef RapidJsonObjectMemberIterator iterator;
/// Construct a RapidJsonObject referencing an empty object singleton.
RapidJsonObject()
: value(emptyObject()) { }
/**
* @brief Construct a RapidJsonObject referencing a specific RapidJson
* value.
*
* @param value reference to a RapidJson value
*
* Note that this constructor will throw an exception if the value is not
* an object.
*/
RapidJsonObject(const rapidjson::Value &value)
: value(value)
{
if (!value.IsObject()) {
throw std::runtime_error("Value is not an object.");
}
}
/**
* @brief Return an iterator for this first object member
*
* The iterator return by this function is effectively a wrapper around
* the pointer value returned by the underlying RapidJson implementation.
*/
RapidJsonObjectMemberIterator begin() const;
/**
* @brief Return an iterator for an invalid object member that indicates
* the end of the collection.
*
* The iterator return by this function is effectively a wrapper around
* the pointer value returned by the underlying RapidJson implementation.
*/
RapidJsonObjectMemberIterator end() const;
/**
* @brief Return an iterator for the object member with the specified
* property name.
*
* If an object member with the specified name does not exist, the iterator
* returned will be the same as the iterator returned by the end() function.
*
* @param property property name to search for
*/
RapidJsonObjectMemberIterator find(const std::string &property) const;
/// Returns the number of members belonging to this object.
size_t size() const
{
return value.MemberEnd() - value.MemberBegin();
}
private:
/**
* @brief Return a reference to a RapidJson value that is empty object.
*
* Note that the value returned by this function is a singleton.
*/
static const rapidjson::Value & emptyObject()
{
static rapidjson::Value object(rapidjson::kObjectType);
return object;
}
/// Reference to the contained object
const rapidjson::Value &value;
};
/**
* @brief Stores an independent copy of a RapidJson value.
*
* This class allows a RapidJson value to be stored independent of its original
* document. RapidJson makes this a bit harder than usual, because RapidJson
* values are associated with a custom memory allocator. As such, RapidJson
* values have to be copied recursively, referencing a custom allocator held
* by this class.
*
* @see FrozenValue
*/
class RapidJsonFrozenValue: public FrozenValue
{
public:
/**
* @brief Make a copy of a RapidJson value
*
* @param source the RapidJson value to be copied
*/
RapidJsonFrozenValue(const rapidjson::Value &source)
{
if (!copy(source, value, allocator)) {
throw std::runtime_error("Failed to copy rapidjson::Value");
}
}
virtual FrozenValue * clone() const
{
return new RapidJsonFrozenValue(value);
}
virtual bool equalTo(const Adapter &other, bool strict) const;
private:
/**
* @brief Recursively copy a RapidJson value using a separate allocator
*
* @param source value to copy from
* @param dest value to copy into
* @param allocator reference to an allocator held by this class
*
* @tparam Allocator type of RapidJson Allocator to be used
*
* @returns true if copied successfully, false otherwise.
*/
template<typename Allocator>
static bool copy(const rapidjson::Value &source,
rapidjson::Value &dest,
Allocator &allocator)
{
switch (source.GetType()) {
case rapidjson::kNullType:
dest.SetNull();
return true;
case rapidjson::kFalseType:
dest.SetBool(false);
return true;
case rapidjson::kTrueType:
dest.SetBool(true);
return true;
case rapidjson::kObjectType:
dest.SetObject();
for (rapidjson::Value::ConstMemberIterator itr = source.MemberBegin();
itr != source.MemberEnd(); ++itr) {
rapidjson::Value name(itr->name.GetString(), itr->name.GetStringLength(), allocator);
rapidjson::Value value;
copy(itr->value, value, allocator);
dest.AddMember(name, value, allocator);
}
return true;
case rapidjson::kArrayType:
dest.SetArray();
for (rapidjson::Value::ConstValueIterator itr = source.Begin(); itr != source.End(); ++itr) {
rapidjson::Value value;
copy(*itr, value, allocator);
dest.PushBack(value, allocator);
}
return true;
case rapidjson::kStringType:
dest.SetString(source.GetString(), source.GetStringLength(), allocator);
return true;
case rapidjson::kNumberType:
if (source.IsInt()) {
dest.SetInt(source.GetInt());
} else if (source.IsUint()) {
dest.SetUint(source.GetUint());
} else if (source.IsInt64()) {
dest.SetInt64(source.GetInt64());
} else if (source.IsUint64()) {
dest.SetUint64(source.GetUint64());
} else {
dest.SetDouble(source.GetDouble());
}
return true;
default:
break;
}
return false;
}
/// Local memory allocator for RapidJson value
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> allocator;
/// Local RapidJson value
rapidjson::Value value;
};
/**
* @brief Light weight wrapper for a RapidJson value.
*
* This class is passed as an argument to the BasicAdapter template class,
* and is used to provide access to a RapidJson value. This class is responsible
* for the mechanics of actually reading a RapidJson value, whereas the
* BasicAdapter class is responsible for the semantics of type comparisons
* and conversions.
*
* The functions that need to be provided by this class are defined implicitly
* by the implementation of the BasicAdapter template class.
*
* @see BasicAdapter
*/
class RapidJsonValue
{
public:
/// Construct a wrapper for the empty object singleton
RapidJsonValue()
: value(emptyObject()) { }
/// Construct a wrapper for a specific RapidJson value
RapidJsonValue(const rapidjson::Value &value)
: value(value) { }
/**
* @brief Create a new RapidJsonFrozenValue instance that contains the
* value referenced by this RapidJsonValue instance.
*
* @returns pointer to a new RapidJsonFrozenValue instance, belonging to
* the caller.
*/
FrozenValue * freeze() const
{
return new RapidJsonFrozenValue(value);
}
/**
* @brief Optionally return a RapidJsonArray instance.
*
* If the referenced RapidJson value is an array, this function will return
* a boost::optional containing a RapidJsonArray instance referencing the
* array.
*
* Otherwise it will return boost::none.
*/
boost::optional<RapidJsonArray> getArrayOptional() const
{
if (value.IsArray()) {
return boost::make_optional(RapidJsonArray(value));
}
return boost::none;
}
/**
* @brief Retrieve the number of elements in the array
*
* If the referenced RapidJson value is an array, this function will
* retrieve the number of elements in the array and store it in the output
* variable provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of elements was retrieved, false otherwise.
*/
bool getArraySize(size_t &result) const
{
if (value.IsArray()) {
result = value.Size();
return true;
}
return false;
}
bool getBool(bool &result) const
{
if (value.IsBool()) {
result = value.GetBool();
return true;
}
return false;
}
bool getDouble(double &result) const
{
if (value.IsDouble()) {
result = value.GetDouble();
return true;
}
return false;
}
bool getInteger(int64_t &result) const
{
if (value.IsInt()) {
result = value.GetInt();
return true;
} else if (value.IsInt64()) {
result = value.GetInt64();
return true;
} else if (value.IsUint()) {
result = static_cast<int64_t>(value.GetUint());
return true;
} else if (value.IsUint64()) {
result = static_cast<int64_t>(value.GetUint64());
return true;
}
return false;
}
/**
* @brief Optionally return a RapidJsonObject instance.
*
* If the referenced RapidJson value is an object, this function will return
* a boost::optional containing a RapidJsonObject instance referencing the
* object.
*
* Otherwise it will return boost::none.
*/
boost::optional<RapidJsonObject> getObjectOptional() const
{
if (value.IsObject()) {
return boost::make_optional(RapidJsonObject(value));
}
return boost::none;
}
/**
* @brief Retrieve the number of members in the object
*
* If the referenced RapidJson value is an object, this function will
* retrieve the number of members in the object and store it in the output
* variable provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of members was retrieved, false otherwise.
*/
bool getObjectSize(size_t &result) const
{
if (value.IsObject()) {
result = value.MemberEnd() - value.MemberBegin();
return true;
}
return false;
}
bool getString(std::string &result) const
{
if (value.IsString()) {
result.assign(value.GetString(), value.GetStringLength());
return true;
}
return false;
}
static bool hasStrictTypes()
{
return true;
}
bool isArray() const
{
return value.IsArray();
}
bool isBool() const
{
return value.IsBool();
}
bool isDouble() const
{
return value.IsDouble();
}
bool isInteger() const
{
return value.IsInt() || value.IsInt64() || value.IsUint() ||
value.IsUint64();
}
bool isNull() const
{
return value.IsNull();
}
bool isNumber() const
{
return value.IsNumber();
}
bool isObject() const
{
return value.IsObject();
}
bool isString() const
{
return value.IsString();
}
private:
/// Return a reference to an empty object singleton
static const rapidjson::Value & emptyObject()
{
static const rapidjson::Value object(rapidjson::kObjectType);
return object;
}
/// Reference to the contained RapidJson value.
const rapidjson::Value &value;
};
/**
* @brief An implementation of the Adapter interface supporting RapidJson.
*
* This class is defined in terms of the BasicAdapter template class, which
* helps to ensure that all of the Adapter implementations behave consistently.
*
* @see Adapter
* @see BasicAdapter
*/
class RapidJsonAdapter:
public BasicAdapter<RapidJsonAdapter,
RapidJsonArray,
RapidJsonObjectMember,
RapidJsonObject,
RapidJsonValue>
{
public:
/// Construct a RapidJsonAdapter that contains an empty object
RapidJsonAdapter()
: BasicAdapter() { }
/// Construct a RapidJsonAdapter containing a specific RapidJson value
RapidJsonAdapter(const rapidjson::Value &value)
: BasicAdapter(value) { }
};
/**
* @brief Class for iterating over values held in a JSON array.
*
* This class provides a JSON array iterator that dereferences as an instance of
* RapidJsonAdapter representing a value stored in the array. It has been
* implemented using the boost iterator_facade template.
*
* @see RapidJsonArray
*/
class RapidJsonArrayValueIterator:
public boost::iterator_facade<
RapidJsonArrayValueIterator, // name of derived type
RapidJsonAdapter, // value type
boost::bidirectional_traversal_tag, // bi-directional iterator
RapidJsonAdapter> // type returned when dereferenced
{
public:
/**
* @brief Construct a new RapidJsonArrayValueIterator using an existing
* RapidJson iterator.
*
* @param itr RapidJson iterator to store
*/
RapidJsonArrayValueIterator(
const rapidjson::Value::ConstValueIterator &itr)
: itr(itr) { }
/// Returns a RapidJsonAdapter that contains the value of the current
/// element.
RapidJsonAdapter dereference() const
{
return RapidJsonAdapter(*itr);
}
/**
* @brief Compare this iterator against another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param other iterator to compare against
*
* @returns true if the iterators are equal, false otherwise.
*/
bool equal(const RapidJsonArrayValueIterator &other) const
{
return itr == other.itr;
}
void increment()
{
itr++;
}
void decrement()
{
itr--;
}
void advance(std::ptrdiff_t n)
{
itr += n;
}
std::ptrdiff_t difference(const RapidJsonArrayValueIterator &other)
{
return std::distance(itr, other.itr);
}
private:
rapidjson::Value::ConstValueIterator itr;
};
/**
* @brief Class for iterating over the members belonging to a JSON object.
*
* This class provides a JSON object iterator that dereferences as an instance
* of RapidJsonObjectMember representing one of the members of the object. It
* has been implemented using the boost iterator_facade template.
*
* @see RapidJsonObject
* @see RapidJsonObjectMember
*/
class RapidJsonObjectMemberIterator:
public boost::iterator_facade<
RapidJsonObjectMemberIterator, // name of derived type
RapidJsonObjectMember, // value type
boost::bidirectional_traversal_tag, // bi-directional iterator
RapidJsonObjectMember> // type returned when dereferenced
{
public:
/**
* @brief Construct an iterator from a RapidJson iterator.
*
* @param itr RapidJson iterator to store
*/
RapidJsonObjectMemberIterator(
const rapidjson::Value::ConstMemberIterator &itr)
: itr(itr) { }
/**
* @brief Returns a RapidJsonObjectMember that contains the key and value
* belonging to the object member identified by the iterator.
*/
RapidJsonObjectMember dereference() const
{
return RapidJsonObjectMember(
std::string(itr->name.GetString(), itr->name.GetStringLength()),
itr->value);
}
/**
* @brief Compare this iterator with another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param other Iterator to compare with
*
* @returns true if the underlying iterators are equal, false otherwise
*/
bool equal(const RapidJsonObjectMemberIterator &other) const
{
return itr == other.itr;
}
void increment()
{
itr++;
}
void decrement()
{
itr--;
}
std::ptrdiff_t difference(const RapidJsonObjectMemberIterator &other)
{
return std::distance(itr, other.itr);
}
private:
/// Iternal copy of the original RapidJson iterator
rapidjson::Value::ConstMemberIterator itr;
};
/// RapidJson specialisation of the AdapterTraits template struct.
template<>
struct AdapterTraits<valijson::adapters::RapidJsonAdapter>
{
typedef rapidjson::Document DocumentType;
static std::string adapterName()
{
return "RapidJsonAdapter";
}
};
inline bool RapidJsonFrozenValue::equalTo(const Adapter &other, bool strict) const
{
return RapidJsonAdapter(value).equalTo(other, strict);
}
inline RapidJsonArrayValueIterator RapidJsonArray::begin() const
{
return value.Begin();
}
inline RapidJsonArrayValueIterator RapidJsonArray::end() const
{
return value.End();
}
inline RapidJsonObjectMemberIterator RapidJsonObject::begin() const
{
return value.MemberBegin();
}
inline RapidJsonObjectMemberIterator RapidJsonObject::end() const
{
return value.MemberEnd();
}
inline RapidJsonObjectMemberIterator RapidJsonObject::find(
const std::string &propertyName) const
{
const rapidjson::Value::ConstMemberIterator
itr = value.FindMember(propertyName.c_str());
return itr ? itr : value.MemberEnd();
}
} // namespace adapters
} // namespace valijson
#endif

View File

@ -0,0 +1,36 @@
#ifndef __VALIJSON_CONSTRAINTS_BASIC_CONSTRAINT_HPP
#define __VALIJSON_CONSTRAINTS_BASIC_CONSTRAINT_HPP
#include <valijson/constraints/constraint.hpp>
#include <valijson/constraints/constraint_visitor.hpp>
namespace valijson {
namespace constraints {
/**
* @brief Template class that implements the accept() and clone() functions of
* the Constraint interface.
*
* @tparam ConstraintType name of the concrete constraint type, which must
* provide a copy constructor.
*/
template<typename ConstraintType>
struct BasicConstraint: Constraint
{
virtual ~BasicConstraint<ConstraintType>() { }
virtual bool accept(ConstraintVisitor &visitor) const
{
return visitor.visit(*static_cast<const ConstraintType*>(this));
}
virtual Constraint * clone() const
{
return new ConstraintType(*static_cast<const ConstraintType*>(this));
}
};
} // namespace constraints
} // namespace valijson
#endif

View File

@ -0,0 +1,441 @@
/**
* @file
*
* @brief Class definitions to support JSON Schema constraints
*
* This file contains class definitions for all of the constraints required to
* support JSON Schema. These classes all inherit from the BasicConstraint
* template class, which implements the common parts of the Constraint
* interface.
*
* @see BasicConstraint
* @see Constraint
*/
#ifndef __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP
#define __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP
#include <limits>
#include <set>
#include <string>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <valijson/adapters/frozen_value.hpp>
#include <valijson/constraints/basic_constraint.hpp>
#include <valijson/schema.hpp>
namespace valijson {
class Schema;
namespace constraints {
/**
* @brief Represents an 'allOf' constraint.
*
* An allOf constraint provides a collection of sub-schemas that a value must
* validate against. If a value fails to validate against any of these sub-
* schemas, then validation fails.
*/
struct AllOfConstraint: BasicConstraint<AllOfConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
AllOfConstraint(const Schemas &schemas)
: schemas(schemas) { }
/// Collection of schemas that must all be satisfied
const Schemas schemas;
};
/**
* @brief Represents an 'anyOf' constraint
*
* An anyOf constraint provides a collection of sub-schemas that a value can
* validate against. If a value validates against one of these sub-schemas,
* then the validation passes.
*/
struct AnyOfConstraint: BasicConstraint<AnyOfConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
AnyOfConstraint(const Schemas &schemas)
: schemas(schemas) { }
/// Collection of schemas of which one must be satisfied
const Schemas schemas;
};
/**
* @brief Represents a 'dependencies' constraint.
*
* A dependency constraint ensures that a given property is valid only if the
* properties that it depends on are present.
*/
struct DependenciesConstraint: BasicConstraint<DependenciesConstraint>
{
// A mapping from property names to the set of names of their dependencies
typedef std::set<std::string> Dependencies;
typedef std::map<std::string, Dependencies> PropertyDependenciesMap;
// A mapping from property names to dependent schemas
typedef boost::ptr_map<std::string, Schema> PropertyDependentSchemasMap;
DependenciesConstraint(const PropertyDependenciesMap &dependencies,
const PropertyDependentSchemasMap &dependentSchemas)
: dependencies(dependencies),
dependentSchemas(dependentSchemas) { }
const PropertyDependenciesMap dependencies;
const PropertyDependentSchemasMap dependentSchemas;
};
/**
* @brief Represents an 'enum' constraint.
*
* An enum constraint provides a set of permissible values for a JSON node. The
* node will only validate against this constraint if it matches one of the
* values in the set.
*/
struct EnumConstraint: BasicConstraint<EnumConstraint>
{
typedef boost::ptr_vector<adapters::FrozenValue> Values;
EnumConstraint(const Values &values) // Copy each of the frozen values
: values(values) { }
const Values values;
};
/**
* @brief Represents a pair of 'items' and 'additionalItems' constraints.
*/
struct ItemsConstraint: BasicConstraint<ItemsConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
/**
* @brief Construct a singular item constraint that allows no additional
* items
*
* @param itemSchema
*/
ItemsConstraint(const Schema &itemSchema)
: itemSchema(new Schema(itemSchema)) { }
/**
* @brief Construct a singular item schema that allows additional items
*
* @param itemSchema
* @param additionalItemsSchema
*/
ItemsConstraint(const Schema &itemSchema,
const Schema &additionalItemsSchema)
: itemSchema(new Schema(itemSchema)),
additionalItemsSchema(new Schema(additionalItemsSchema)) { }
/**
* @brief Construct a plural items constraint that does not allow for
* additional item schemas
*
* @param itemSchemas collection of item schemas
*/
ItemsConstraint(const Schemas &itemSchemas)
: itemSchemas(new Schemas(itemSchemas)) { }
/**
* @brief Construct a plural items constraint that allows additional items
*
* @param itemSchemas
* @param additionalItemsSchema
*/
ItemsConstraint(const Schemas &itemSchemas,
const Schema &additionalItemsSchema)
: itemSchemas(new Schemas(itemSchemas)),
additionalItemsSchema(new Schema(additionalItemsSchema)) { }
/**
* @brief Copy constructor
*/
ItemsConstraint(const ItemsConstraint &other)
: itemSchema(other.itemSchema ? new Schema(*other.itemSchema.get()) : NULL),
itemSchemas(other.itemSchemas ? new Schemas(*other.itemSchemas.get()) : NULL),
additionalItemsSchema(other.additionalItemsSchema ? new Schema(*other.additionalItemsSchema.get()) : NULL) { }
const boost::scoped_ptr<const Schema> itemSchema;
const boost::scoped_ptr<const Schemas> itemSchemas;
const boost::scoped_ptr<const Schema> additionalItemsSchema;
};
/**
* @brief Represents a 'maximum' constraint.
*/
struct MaximumConstraint: BasicConstraint<MaximumConstraint>
{
MaximumConstraint(double maximum, bool exclusiveMaximum)
: maximum(maximum),
exclusiveMaximum(exclusiveMaximum) { }
const double maximum;
const bool exclusiveMaximum;
};
/**
* @brief Represents a 'maxItems' constraint.
*/
struct MaxItemsConstraint: BasicConstraint<MaxItemsConstraint>
{
MaxItemsConstraint(int64_t maxItems)
: maxItems(maxItems) { }
const int64_t maxItems;
};
/**
* @brief Represents a 'maxLength' constraint.
*/
struct MaxLengthConstraint: BasicConstraint<MaxLengthConstraint>
{
MaxLengthConstraint(int64_t maxLength)
: maxLength(maxLength) { }
const int64_t maxLength;
};
/**
* @brief Represents a 'maxProperties' constraint.
*/
struct MaxPropertiesConstraint: BasicConstraint<MaxPropertiesConstraint>
{
MaxPropertiesConstraint(int64_t maxProperties)
: maxProperties(maxProperties) { }
const int64_t maxProperties;
};
/**
* @brief Represents a pair of 'minimum' and 'exclusiveMinimum' constraints.
*/
struct MinimumConstraint: BasicConstraint<MinimumConstraint>
{
MinimumConstraint(double minimum, bool exclusiveMinimum)
: minimum(minimum),
exclusiveMinimum(exclusiveMinimum) { }
const double minimum;
const bool exclusiveMinimum;
};
/**
* @brief Represents a 'minItems' constraint.
*/
struct MinItemsConstraint: BasicConstraint<MinItemsConstraint>
{
MinItemsConstraint(int64_t minItems)
: minItems(minItems) { }
const int64_t minItems;
};
/**
* @brief Represents a 'minLength' constraint.
*/
struct MinLengthConstraint: BasicConstraint<MinLengthConstraint>
{
MinLengthConstraint(int64_t minLength)
: minLength(minLength) { }
const int64_t minLength;
};
/**
* @brief Represents a 'minProperties' constraint.
*/
struct MinPropertiesConstraint: BasicConstraint<MinPropertiesConstraint>
{
MinPropertiesConstraint(int64_t minProperties)
: minProperties(minProperties) { }
const int64_t minProperties;
};
/**
* @brief Represents a 'multipleOf' or 'divisibleBy' constraint.
*/
struct MultipleOfConstraint: BasicConstraint<MultipleOfConstraint>
{
MultipleOfConstraint(double multipleOf)
: multipleOf(multipleOf),
epsilon(std::numeric_limits<double>::epsilon()) { }
const double multipleOf;
const double epsilon;
};
/**
* @brief Represents a 'not' constraint.
*/
struct NotConstraint: BasicConstraint<NotConstraint>
{
NotConstraint(const Schema &schema)
: schema(new Schema(schema)) { }
NotConstraint(const NotConstraint &other)
: schema(other.schema ? new Schema(*other.schema) : NULL) { }
const boost::scoped_ptr<const Schema> schema;
};
/**
* @brief Represents a 'oneOf' constraint.
*/
struct OneOfConstraint: BasicConstraint<OneOfConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
OneOfConstraint(const Schemas &schemas)
: schemas(schemas) { }
/// Collection of schemas that must all be satisfied
const Schemas schemas;
};
/**
* @brief Represents a 'pattern' constraint.
*/
struct PatternConstraint: BasicConstraint<PatternConstraint>
{
PatternConstraint(const std::string &pattern)
: pattern(pattern) { }
const std::string pattern;
};
/**
* @brief Represents a tuple of 'properties', 'patternProperties' and
* 'additionalProperties' constraints.
*/
struct PropertiesConstraint: BasicConstraint<PropertiesConstraint> {
typedef boost::ptr_map<std::string, Schema> PropertySchemaMap;
PropertiesConstraint(const PropertySchemaMap &properties,
const PropertySchemaMap &patternProperties)
: properties(properties),
patternProperties(patternProperties) { }
PropertiesConstraint(const PropertySchemaMap &properties,
const PropertySchemaMap &patternProperties,
const Schema &additionalProperties)
: properties(properties),
patternProperties(patternProperties),
additionalProperties(new Schema(additionalProperties)) { }
PropertiesConstraint(const PropertiesConstraint &other)
: properties(other.properties),
patternProperties(other.patternProperties),
additionalProperties(other.additionalProperties ?
new Schema(*other.additionalProperties.get()) : NULL) {}
const PropertySchemaMap properties;
const PropertySchemaMap patternProperties;
const boost::scoped_ptr<const Schema> additionalProperties;
};
/**
* @brief Represents a 'required' constraint.
*/
struct RequiredConstraint: BasicConstraint<RequiredConstraint>
{
typedef std::set<std::string> RequiredProperties;
RequiredConstraint(const RequiredProperties &requiredProperties)
: requiredProperties(requiredProperties)
{
}
const RequiredProperties requiredProperties;
};
/**
* @brief Represents a 'type' constraint.
*/
struct TypeConstraint: BasicConstraint<TypeConstraint>
{
enum JsonType {
kAny,
kArray,
kBoolean,
kInteger,
kNull,
kNumber,
kObject,
kString
};
typedef std::set<JsonType> JsonTypes;
typedef boost::ptr_vector<Schema> Schemas;
TypeConstraint(const JsonType jsonType)
: jsonTypes(makeJsonTypes(jsonType)) { }
TypeConstraint(const JsonTypes jsonTypes)
: jsonTypes(jsonTypes) { }
TypeConstraint(const JsonTypes jsonTypes,
const Schemas &schemas)
: jsonTypes(jsonTypes),
schemas(schemas) { }
static JsonTypes makeJsonTypes(const JsonType jsonType)
{
JsonTypes jsonTypes;
jsonTypes.insert(jsonType);
return jsonTypes;
}
static JsonType jsonTypeFromString(const std::string &typeName)
{
if (typeName.compare("any") == 0) {
return kAny;
} else if (typeName.compare("array") == 0) {
return kArray;
} else if (typeName.compare("boolean") == 0) {
return kBoolean;
} else if (typeName.compare("integer") == 0) {
return kInteger;
} else if (typeName.compare("null") == 0) {
return kNull;
} else if (typeName.compare("number") == 0) {
return kNumber;
} else if (typeName.compare("object") == 0) {
return kObject;
} else if (typeName.compare("string") == 0) {
return kString;
}
throw std::runtime_error("Unrecognised JSON type name '" + typeName + "'");
}
const JsonTypes jsonTypes;
const Schemas schemas;
};
/**
* @brief Represents a 'uniqueItems' constraint.
*/
struct UniqueItemsConstraint: BasicConstraint<UniqueItemsConstraint>
{
// Don't need anything here.
};
} // namespace constraints
} // namespace valijson
#endif

View File

@ -0,0 +1,52 @@
#ifndef __VALIJSON_CONSTRAINTS_CONSTRAINT_HPP
#define __VALIJSON_CONSTRAINTS_CONSTRAINT_HPP
namespace valijson {
namespace constraints {
class ConstraintVisitor;
/**
* @brief Interface that must be implemented by concrete constraint types.
*
* @todo Consider using something like the boost::cloneable concept here.
*/
struct Constraint {
/**
* @brief Virtual destructor.
*/
virtual ~Constraint() { }
/**
* @brief Perform an action on the constraint using the visitor pattern.
*
* Note that Constraints cannot be modified by visitors.
*
* @param visitor Reference to a ConstraintVisitor object.
*
* @returns the boolean value returned by one of the visitor's visit
* functions.
*/
virtual bool accept(ConstraintVisitor &visitor) const = 0;
/**
* @brief Make a copy of a constraint.
*
* Note that this should be a deep copy of the constraint.
*
* @returns an owning-pointer to the new constraint.
*/
virtual Constraint * clone() const = 0;
};
inline Constraint * new_clone(const Constraint &constraint)
{
return constraint.clone();
}
} // namespace constraints
} // namespace valijson
#endif

View File

@ -0,0 +1,86 @@
#ifndef __VALIJSON_CONSTRAINTS_CONSTRAINT_VISITOR_HPP
#define __VALIJSON_CONSTRAINTS_CONSTRAINT_VISITOR_HPP
namespace valijson {
namespace constraints {
struct AllOfConstraint;
struct AnyOfConstraint;
struct DependenciesConstraint;
struct EnumConstraint;
struct ItemsConstraint;
struct FormatConstraint;
struct MaximumConstraint;
struct MaxItemsConstraint;
struct MaxLengthConstraint;
struct MaxPropertiesConstraint;
struct MinimumConstraint;
struct MinItemsConstraint;
struct MinLengthConstraint;
struct MinPropertiesConstraint;
struct MultipleOfConstraint;
struct NotConstraint;
struct OneOfConstraint;
struct PatternConstraint;
struct PropertiesConstraint;
struct RequiredConstraint;
struct TypeConstraint;
struct UniqueItemsConstraint;
class ConstraintVisitor
{
protected:
// Shorten type names for derived classes outside of this namespace
typedef constraints::AllOfConstraint AllOfConstraint;
typedef constraints::AnyOfConstraint AnyOfConstraint;
typedef constraints::DependenciesConstraint DependenciesConstraint;
typedef constraints::EnumConstraint EnumConstraint;
typedef constraints::ItemsConstraint ItemsConstraint;
typedef constraints::MaximumConstraint MaximumConstraint;
typedef constraints::MaxItemsConstraint MaxItemsConstraint;
typedef constraints::MaxLengthConstraint MaxLengthConstraint;
typedef constraints::MaxPropertiesConstraint MaxPropertiesConstraint;
typedef constraints::MinimumConstraint MinimumConstraint;
typedef constraints::MinItemsConstraint MinItemsConstraint;
typedef constraints::MinLengthConstraint MinLengthConstraint;
typedef constraints::MinPropertiesConstraint MinPropertiesConstraint;
typedef constraints::MultipleOfConstraint MultipleOfConstraint;
typedef constraints::NotConstraint NotConstraint;
typedef constraints::OneOfConstraint OneOfConstraint;
typedef constraints::PatternConstraint PatternConstraint;
typedef constraints::PropertiesConstraint PropertiesConstraint;
typedef constraints::RequiredConstraint RequiredConstraint;
typedef constraints::TypeConstraint TypeConstraint;
typedef constraints::UniqueItemsConstraint UniqueItemsConstraint;
public:
virtual bool visit(const AllOfConstraint &) = 0;
virtual bool visit(const AnyOfConstraint &) = 0;
virtual bool visit(const DependenciesConstraint &) = 0;
virtual bool visit(const EnumConstraint &) = 0;
virtual bool visit(const ItemsConstraint &) = 0;
virtual bool visit(const MaximumConstraint &) = 0;
virtual bool visit(const MaxItemsConstraint &) = 0;
virtual bool visit(const MaxLengthConstraint &) = 0;
virtual bool visit(const MaxPropertiesConstraint &) = 0;
virtual bool visit(const MinimumConstraint &) = 0;
virtual bool visit(const MinItemsConstraint &) = 0;
virtual bool visit(const MinLengthConstraint &) = 0;
virtual bool visit(const MinPropertiesConstraint &) = 0;
virtual bool visit(const MultipleOfConstraint &) = 0;
virtual bool visit(const NotConstraint &) = 0;
virtual bool visit(const OneOfConstraint &) = 0;
virtual bool visit(const PatternConstraint &) = 0;
virtual bool visit(const PropertiesConstraint &) = 0;
virtual bool visit(const RequiredConstraint &) = 0;
virtual bool visit(const TypeConstraint &) = 0;
virtual bool visit(const UniqueItemsConstraint &) = 0;
};
} // namespace constraints
} // namespace valijson
#endif

232
include/valijson/schema.hpp Normal file
View File

@ -0,0 +1,232 @@
#ifndef __VALIJSON_SCHEMA_HPP
#define __VALIJSON_SCHEMA_HPP
#include <boost/foreach.hpp>
#include <boost/function.hpp>
#include <boost/optional.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <valijson/constraints/constraint.hpp>
namespace valijson {
/**
* @brief Class that holds a list of Constraints that together form a schema.
*
* This class maintains an internal list of Constraint objects that define a
* schema. It provides useful functionality such as the ability to easily make
* independent copies of a schema.
*
* Schemas can be modified after construction by adding more constraints, or
* by setting a schema title.
*/
class Schema
{
public:
/// Typedef the Constraint class into the local namespace for convenience
typedef constraints::Constraint Constraint;
/// Typedef for a function that can be applied to each of the Constraint
/// instances owned by a Schema.
typedef boost::function<bool (const Constraint &)> ApplyFunction;
/**
* @brief Construct a new Schema object with no constraints, using the
* default scope.
*
* The constructed Schema object will have no constraints.
*/
Schema() { }
/**
* @brief Construct a new Schema object with no constraints, and inherit
* the scope of another Schema.
*
* The functions getScope(), getCanonicalURI() and getInheritedURI() use
* the scope provided by this constructor, unless the URI for this schema
* specifies its own scope.
*
* @param parentScope Scope to inherit.
*/
Schema(const std::string &parentScope)
: parentScope(parentScope) {}
/**
* @brief Construct a new Schema based an existing Schema object.
*
* The constructed Schema object will contain a copy of each constraint
* defined in the referenced Schema.
*
* @param schema schema to copy constraints from
*/
Schema(const Schema &schema)
: constraints(schema.constraints),
parentScope(schema.parentScope),
title(schema.title) { }
/**
* @brief Add a constraint to the schema.
*
* A copy of the referenced Constraint object will be added to the Schema.
*
* @param constraint Reference to the constraint to copy.
*/
void addConstraint(const Constraint &constraint)
{
constraints.push_back(constraint.clone());
}
/**
* @brief Add a constraint to the schema.
*
* The schema will take ownership of the provided Constraint.
*
* @param constraint Pointer to the constraint to take ownership of.
*/
void addConstraint(Constraint *constraint)
{
constraints.push_back(constraint);
}
/**
* @brief Invoke a function on each constraint in the schema.
*
* This function will apply the callback function to each constraint in
* the schema, even if one of the invokations returns false. If a single
* invokation returns false, this function will return false.
*
* @returns true if all invokations of the callback function are
* successful, false otherwise.
*/
bool apply(ApplyFunction &applyFunction) const
{
bool allTrue = true;
BOOST_FOREACH( const Constraint &constraint, constraints ) {
allTrue = allTrue && applyFunction(constraint);
}
return allTrue;
}
/**
* @brief Invoke a function on each constraint in the schema.
*
* This is a stricter version of the apply() function that will return
* immediately if any of the invokations return false.
*
* @returns true if all invokations of the callback function are
* successful, false otherwise.
*/
bool applyStrict(ApplyFunction &applyFunction) const
{
BOOST_FOREACH( const Constraint &constraint, constraints ) {
if (!applyFunction(constraint)) {
return false;
}
}
return true;
}
std::string getId() const
{
if (id) {
return *id;
}
throw std::runtime_error("id has not been set");
}
std::string getScope() const
{
return std::string();
}
std::string getUri() const
{
return std::string();
}
/**
* @brief Get the title for this schema.
*
* If the title has not been set, this function will throw an exception.
*
* @throw std::runtime_error
*
* @return schema title string
*/
std::string getTitle() const
{
if (title) {
return *title;
}
throw std::runtime_error("Schema does not have a title.");
}
/**
* @brief Returns a boolean value that indicates whether the id has been
* set or not.
*
* @return boolean value
*/
bool hasId() const
{
return id;
}
/**
* @brief Returns a boolean value that indicates whether the schema title
* has been set or not.
*
* @return boolean value
*/
bool hasTitle() const
{
return title;
}
std::string resolveUri(const std::string &relative) const
{
return std::string();
}
void setId(const std::string &id)
{
this->id = id;
}
/**
* @brief Set the title for this schema.
*
* The title is not used for validation, but can be used as part of the
* validation error descriptions that are produced by the Validator and
* ValidationVisitor classes.
*
* @param title new title
*/
void setTitle(const std::string &title)
{
this->title = title;
}
private:
/// List of pointers to constraints that apply to this schema.
boost::ptr_vector<Constraint> constraints;
/// Id to apply when resolving the schema URI.
boost::optional<std::string> id;
/// Scope inherited from a parent schema, or an empty string by default
boost::optional<std::string> parentScope;
/// Title string associated with the schema (optional).
boost::optional<std::string> title;
};
} // namespace valijson
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
#ifndef __VALIJSON_FILE_UTILS_HPP
#define __VALIJSON_FILE_UTILS_HPP
#include <fstream>
namespace valijson {
namespace utils {
/**
* Load a file into a string
*
* @param path path to the file to be loaded
* @param dest string into which file should be loaded
*
* @return true if loaded, false otherwise
*/
inline bool loadFile(const std::string &path, std::string &dest)
{
// Open file for reading
std::ifstream file(path.c_str());
if (!file.is_open()) {
return false;
}
// Allocate space for file contents
file.seekg(0, std::ios::end);
dest.clear();
dest.reserve(file.tellg());
// Assign file contents to destination string
file.seekg(0, std::ios::beg);
dest.assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
return true;
}
} // namespace utils
} // namespace valijson
#endif

View File

@ -0,0 +1,34 @@
#ifndef __VALIJSON_UTILS_JSONCPP_UTILS_HPP
#define __VALIJSON_UTILS_JSONCPP_UTILS_HPP
#include <json/json.h>
#include <valijson/utils/file_utils.hpp>
namespace valijson {
namespace utils {
inline bool loadDocument(const std::string &path, Json::Value &document)
{
// Load schema JSON from file
std::string file;
if (!loadFile(path, file)) {
std::cerr << "Failed to load json from file '" << path << "'." << std::endl;
return false;
}
Json::Reader reader;
bool parsingSuccessful = reader.parse(file, document);
if (!parsingSuccessful) {
std::cerr << "Jsoncpp parser failed to parse the document:" << std::endl
<< reader.getFormatedErrorMessages();
return false;
}
return true;
}
} // namespace utils
} // namespace valijson
#endif

View File

@ -0,0 +1,41 @@
#ifndef __VALIJSON_UTILS_PROPERTY_TREE_UTILS_HPP
#define __VALIJSON_UTILS_PROPERTY_TREE_UTILS_HPP
#include <sstream>
#include <json/json.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/detail/json_parser_error.hpp>
#include <valijson/utils/file_utils.hpp>
namespace valijson {
namespace utils {
inline bool loadDocument(const std::string &path, boost::property_tree::ptree &document)
{
// Load schema JSON from file
std::string file;
if (!loadFile(path, file)) {
std::cerr << "Failed to load json from file '" << path << "'." << std::endl;
return false;
}
std::istringstream is(file);
try {
boost::property_tree::read_json(is, document);
} catch (boost::property_tree::json_parser::json_parser_error &e) {
std::cerr << "Boost Property Tree JSON parser failed to parse the document:" << std::endl;
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
} // namespace utils
} // namespace valijson
#endif

View File

@ -0,0 +1,35 @@
#ifndef __VALIJSON_UTILS_RAPIDJSON_UTILS_HPP
#define __VALIJSON_UTILS_RAPIDJSON_UTILS_HPP
#include <rapidjson/document.h>
#include <valijson/utils/file_utils.hpp>
namespace valijson {
namespace utils {
inline bool loadDocument(const std::string &path, rapidjson::Document &document)
{
// Load schema JSON from file
std::string file;
if (!loadFile(path, file)) {
std::cerr << "Failed to load json from file '" << path << "'." << std::endl;
return false;
}
// Parse schema
document.Parse<0>(file.c_str());
if (document.HasParseError()) {
std::cerr << "RapidJson failed to parse the document:" << std::endl;
std::cerr << "Parse error: " << document.GetParseError() << std::endl;
std::cerr << "Near: " << file.substr(std::max(size_t(0), document.GetErrorOffset() - 20), 40) << std::endl;
return false;
}
return true;
}
} // namespace utils
} // namespace valijson
#endif

View File

@ -0,0 +1,108 @@
#ifndef __VALIJSON_VALIDATION_RESULTS_HPP
#define __VALIJSON_VALIDATION_RESULTS_HPP
#include <deque>
#include <string>
namespace valijson {
/**
* @brief Class that encapsulates the storage of validation errors.
*
* This class maintains an internal FIFO queue of errors that are reported
* during validation. Errors are pushed on to the back of an internal
* queue, and can retrieved by popping them from the front of the queue.
*/
class ValidationResults
{
public:
/**
* @brief Describes a validation error.
*
* This struct is used to pass around the context and description of a
* validation error.
*/
struct Error
{
/**
* @brief Construct an Error object with no context or description.
*/
Error() { }
/**
* @brief Construct an Error object using a context and description.
*
* @param context Context string to use
* @param description Description string to use
*/
Error(const std::string &context, const std::string &description)
: context(context),
description(description) { }
/// Path to the node that failed validation.
std::string context;
/// A detailed description of the validation error.
std::string description;
};
/**
* @brief Return the number of errors in the queue.
*/
size_t numErrors() const
{
return errors.size();
}
/**
* @brief Copy an Error and push it on to the back of the queue.
*
* @param error Reference to an Error object to be copied.
*/
void pushError(const Error &error)
{
errors.push_back(error);
}
/**
* @brief Push an error onto the back of the queue.
*
* @param context Context of the validation error.
* @param description Description of the validation error.
*/
void
pushError(const std::string &context, const std::string &description)
{
errors.push_back(Error(context, description));
}
/**
* @brief Pop an error from the front of the queue.
*
* @param error Reference to an Error object to populate.
*
* @returns true if an Error was popped, false otherwise.
*/
bool
popError(Error &error)
{
if (errors.empty()) {
return false;
}
error = errors.front();
errors.pop_front();
return true;
}
private:
/// FIFO queue of validation errors that have been reported
std::deque<Error> errors;
};
} // namespace valijson
#endif // __VALIJSON_VALIDATION_RESULTS_HPP

View File

@ -0,0 +1,971 @@
#ifndef __VALIJSON_VALIDATION_VISITOR_HPP
#define __VALIJSON_VALIDATION_VISITOR_HPP
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <valijson/constraints/concrete_constraints.hpp>
#include <valijson/constraints/constraint_visitor.hpp>
#include <valijson/validation_results.hpp>
namespace valijson {
class ValidationResults;
/**
* @brief Implementation of the ConstraintVisitor interface that validates a
* target document.
*
* @tparam AdapterType Adapter type for the target document.
*/
template<typename AdapterType>
class ValidationVisitor: public constraints::ConstraintVisitor
{
public:
/**
* @brief Construct a new validator for a given target value and context.
*
* @param target Target value to be validated
* @param context Current context for validation error descriptions,
* only used if results is set.
* @param strictTypes Use strict type comparison
* @param results Optional pointer to ValidationResults object, for
* recording error descriptions. If this pointer is set
* to NULL, validation errors will caused validation to
* stop immediately.
*/
ValidationVisitor(const AdapterType &target,
const std::string &context,
const bool strictTypes,
ValidationResults *results)
: target(target),
context(context),
strictTypes(strictTypes),
results(results) { }
/**
* @brief Validate the target against a schema.
*
* When a ValidationResults object has been set via the 'results' member
* variable, validation will proceed as long as no fatal errors occur,
* with error descriptions added to the ValidationResults object.
*
* If a pointer to a ValidationResults instance is not provided, validation
* will only continue for as long as the constraints are validated
* successfully.
*
* @param schema Schema that the target must validate against
*
* @return true if validation passes, false otherwise
*/
bool validateSchema(const Schema &schema)
{
// Wrap the validationCallback() function below so that it will be
// passed a reference to a constraint (_1), and a reference to the
// visitor (*this).
Schema::ApplyFunction fn(boost::bind(validationCallback, _1, *this));
// Perform validation against each constraint defined in the schema
if (results == NULL) {
// The applyStrict() function will return immediately if the
// callback function returns false
if (!schema.applyStrict(fn)) {
return false;
}
} else {
// The apply() function will iterate over all constraints in the
// schema, even if the callback function returns false. Once
// iteration is complete, the apply() function will return true
// only if all invokations of the callback function returned true.
if (!schema.apply(fn)) {
return false;
}
}
return true;
}
/**
* @brief Validate the target against an allOf constraint.
*
* An allOf constraint provides a set of child schemas against which the
* target must be validated in order for the constraint to the satifisfied.
*
* When a ValidationResults object has been set via the 'results' member
* variable, validation will proceed as long as no fatal errors occur,
* with error descriptions added to the ValidationResults object.
*
* If a pointer to a ValidationResults instance is not provided, validation
* will only continue for as long as the child schemas are validated
* successfully.
*
* @param constraint Constraint that the target must validate against
*
* @return true if validation passes, false otherwise
*/
virtual bool visit(const AllOfConstraint &constraint)
{
// Flag used to track validation status if errors are non-fatal
bool validated = true;
// Validate against each child schema
unsigned int index = 0;
BOOST_FOREACH( const Schema &schema, constraint.schemas ) {
// Ensure that the target validates against child schema
if (!validateSchema(schema)) {
if (results) {
validated = false;
results->pushError(context,
std::string("Failed to validate against child schema at index #") +
boost::lexical_cast<std::string>(index) + " of allOf constraint.");
} else {
return false;
}
}
index++;
}
return validated;
}
/**
* @brief Validate against the anyOf constraint represented by an
* AnyOfConstraint object.
*
* An anyOf constraint provides a set of child schemas, any of which the
* target may be validated against in order for the constraint to the
* satifisfied.
*
* Because an anyOf constraint does not require the target to validate
* against all child schemas, if validation against a single schema fails,
* the results will not be added to a ValidationResults object. Only if
* validation fails for all child schemas will an error be added to the
* ValidationResults object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if validation passes, false otherwise.
*/
virtual bool visit(const AnyOfConstraint &constraint)
{
// Wrap the validationCallback() function below so that it will be
// passed a reference to a constraint (_1), and a reference to the
// visitor (*this).
Schema::ApplyFunction fn(boost::bind(validationCallback, _1, *this));
BOOST_FOREACH( const Schema &schema, constraint.schemas ) {
if (schema.apply(fn)) {
return true;
}
}
if (results) {
results->pushError(context, "Failed to validate against any child schemas.");
}
return false;
}
/**
* @brief Validate against the dependencies constraint represented by a
* DependenciesConstraint object.
*
* A dependencies constraint can specify either a mapping of attribute names
* to their dependencies, or a mapping of attribute names to child schemas
* that must be satisfied if a given attribute is present.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if validation passes, false otherwise.
*/
virtual bool visit(const DependenciesConstraint &constraint)
{
// Ignore non-objects
if (!target.isObject()) {
return true;
}
// Typedef and reference for conciseness in nested loops
typedef DependenciesConstraint::PropertyDependenciesMap PDM;
const PDM &deps = constraint.dependencies;
typedef DependenciesConstraint::PropertyDependentSchemasMap PDSM;
const PDSM &depSchemas = constraint.dependentSchemas;
// Get access to the target as an object
const typename AdapterType::Object object = target.getObject();
// Flag used to track validation status if errors are non-fatal
bool validated = true;
// For each property in the object, check for a list of dependencies in
// the constraint object and verify that the dependencies have been
// satisfied.
BOOST_FOREACH( const typename AdapterType::ObjectMember m, object ) {
// Check for this property in the dependency map. If it is not
// present, we can move on to the next one...
PDM::const_iterator itr = deps.find(m.first);
if (itr != deps.end()) {
BOOST_FOREACH( const std::string &name, itr->second ) {
if (object.find(name) == object.end()) {
if (!results) {
return false;
}
validated = false;
results->pushError(context, "Missing dependency '" + name + "'.");
}
}
}
// Check for this property in the dependent schemas map. If it is
// present then we need to validate the current target against the
// dependent schema.
PDSM::const_iterator depSchemasItr = depSchemas.find(m.first);
if (depSchemasItr != depSchemas.end()) {
const Schema *schema = depSchemasItr->second;
if (!validateSchema(*schema)) {
if (results) {
results->pushError(context, "Failed to validate against dependent schema.");
validated = false;
} else {
return false;
}
}
}
}
return validated;
}
/**
* @brief Validate against the enum constraint represented by an
* EnumConstraint object.
*
* Validation succeeds if the target is equal to one of the values provided
* by the enum constraint.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if validation succeeds, false otherwise.
*/
virtual bool visit(const EnumConstraint &constraint)
{
// Compare the target with each 'frozen' value in the enum constraint.
BOOST_FOREACH( const adapters::FrozenValue &value, constraint.values ) {
if (value.equalTo(target, true)) {
return true;
}
}
if (results) {
results->pushError(context, "Failed to match against any enum values.");
}
return false;
}
/**
* @brief Validate against the items and additionalItems constraints
* represented by an ItemsConstraint object.
*
* An items constraint restricts the values in array to those that match a
* given set of schemas. An item constraint can specify either an ordered
* list of child schemas that will be used to validate the corresponding
* value in the target array, or a single schema that will be used to
* validate all items.
*
* If a list of child schemas is used, then the additionalItems constraint
* will also be considered. If present, the schema derived from the
* additionalItems constraint will be used to validate items that do not
* have a corresponding child schema in the items constraint. If the
* items constraint was not provided, then the additionalItems schema will
* be used to validate all items in the array.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if validatation succeeds, false otherwise.
*/
virtual bool visit(const ItemsConstraint &constraint)
{
// Ignore values that are not arrays
if (!target.isArray()) {
return true;
}
bool validated = true;
if (constraint.itemSchema) {
// Validate all items against single schema
unsigned int index = 0;
BOOST_FOREACH( const AdapterType arrayItem, target.getArray() ) {
ValidationVisitor<AdapterType> v(arrayItem,
context + "[" + boost::lexical_cast<std::string>(index) + "]",
strictTypes,
results);
if (!v.validateSchema(*constraint.itemSchema)) {
if (results) {
results->pushError(context, "Failed to validate item #" + boost::lexical_cast<std::string>(index) + " in array.");
validated = false;
} else {
return false;
}
}
++index;
}
} else if (constraint.itemSchemas) {
if (!constraint.additionalItemsSchema) {
// Check that the array length is <= length of the itemsSchema list
if (target.getArray().size() > constraint.itemSchemas->size()) {
if (results) {
results->pushError(context, "Array contains more items than allowed by items constraint.");
validated = false;
} else {
return false;
}
}
}
// Validate items against the schema with the same index, or
// additionalItems schema
unsigned int index = 0;
BOOST_FOREACH( const AdapterType arrayItem, target.getArray() ) {
ValidationVisitor<AdapterType> v(arrayItem,
context + "[" + boost::lexical_cast<std::string>(index) + "]",
strictTypes,
results);
if (index >= constraint.itemSchemas->size()) {
if (constraint.additionalItemsSchema) {
if (!v.validateSchema(*constraint.additionalItemsSchema)) {
if (results) {
results->pushError(context, "Failed to validate item #" +
boost::lexical_cast<std::string>(index) + " against additional items schema.");
validated = false;
} else {
return false;
}
}
} else {
results->pushError(context, "Cannot validate item #" +
boost::lexical_cast<std::string>(index) + " in array due to missing schema.");
validated = false;
}
} else if (!v.validateSchema(constraint.itemSchemas->at(index))) {
if (results) {
results->pushError(context, "Failed to validate item #" +
boost::lexical_cast<std::string>(index) + " against corresponding item schema.");
validated = false;
} else {
return false;
}
}
++index;
}
} else if (constraint.additionalItemsSchema) {
// Validate each item against additional items schema
BOOST_FOREACH( const AdapterType arrayItem, target.getArray() ) {
ValidationVisitor<AdapterType> v(arrayItem,
context + "[" + boost::lexical_cast<std::string>(index) + "]",
strictTypes,
results);
if (!v.validateSchema(*constraint.additionalItemsSchema)) {
if (results) {
results->pushError(context, "Failed to validate item #" +
boost::lexical_cast<std::string>(index) + " against additional items schema.");
validated = false;
} else {
return false;
}
}
}
}
return validated;
}
/**
* @brief Validate against the maximum and exclusiveMaximum constraints
* represented by a MaximumConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if constraints are satisfied, false otherwise.
*/
virtual bool visit(const MaximumConstraint &constraint)
{
if (!target.isNumber()) {
// Ignore values that are not numbers
return true;
}
if (constraint.exclusiveMaximum) {
if (target.getNumber() >= constraint.maximum) {
if (results) {
results->pushError(context,
"Expected number less than " + boost::lexical_cast<std::string>(constraint.maximum));
}
return false;
}
} else {
if (target.getNumber() > constraint.maximum) {
if (results) {
results->pushError(context,
"Expected number less than or equal to" +
boost::lexical_cast<std::string>(constraint.maximum));
}
return false;
}
}
return true;
}
/**
* @brief Validate against the maxItems constraint represented by a
* MaxItemsConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if constraint is satisfied, false otherwise.
*/
virtual bool visit(const MaxItemsConstraint &constraint)
{
if (target.isArray() &&
target.getArray().size() > constraint.maxItems) {
if (results) {
results->pushError(context, "Array should contain no more than " +
boost::lexical_cast<std::string>(constraint.maxItems) +
" elements.");
}
return false;
}
return true;
}
/**
* @brief Validate against the maxLength constraint represented by a
* MaxLengthConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if constraint is satisfied, false otherwise.
*/
virtual bool visit(const MaxLengthConstraint &constraint)
{
if (target.isString() &&
target.getString().size() > constraint.maxLength) {
if (results) {
results->pushError(context, "String should be no more than " +
boost::lexical_cast<std::string>(constraint.maxLength) +
" characters in length.");
}
return false;
}
return true;
}
/**
* @brief Validate against the maxProperties constraint represented by a
* MaxPropertiesConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const MaxPropertiesConstraint &constraint)
{
if (target.isObject() &&
target.getObject().size() > constraint.maxProperties) {
if (results) {
results->pushError(context, "Object should have no more than" +
boost::lexical_cast<std::string>(constraint.maxProperties) +
" properties.");
}
return false;
}
return true;
}
/**
* @brief Validate against the minimum constraint represented by a
* MinimumConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const MinimumConstraint &constraint)
{
if (!target.isNumber()) {
// Ignore values that are not numbers
return true;
}
if (constraint.exclusiveMinimum) {
if (target.getNumber() <= constraint.minimum) {
if (results) {
results->pushError(context,
"Expected number greater than " +
boost::lexical_cast<std::string>(constraint.minimum));
}
return false;
}
} else {
if (target.getNumber() < constraint.minimum) {
if (results) {
results->pushError(context,
"Expected number greater than or equal to" +
boost::lexical_cast<std::string>(constraint.minimum));
}
return false;
}
}
return true;
}
/**
* @brief Validate against the minItems constraint represented by a
* MinItemsConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const MinItemsConstraint &constraint)
{
if (target.isArray() &&
target.getArray().size() < constraint.minItems) {
if (results) {
results->pushError(context, "Array should contain no fewer than " +
boost::lexical_cast<std::string>(constraint.minItems) +
" elements.");
}
return false;
}
return true;
}
/**
* @brief Validate against the minLength constraint represented by a
* MinLengthConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const MinLengthConstraint &constraint)
{
if (target.isString() &&
target.getString().size() < constraint.minLength) {
if (results) {
results->pushError(context, "String should be no fewer than " +
boost::lexical_cast<std::string>(constraint.minLength) +
" characters in length.");
}
return false;
}
return true;
}
/**
* @brief Validate against the minProperties constraint represented by a
* MinPropertiesConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const MinPropertiesConstraint &constraint)
{
if (target.isObject() &&
target.getObject().size() < constraint.minProperties) {
if (results) {
results->pushError(context, "Object should have no fewer than" +
boost::lexical_cast<std::string>(constraint.minProperties) +
" properties.");
}
return false;
}
return true;
}
/**
* @brief Validate against the multipleOf or divisibleBy constraints
* represented by a MultipleOfConstraint object.
*
* @todo Not implemented.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const MultipleOfConstraint &constraint)
{
return true;
}
/**
* @brief Validate against the not constraint represented by a
* NotConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const NotConstraint &constraint)
{
ValidationVisitor<AdapterType> v(target, context, strictTypes, NULL);
if (v.validateSchema(*constraint.schema)) {
if (results) {
results->pushError(context, "Target should not validate against schema specified in 'not' constraint.");
}
return false;
}
return true;
}
/**
* @brief Validate against the oneOf constraint represented by a
* OneOfConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const OneOfConstraint &constraint)
{
unsigned int numValidated = 0;
BOOST_FOREACH( const Schema &schema, constraint.schemas ) {
if (validateSchema(schema)) {
numValidated++;
}
}
if (numValidated != 1) {
if (results) {
results->pushError(context, "Failed to validate against exactly one child schema.");
}
return false;
}
return true;
}
/**
* @brief Validate against the pattern constraint represented by a
* PatternConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const PatternConstraint &constraint)
{
if (!target.isString()) {
return true;
}
const boost::regex r(constraint.pattern, boost::regex::perl);
if (!boost::regex_search(target.getString(), r)) {
if (results) {
results->pushError(context, "Failed to match regex specified by 'pattern' constraint.");
}
return false;
}
return true;
}
/**
* @brief Validate against the properties, patternProperties, and
* additionalProperties constraints represented by a
* PatternConstraint object.
*
* @param constraint Constraint that the target must validate against.
*
* @return true if the constraint is satisfied, false otherwise.
*/
virtual bool visit(const PropertiesConstraint &constraint)
{
if (!target.isObject()) {
return true;
}
bool validated = true;
// Validate each property in the target object
BOOST_FOREACH( const typename AdapterType::ObjectMember m, target.getObject() ) {
const std::string propertyName = m.first;
bool propertyNameMatched = false;
ValidationVisitor<AdapterType> v(m.second, context + "." + m.first, strictTypes, results);
// Search for matching property name
PropertiesConstraint::PropertySchemaMap::const_iterator itr =
constraint.properties.find(propertyName);
if (itr != constraint.properties.end()) {
propertyNameMatched = true;
if (!v.validateSchema(*itr->second)) {
if (results) {
results->pushError(context,
"Failed to validate against 'properties' schema associated with property name '" +
propertyName + "'.");
validated = false;
} else {
return false;
}
}
}
// Search for a regex that matches the property name
for (itr = constraint.patternProperties.begin(); itr != constraint.patternProperties.end(); ++itr) {
const boost::regex r(itr->first, boost::regex::perl);
if (boost::regex_search(propertyName, r)) {
propertyNameMatched = true;
// Check schema
if (!v.validateSchema(*itr->second)) {
if (results) {
results->pushError(context,
"Failed to validate against 'patternProperties' schema associated with regex '" +
itr->first + "'.");
validated = false;
} else {
return false;
}
}
}
}
// If the property name has been matched by a name in 'properties'
// or a regex in 'patternProperties', then it should not be
// validated against the 'additionalPatterns' schema.
if (propertyNameMatched) {
continue;
}
// If an additionalProperties schema has been provided, the values
// associated with unmatched property names should be validated
// against that schema.
if (constraint.additionalProperties) {
if (v.validateSchema(*constraint.additionalProperties)) {
continue;
} else if (results) {
results->pushError(context, "Failed to validate property '" +
propertyName + "' against additionalProperties schema.");
validated = false;
} else {
return false;
}
} else if (results) {
results->pushError(context, "Failed to match property name to any names in 'properties' or regexes in 'patternProperties'");
validated = false;
} else {
return false;
}
}
return validated;
}
/**
* @brief Validate against the required constraint represented by a
* RequiredConstraint object.
*
* A required constraint specifies a list of properties that must be present
* in the target.
*
* @param constraint Constraint that the target must validate against
*
* @return true if validation succeeds, false otherwise
*/
virtual bool visit(const RequiredConstraint &constraint)
{
if (!target.isObject()) {
if (results) {
results->pushError(context, "Object required to validate 'required' properties.");
}
return false;
}
bool validated = true;
const typename AdapterType::Object object = target.getObject();
BOOST_FOREACH( const std::string &requiredProperty, constraint.requiredProperties ) {
if (object.find(requiredProperty) == object.end()) {
if (results) {
results->pushError(context, "Missing required property '" + requiredProperty + "'.");
validated = false;
} else {
return false;
}
}
}
return validated;
}
/**
* @brief Validate against the type constraint represented by a
* TypeConstraint object.
*
* Checks that the target is of the expected type.
*
* @param constraint Constraint that the target must validate against
*
* @return true if validation succeeds, false otherwise
*/
virtual bool visit(const TypeConstraint &constraint)
{
// Try to match the type to one of the types in the jsonTypes array
BOOST_FOREACH( const TypeConstraint::JsonType jsonType, constraint.jsonTypes ) {
switch (jsonType) {
case TypeConstraint::kAny:
return true;
case TypeConstraint::kArray:
if (target.isArray()) {
return true;
}
break;
case TypeConstraint::kBoolean:
if (target.isBool() || (!strictTypes && target.maybeBool())) {
return true;
}
break;
case TypeConstraint::kInteger:
if (target.isInteger() || (!strictTypes && target.maybeInteger())) {
return true;
}
break;
case TypeConstraint::kNull:
if (target.isNull() || (!strictTypes && target.maybeNull())) {
return true;
}
break;
case TypeConstraint::kNumber:
if (target.isNumber() || (!strictTypes && target.maybeDouble())) {
return true;
}
break;
case TypeConstraint::kObject:
if (target.isObject()) {
return true;
}
break;
case TypeConstraint::kString:
if (target.isString()) {
return true;
}
break;
}
}
BOOST_FOREACH( const Schema &schema, constraint.schemas ) {
if (validateSchema(schema)) {
return true;
}
}
if (results) {
results->pushError(context, "Value type not permitted by 'type' constraint.");
}
return false;
}
/**
* @brief Validate the uniqueItems constraint represented by a
* UniqueItems object.
*
* A uniqueItems constraint requires that each of the values in an array
* are unique. Comparison is performed recursively.
*
* @param constraint Constraint that the target must validate against
*
* @return true if validation succeeds, false otherwise
*/
virtual bool visit(const UniqueItemsConstraint &constraint)
{
if (!target.isArray()) {
return true;
}
bool validated = true;
const typename AdapterType::Array targetArray = target.getArray();
const typename AdapterType::Array::const_iterator end = targetArray.end();
const typename AdapterType::Array::const_iterator secondLast = end - 1;
unsigned int outerIndex = 0;
for (typename AdapterType::Array::const_iterator outerItr = targetArray.begin(); outerItr != secondLast; ++outerItr) {
unsigned int innerIndex = 0;
for (typename AdapterType::Array::const_iterator innerItr = outerItr + 1; innerItr != end; ++innerItr) {
if (outerItr->equalTo(*innerItr, true)) {
if (results) {
results->pushError(context, "Elements at indexes #" +
boost::lexical_cast<std::string>(outerIndex) + " and #" +
boost::lexical_cast<std::string>(innerIndex) + " violate uniqueness constraint.");
validated = false;
} else {
return false;
}
}
++innerIndex;
}
++outerIndex;
}
return validated;
}
private:
/**
* @brief Callback function that passes a visitor to a constraint.
*
* @param constraint Reference to constraint to be visited
* @param visitor Reference to visitor to be applied
*
* @return true if the visitor returns successfully, false otherwise.
*/
static bool validationCallback(const constraints::Constraint &constraint,
ValidationVisitor<AdapterType> &visitor)
{
return constraint.accept(visitor);
}
/// Reference to the JSON value being validated
const AdapterType &target;
/// String describing the current object context
const std::string context;
/// Optional pointer to a ValidationResults object to be populated
ValidationResults *results;
/// Option to use strict type comparison
const bool strictTypes;
};
} // namespace valijson
#endif

View File

@ -0,0 +1,89 @@
#ifndef __VALIJSON_VALIDATOR_HPP
#define __VALIJSON_VALIDATOR_HPP
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <valijson/schema.hpp>
#include <valijson/validation_visitor.hpp>
namespace valijson {
class Schema;
class ValidationResults;
/**
* @brief Class that wraps a schema and provides validation functionality.
*
* This class wraps a Schema object, and encapsulates the logic required to
* validate rapidjson values aginst the schema.
*/
class Validator {
public:
/**
* @brief Construct a validator using the specified schema.
*
* The schema that is provided will be copied.
*
* @param schema A schema to use for validation
*/
Validator(const Schema &schema)
: schema(new Schema(schema)),
strictTypes(true) { }
/**
* @brief Set flag to use strict comparison during validation.
*
* The default value is true, but this can be changed at any time. Future
* invokations of validate() will make use of the new value.
*
* @param strictTypes whether or not to use strict comparison
*/
void setStrict(bool strictTypes)
{
this->strictTypes = strictTypes;
}
/**
* @brief Validate a JSON document and optionally return the results.
*
* When a ValidationResults object is provided via the \c results parameter,
* validation will be performed against each constraint defined by the
* schema, even if validation fails for some or all constraints.
*
* If a pointer to a ValidationResults instance is not provided, validation
* will only continue for as long as the constraints are validated
* successfully.
*
* @param target A rapidjson::Value to be validated.
*
* @param results An optional pointer to a ValidationResults instance that
* will be used to report validation errors.
*
* @returns true if validation succeeds, false otherwise.
*/
template<typename AdapterType>
bool validate(const AdapterType &target, ValidationResults *results)
{
// Construct a ValidationVisitor to perform validation at the root level
ValidationVisitor<AdapterType> v(target, std::string(),
strictTypes, results);
return v.validateSchema(*schema);
}
private:
/// Pointer to an internal copy of a schema to use for validation
boost::scoped_ptr<const Schema> schema;
/// Flag indicating that strict type comparisons should be used
bool strictTypes;
};
} // namespace valijson
#endif

View File

@ -0,0 +1 @@
[10.0, 20.0, 30.0, 40.0]

View File

@ -0,0 +1 @@
[1.0, 2.0, 3.0]

View File

@ -0,0 +1 @@
[1.0, 2.0, 3.0, 4.0]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[10, 20, 30, 40]

View File

@ -0,0 +1 @@
[1, 2, 3]

View File

@ -0,0 +1 @@
[1, 2, 3, 4]

View File

@ -0,0 +1 @@
["10", "20", "30", "40"]

View File

@ -0,0 +1 @@
["1", "2", "3"]

View File

@ -0,0 +1 @@
["1", "2", "3", "4"]

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,20 @@
{
"comment": "Document must contain an array of integers, all unique.",
"allOf": [
{
"items": {
"type": "integer"
},
"additionalItems": false,
"type": "array"
},
{
"items": {
"type": "number"
},
"additionalItems": false,
"type": "array",
"uniqueItems": true
}
]
}

View File

@ -0,0 +1,157 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <valijson/adapters/jsoncpp_adapter.hpp>
#include <valijson/adapters/property_tree_adapter.hpp>
#include <valijson/adapters/rapidjson_adapter.hpp>
#include <valijson/utils/jsoncpp_utils.hpp>
#include <valijson/utils/property_tree_utils.hpp>
#include <valijson/utils/rapidjson_utils.hpp>
#define TEST_DATA_DIR "../tests/data/documents/"
using valijson::adapters::AdapterTraits;
class TestAdapterComparison : public testing::Test
{
protected:
struct JsonFile
{
JsonFile(const std::string &path, int strictGroup, int looseGroup)
: path(path),
strictGroup(strictGroup),
looseGroup(looseGroup) { }
const std::string path;
int strictGroup;
int looseGroup;
};
static void SetUpTestCase() {
const std::string testDataDir(TEST_DATA_DIR);
//
// Each test is allocated to two groups. The first group is the strict
// comparison group. All test files that have been assigned to the same
// group should be equal, when compared using strict types. The second
// group is the loose comparison group. All tests files in a loose
// group should be equal, when compared without using strict types.
//
// As an example, the first three test files are in the same loose
// group. This means they are expected to be equal when compared without
// strict types. However, only the first two files in the same strict
// group, which means that only they should be equal.
//
jsonFiles.push_back(JsonFile(testDataDir + "array_doubles_1_2_3.json", 1, 1));
jsonFiles.push_back(JsonFile(testDataDir + "array_integers_1_2_3.json", 1, 1));
jsonFiles.push_back(JsonFile(testDataDir + "array_strings_1_2_3.json", 2, 1));
jsonFiles.push_back(JsonFile(testDataDir + "array_doubles_1_2_3_4.json", 3, 2));
jsonFiles.push_back(JsonFile(testDataDir + "array_integers_1_2_3_4.json", 3, 2));
jsonFiles.push_back(JsonFile(testDataDir + "array_strings_1_2_3_4.json", 4, 2));
jsonFiles.push_back(JsonFile(testDataDir + "array_doubles_10_20_30_40.json", 5, 3));
jsonFiles.push_back(JsonFile(testDataDir + "array_integers_10_20_30_40.json", 5, 3));
jsonFiles.push_back(JsonFile(testDataDir + "array_strings_10_20_30_40.json", 6, 3));
}
template<typename Adapter1, typename Adapter2>
static void testComparison()
{
std::vector<JsonFile>::const_iterator outerItr, innerItr;
for(outerItr = jsonFiles.begin(); outerItr != jsonFiles.end() - 1; ++outerItr) {
for(innerItr = outerItr; innerItr != jsonFiles.end(); ++innerItr) {
const bool expectedStrict = (outerItr->strictGroup == innerItr->strictGroup);
const bool expectedLoose = (outerItr->looseGroup == innerItr->looseGroup);
typename AdapterTraits<Adapter1>::DocumentType document1;
ASSERT_TRUE( valijson::utils::loadDocument(outerItr->path, document1) );
const Adapter1 adapter1(document1);
const std::string adapter1Name = AdapterTraits<Adapter1>::adapterName();
typename AdapterTraits<Adapter2>::DocumentType document2;
ASSERT_TRUE( valijson::utils::loadDocument(innerItr->path, document2) );
const Adapter2 adapter2(document2);
const std::string adapter2Name = AdapterTraits<Adapter2>::adapterName();
// If either adapter does not support strict types, then strict
// comparison should not be used, UNLESS the adapters are of the
// same type. If they are of the same type, then the internal
// type degradation should be the same, therefore strict testing
// of equality makes sense.
if (adapter1.hasStrictTypes() && adapter2.hasStrictTypes() && adapter1Name == adapter2Name) {
EXPECT_EQ(expectedStrict, adapter1.equalTo(adapter2, true))
<< "Comparing '" << outerItr->path << "' to '"
<< innerItr->path << "' "
<< "with strict comparison enabled";
EXPECT_EQ(expectedStrict, adapter2.equalTo(adapter1, true))
<< "Comparing '" << innerItr->path << "' to '"
<< outerItr->path << "' "
<< "with strict comparison enabled";
}
EXPECT_EQ(expectedLoose, adapter1.equalTo(adapter2, false))
<< "Comparing '" << outerItr->path << "' to '"
<< innerItr->path << "' "
<< "with strict comparison disabled";
EXPECT_EQ(expectedLoose, adapter2.equalTo(adapter1, false))
<< "Comparing '" << innerItr->path << "' to '"
<< outerItr->path << "' "
<< "with strict comparison disabled";
}
}
}
static std::vector<JsonFile> jsonFiles;
};
std::vector<TestAdapterComparison::JsonFile> TestAdapterComparison::jsonFiles;
TEST_F(TestAdapterComparison, JsonCppVsJsonCpp)
{
testComparison<
valijson::adapters::JsonCppAdapter,
valijson::adapters::JsonCppAdapter>();
}
TEST_F(TestAdapterComparison, JsonCppVsPropertyTree)
{
testComparison<
valijson::adapters::JsonCppAdapter,
valijson::adapters::PropertyTreeAdapter>();
}
TEST_F(TestAdapterComparison, JsonCppVsRapidJson)
{
testComparison<
valijson::adapters::JsonCppAdapter,
valijson::adapters::RapidJsonAdapter>();
}
TEST_F(TestAdapterComparison, PropertyTreeVsPropertyTree)
{
testComparison<
valijson::adapters::PropertyTreeAdapter,
valijson::adapters::PropertyTreeAdapter>();
}
TEST_F(TestAdapterComparison, PropertyTreeVsRapidJson)
{
testComparison<
valijson::adapters::PropertyTreeAdapter,
valijson::adapters::RapidJsonAdapter>();
}
TEST_F(TestAdapterComparison, RapidJsonVsRapidJson)
{
testComparison<
valijson::adapters::RapidJsonAdapter,
valijson::adapters::RapidJsonAdapter>();
}

View File

@ -0,0 +1,15 @@
#include <gtest/gtest.h>
#include <valijson/schema.hpp>
using valijson::Schema;
class TestDereferenceCallback : public ::testing::Test
{
};
TEST_F(TestDereferenceCallback, Basics)
{
}

View File

@ -0,0 +1,82 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <valijson/adapters/jsoncpp_adapter.hpp>
class TestJsonCppAdapter : public testing::Test
{
};
TEST_F(TestJsonCppAdapter, BasicArrayIteration)
{
const unsigned int numElements = 10;
// Create a rapidjson document that consists of an array of numbers
Json::Value document(Json::arrayValue);
for (unsigned int i = 0; i < numElements; i++) {
document.append(Json::Value(i));
}
// Ensure that wrapping the document preserves the array and does not allow
// it to be cast to other types
valijson::adapters::JsonCppAdapter adapter(document);
ASSERT_NO_THROW( adapter.getArray() );
ASSERT_ANY_THROW( adapter.getBool() );
ASSERT_ANY_THROW( adapter.getDouble() );
ASSERT_ANY_THROW( adapter.getObject() );
ASSERT_ANY_THROW( adapter.getString() );
// Ensure that the array contains the expected number of elements
EXPECT_EQ( numElements, adapter.getArray().size() );
// Ensure that the elements are returned in the order they were inserted
unsigned int expectedValue = 0;
BOOST_FOREACH( const valijson::adapters::JsonCppAdapter value, adapter.getArray() ) {
ASSERT_TRUE( value.isNumber() );
EXPECT_EQ( double(expectedValue), value.getNumber() );
expectedValue++;
}
// Ensure that the correct number of elements were iterated over
EXPECT_EQ(numElements, expectedValue);
}
TEST_F(TestJsonCppAdapter, BasicObjectIteration)
{
const unsigned int numElements = 10;
// Create a rapidjson document that consists of an object that maps numeric
// strings their corresponding numeric values
Json::Value document(Json::objectValue);
for (unsigned int i = 0; i < numElements; i++) {
std::string name(boost::lexical_cast<std::string>(i));
document[name] = Json::Value(double(i));
}
// Ensure that wrapping the document preserves the object and does not
// allow it to be cast to other types
valijson::adapters::JsonCppAdapter adapter(document);
ASSERT_NO_THROW( adapter.getObject() );
ASSERT_ANY_THROW( adapter.getArray() );
ASSERT_ANY_THROW( adapter.getBool() );
ASSERT_ANY_THROW( adapter.getDouble() );
ASSERT_ANY_THROW( adapter.getString() );
// Ensure that the object contains the expected number of members
EXPECT_EQ( numElements, adapter.getObject().size() );
// Ensure that the members are returned in the order they were inserted
unsigned int expectedValue = 0;
BOOST_FOREACH( const valijson::adapters::JsonCppAdapter::ObjectMember member, adapter.getObject() ) {
ASSERT_TRUE( member.second.isNumber() );
EXPECT_EQ( boost::lexical_cast<std::string>(expectedValue), member.first );
EXPECT_EQ( double(expectedValue), member.second.getDouble() );
expectedValue++;
}
// Ensure that the correct number of elements were iterated over
EXPECT_EQ( numElements, expectedValue );
}

View File

@ -0,0 +1,89 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <valijson/adapters/property_tree_adapter.hpp>
class TestPropertyTreeAdapter : public testing::Test
{
};
TEST_F(TestPropertyTreeAdapter, BasicArrayIteration)
{
const unsigned int numElements = 10;
// Create a boost property that is equivalent to a JSON array containing a
// list of numbers.
boost::property_tree::ptree document;
for (unsigned int i = 0; i < numElements; i++) {
document.push_back(std::make_pair(std::string(),
boost::property_tree::ptree(boost::lexical_cast<std::string>(i))));
}
// Ensure that wrapping the document preserves the array and does not allow
// it to be cast to other types
valijson::adapters::PropertyTreeAdapter adapter(document);
ASSERT_NO_THROW( adapter.getArray() );
ASSERT_ANY_THROW( adapter.getBool() );
ASSERT_ANY_THROW( adapter.getDouble() );
ASSERT_ANY_THROW( adapter.getObject() );
ASSERT_ANY_THROW( adapter.getString() );
// Ensure that the array contains the expected number of elements
EXPECT_EQ( numElements, adapter.getArray().size() );
// Ensure that the elements are returned in the order they were inserted
unsigned int expectedValue = 0;
BOOST_FOREACH( const valijson::adapters::PropertyTreeAdapter value, adapter.getArray() ) {
ASSERT_TRUE( value.isString() );
ASSERT_FALSE( value.isNumber() );
ASSERT_TRUE( value.maybeDouble() );
EXPECT_EQ( double(expectedValue), value.asDouble() );
expectedValue++;
}
// Ensure that the correct number of elements were iterated over
EXPECT_EQ(numElements, expectedValue);
}
TEST_F(TestPropertyTreeAdapter, BasicObjectIteration)
{
const unsigned int numElements = 10;
// Create a rapidjson document that consists of an object that maps numeric
// strings their corresponding numeric values
boost::property_tree::ptree document;
for (unsigned int i = 0; i < numElements; i++) {
std::string name(boost::lexical_cast<std::string>(i));
document.push_back(std::make_pair(name, boost::property_tree::ptree(
boost::lexical_cast<std::string>(double(i)))));
}
// Ensure that wrapping the document preserves the object and does not
// allow it to be cast to other types
valijson::adapters::PropertyTreeAdapter adapter(document);
ASSERT_NO_THROW( adapter.getObject() );
ASSERT_ANY_THROW( adapter.getArray() );
ASSERT_ANY_THROW( adapter.getBool() );
ASSERT_ANY_THROW( adapter.getDouble() );
ASSERT_ANY_THROW( adapter.getString() );
// Ensure that the object contains the expected number of members
EXPECT_EQ( numElements, adapter.getObject().size() );
// Ensure that the members are returned in the order they were inserted
unsigned int expectedValue = 0;
BOOST_FOREACH( const valijson::adapters::PropertyTreeAdapter::ObjectMember member, adapter.getObject() ) {
ASSERT_TRUE( member.second.isString() );
ASSERT_FALSE( member.second.isNumber() );
ASSERT_TRUE( member.second.maybeDouble() );
EXPECT_EQ( boost::lexical_cast<std::string>(expectedValue), member.first );
EXPECT_EQ( double(expectedValue), member.second.asDouble() );
expectedValue++;
}
// Ensure that the correct number of elements were iterated over
EXPECT_EQ( numElements, expectedValue );
}

View File

@ -0,0 +1,88 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <valijson/adapters/rapidjson_adapter.hpp>
class TestRapidJsonAdapter : public testing::Test
{
};
TEST_F(TestRapidJsonAdapter, BasicArrayIteration)
{
const unsigned int numElements = 10;
// Create a rapidjson document that consists of an array of numbers
rapidjson::Document document;
document.SetArray();
for (unsigned int i = 0; i < numElements; i++) {
rapidjson::Value value;
value.SetDouble(i);
document.PushBack(value, document.GetAllocator());
}
// Ensure that wrapping the document preserves the array and does not allow
// it to be cast to other types
valijson::adapters::RapidJsonAdapter adapter(document);
ASSERT_NO_THROW( adapter.getArray() );
ASSERT_ANY_THROW( adapter.getBool() );
ASSERT_ANY_THROW( adapter.getDouble() );
ASSERT_ANY_THROW( adapter.getObject() );
ASSERT_ANY_THROW( adapter.getString() );
// Ensure that the array contains the expected number of elements
EXPECT_EQ( numElements, adapter.getArray().size() );
// Ensure that the elements are returned in the order they were inserted
unsigned int expectedValue = 0;
BOOST_FOREACH( const valijson::adapters::RapidJsonAdapter value, adapter.getArray() ) {
ASSERT_TRUE( value.isNumber() );
EXPECT_EQ( double(expectedValue), value.getDouble() );
expectedValue++;
}
// Ensure that the correct number of elements were iterated over
EXPECT_EQ(numElements, expectedValue);
}
TEST_F(TestRapidJsonAdapter, BasicObjectIteration)
{
const unsigned int numElements = 10;
// Create a rapidjson document that consists of an object that maps numeric
// strings their corresponding numeric values
rapidjson::Document document;
document.SetObject();
for (unsigned int i = 0; i < numElements; i++) {
rapidjson::Value name, value;
name.SetString(boost::lexical_cast<std::string>(i).c_str(), document.GetAllocator());
value.SetDouble(i);
document.AddMember(name, value, document.GetAllocator());
}
// Ensure that wrapping the document preserves the object and does not
// allow it to be cast to other types
valijson::adapters::RapidJsonAdapter adapter(document);
ASSERT_NO_THROW( adapter.getObject() );
ASSERT_ANY_THROW( adapter.getArray() );
ASSERT_ANY_THROW( adapter.getBool() );
ASSERT_ANY_THROW( adapter.getDouble() );
ASSERT_ANY_THROW( adapter.getString() );
// Ensure that the object contains the expected number of members
EXPECT_EQ( numElements, adapter.getObject().size() );
// Ensure that the members are returned in the order they were inserted
unsigned int expectedValue = 0;
BOOST_FOREACH( const valijson::adapters::RapidJsonAdapter::ObjectMember member, adapter.getObject() ) {
ASSERT_TRUE( member.second.isNumber() );
EXPECT_EQ( boost::lexical_cast<std::string>(expectedValue), member.first );
EXPECT_EQ( double(expectedValue), member.second.getDouble() );
expectedValue++;
}
// Ensure that the correct number of elements were iterated over
EXPECT_EQ( numElements, expectedValue );
}

View File

@ -0,0 +1,19 @@
#include <gtest/gtest.h>
#include <valijson/schema.hpp>
using valijson::Schema;
class TestUriResolution : public ::testing::Test
{
};
TEST_F(TestUriResolution, TestDefaultScopeAndUri)
{
Schema schema;
EXPECT_FALSE( schema.hasId() );
EXPECT_ANY_THROW( schema.getId() );
EXPECT_EQ( "", schema.getUri() );
EXPECT_EQ( "", schema.getScope() );
}

View File

@ -0,0 +1,89 @@
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <valijson/adapters/rapidjson_adapter.hpp>
#include <valijson/utils/rapidjson_utils.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validation_results.hpp>
#include <valijson/validator.hpp>
#define TEST_DATA_DIR "../tests/data"
using std::string;
using valijson::adapters::AdapterTraits;
using valijson::adapters::RapidJsonAdapter;
using valijson::utils::loadDocument;
using valijson::Schema;
using valijson::SchemaParser;
using valijson::Validator;
using valijson::ValidationResults;
class TestValidationErrors : public ::testing::Test
{
};
TEST_F(TestValidationErrors, AllOfConstraintFailure)
{
// Load schema document
rapidjson::Document schemaDocument;
ASSERT_TRUE( loadDocument(TEST_DATA_DIR "/schemas/allof_integers_and_numbers.schema.json", schemaDocument) );
RapidJsonAdapter schemaAdapter(schemaDocument);
// Parse schema document
Schema schema;
SchemaParser schemaParser;
ASSERT_NO_THROW( schemaParser.populateSchema(schemaAdapter, schema) );
// Load test document
rapidjson::Document testDocument;
ASSERT_TRUE( loadDocument(TEST_DATA_DIR "/documents/array_doubles_1_2_3.json", testDocument) );
RapidJsonAdapter testAdapter(testDocument);
Validator validator(schema);
ValidationResults results;
EXPECT_FALSE( validator.validate(testAdapter, &results) );
ValidationResults::Error error;
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "[0]", error.context );
EXPECT_EQ( "Value type not permitted by 'type' constraint.", error.description );
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "", error.context );
EXPECT_EQ( "Failed to validate item #0 in array.", error.description );
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "[1]", error.context );
EXPECT_EQ( "Value type not permitted by 'type' constraint.", error.description );
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "", error.context );
EXPECT_EQ( "Failed to validate item #1 in array.", error.description );
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "[2]", error.context );
EXPECT_EQ( "Value type not permitted by 'type' constraint.", error.description );
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "", error.context );
EXPECT_EQ( "Failed to validate item #2 in array.", error.description );
EXPECT_TRUE( results.popError(error) );
EXPECT_EQ( "", error.context );
EXPECT_EQ( "Failed to validate against child schema at index #0 of allOf constraint.", error.description );
EXPECT_FALSE( results.popError(error) );
while (results.popError(error)) {
std::cerr << error.context << std::endl;
std::cerr << error.description << std::endl;
}
}

325
tests/test_validator.cpp Normal file
View File

@ -0,0 +1,325 @@
#include <iostream>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <valijson/adapters/jsoncpp_adapter.hpp>
#include <valijson/adapters/rapidjson_adapter.hpp>
#include <valijson/utils/jsoncpp_utils.hpp>
#include <valijson/utils/rapidjson_utils.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validation_results.hpp>
#include <valijson/validator.hpp>
#define TEST_SUITE_DIR "../thirdparty/JSON-Schema-Test-Suite/tests/"
using valijson::adapters::AdapterTraits;
using valijson::adapters::RapidJsonAdapter;
using valijson::Schema;
using valijson::SchemaParser;
using valijson::Validator;
class TestValidator : public ::testing::TestWithParam<const char *>
{
protected:
template<typename AdapterType>
static void processTestFile(const std::string &testFile,
const SchemaParser::Version version)
{
std::string currentTestCase;
std::string currentTest;
try {
// Load test document
typename AdapterTraits<AdapterType>::DocumentType document;
ASSERT_TRUE( valijson::utils::loadDocument(testFile, document) );
AdapterType testCases(document);
ASSERT_TRUE( testCases.isArray() );
// Process each test case in the file
BOOST_FOREACH( const AdapterType testCase, testCases.getArray() ) {
currentTestCase.clear();
currentTest.clear();
// Ensure that testCase is an object
ASSERT_TRUE( testCase.isObject() );
const typename AdapterType::Object object = testCase.getObject();
// Get test case description
typename AdapterType::Object::const_iterator itr = object.find("description");
ASSERT_NE( object.end(), itr );
currentTestCase = itr->second.getString();
// Ensure that 'schema' property is present
itr = object.find("schema");
ASSERT_NE( object.end(), itr );
// Parse schema
Schema schema;
SchemaParser parser(version);
parser.populateSchema(itr->second, schema);
// For each test in the 'tests' array
itr = object.find("tests");
ASSERT_NE( object.end(), itr );
ASSERT_TRUE( itr->second.isArray() );
BOOST_FOREACH( const AdapterType test, itr->second.getArray() ) {
const bool strict = itr->second.hasStrictTypes();
ASSERT_TRUE( test.isObject() );
const typename AdapterType::Object testObject = test.getObject();
typename AdapterType::Object::const_iterator testItr = testObject.find("valid");
ASSERT_NE( testObject.end(), testItr );
ASSERT_TRUE( testItr->second.maybeBool() );
const bool shouldValidate = testItr->second.getBool();
testItr = testObject.find("description");
ASSERT_NE( testObject.end(), testItr );
currentTest = testItr->second.getString();
testItr = testObject.find("data");
ASSERT_NE( testObject.end(), testItr );
Validator validator(schema);
validator.setStrict(strict);
EXPECT_EQ( shouldValidate, validator.validate(testItr->second, NULL) )
<< "Failed while testing validate() function in '"
<< currentTest << "' of test case '"
<< currentTestCase << "' with adapter '"
<< AdapterTraits<AdapterType>::adapterName() << "'";
}
}
} catch (const std::exception &e) {
FAIL() << "Exception thrown with message '" << e.what()
<< "' in '" << currentTest << "' of test case '"
<< currentTestCase << "' with adapter '"
<< AdapterTraits<AdapterType>::adapterName() << "'";
}
}
void processTestFile(const std::string &testFile,
const SchemaParser::Version version)
{
processTestFile<valijson::adapters::JsonCppAdapter>(testFile, version);
processTestFile<valijson::adapters::RapidJsonAdapter>(testFile, version);
}
void processDraft3TestFile(const std::string &testFile)
{
return processTestFile(testFile, SchemaParser::kDraft3);
}
void processDraft4TestFile(const std::string &testFile)
{
return processTestFile(testFile, SchemaParser::kDraft4);
}
};
TEST_F(TestValidator, Draft3_AdditionalItems)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/additionalItems.json");
}
TEST_F(TestValidator, Draft3_AdditionalProperties)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/additionalProperties.json");
}
TEST_F(TestValidator, Draft3_Dependencies)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/dependencies.json");
}
TEST_F(TestValidator, Draft3_Enum)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/enum.json");
}
TEST_F(TestValidator, Draft3_Items)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/items.json");
}
TEST_F(TestValidator, Draft3_Maximum)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/maximum.json");
}
TEST_F(TestValidator, Draft3_MaxItems)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/maxItems.json");
}
TEST_F(TestValidator, Draft3_MaxLength)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/maxLength.json");
}
TEST_F(TestValidator, Draft3_Minimum)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/minimum.json");
}
TEST_F(TestValidator, Draft3_MinItems)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/minItems.json");
}
TEST_F(TestValidator, Draft3_MinLength)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/minLength.json");
}
TEST_F(TestValidator, Draft3_Pattern)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/pattern.json");
}
TEST_F(TestValidator, Draft3_PatternProperties)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/patternProperties.json");
}
TEST_F(TestValidator, Draft3_Properties)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/properties.json");
}
TEST_F(TestValidator, Draft3_Required)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/required.json");
}
TEST_F(TestValidator, Draft3_Type)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/type.json");
}
TEST_F(TestValidator, Draft3_UniqueItems)
{
processDraft3TestFile(TEST_SUITE_DIR "draft3/uniqueItems.json");
}
TEST_F(TestValidator, Draft4_AdditionalItems)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/additionalItems.json");
}
TEST_F(TestValidator, Draft4_AdditionalProperties)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/additionalProperties.json");
}
TEST_F(TestValidator, Draft4_AllOf)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/allOf.json");
}
TEST_F(TestValidator, Draft4_AnyOf)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/anyOf.json");
}
TEST_F(TestValidator, Draft4_Dependencies)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/dependencies.json");
}
TEST_F(TestValidator, Draft4_Enum)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/enum.json");
}
TEST_F(TestValidator, Draft4_Items)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/items.json");
}
TEST_F(TestValidator, Draft4_Maximum)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/maximum.json");
}
TEST_F(TestValidator, Draft4_MaxItems)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/maxItems.json");
}
TEST_F(TestValidator, Draft4_MaxLength)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/maxLength.json");
}
TEST_F(TestValidator, Draft4_MaxProperties)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/maxProperties.json");
}
TEST_F(TestValidator, Draft4_Minimum)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/minimum.json");
}
TEST_F(TestValidator, Draft4_MinItems)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/minItems.json");
}
TEST_F(TestValidator, Draft4_MinLength)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/minLength.json");
}
TEST_F(TestValidator, Draft4_MinProperties)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/minProperties.json");
}
TEST_F(TestValidator, Draft4_Not)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/not.json");
}
TEST_F(TestValidator, Draft4_OneOf)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/oneOf.json");
}
TEST_F(TestValidator, Draft4_Pattern)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/pattern.json");
}
TEST_F(TestValidator, Draft4_PatternProperties)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/patternProperties.json");
}
TEST_F(TestValidator, Draft4_Properties)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/properties.json");
}
TEST_F(TestValidator, Draft4_Required)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/required.json");
}
TEST_F(TestValidator, Draft4_Type)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/type.json");
}
TEST_F(TestValidator, Draft4_UniqueItems)
{
processDraft4TestFile(TEST_SUITE_DIR "draft4/uniqueItems.json");
}

19
thirdparty/JSON-Schema-Test-Suite/LICENSE vendored Executable file
View File

@ -0,0 +1,19 @@
Copyright (c) 2012 Julian Berman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

80
thirdparty/JSON-Schema-Test-Suite/README.md vendored Executable file
View File

@ -0,0 +1,80 @@
JSON Schema Test Suite
======================
This repository contains a set of JSON objects that implementors of JSON Schema
validation libraries can use to test their validators.
It is meant to be language agnostic and should require only a JSON parser.
The conversion of the JSON objects into tests within your test framework of
choice is still the job of the validator implementor.
Structure of a Test
-------------------
If you're going to use this suite, you need to know how tests are laid out. The
tests are contained in the `tests` directory at the root of this repository.
Inside that directory is a subdirectory for each draft or version of the
schema. We'll use `draft3` as an example.
If you look inside the draft directory, there are a number of `.json` files,
which logically group a set of test cases together. Often the grouping is by
property under test, but not always, especially within optional test files
(discussed below).
Inside each `.json` file is a single array containing objects. It's easiest to
illustrate the structure of these with an example:
```json
{
"description": "the description of the test case",
"schema": {"the schema that should" : "be validated against"},
"tests": [
{
"description": "a specific test of a valid instance",
"data": "the instance",
"valid": true
},
{
"description": "another specific test this time, invalid",
"data": 15,
"valid": false
}
]
}
```
So a description, a schema, and some tests, where tests is an array containing
one or more objects with descriptions, data, and a boolean indicating whether
they should be valid or invalid.
Coverage
--------
Draft 3 and 4 should have full coverage. If you see anything missing or think
there is a useful test missing, please send a pull request or open an issue.
Who Uses the Test Suite
-----------------------
This suite is being used by:
* [json-schema-validator (Java)](https://github.com/fge/json-schema-validator)
* [jsonschema (python)](https://github.com/Julian/jsonschema)
* [aeson-schema (haskell)](https://github.com/timjb/aeson-schema)
* [direct-schema (javascript)](https://github.com/IreneKnapp/direct-schema)
* [jsonschema (javascript)](https://github.com/tdegrunt/jsonschema)
* [JaySchema (javascript)](https://github.com/natesilva/jayschema)
* [z-schema (javascript)](https://github.com/zaggino/z-schema)
* [jesse (Erlang)](https://github.com/klarna/jesse)
* [json-schema (PHP)](https://github.com/justinrainbow/json-schema)
* [gojsonschema (Go)](https://github.com/sigu-399/gojsonschema)
If you use it as well, please fork and send a pull request adding yourself to
the list :).
Contributing
------------
If you see something missing or incorrect, a pull request is most welcome!

View File

@ -0,0 +1,82 @@
[
{
"description": "additionalItems as schema",
"schema": {
"items": [],
"additionalItems": {"type": "integer"}
},
"tests": [
{
"description": "additional items match schema",
"data": [ 1, 2, 3, 4 ],
"valid": true
},
{
"description": "additional items do not match schema",
"data": [ 1, 2, 3, "foo" ],
"valid": false
}
]
},
{
"description": "items is schema, no additionalItems",
"schema": {
"items": {},
"additionalItems": false
},
"tests": [
{
"description": "all items match schema",
"data": [ 1, 2, 3, 4, 5 ],
"valid": true
}
]
},
{
"description": "array of items with no additionalItems",
"schema": {
"items": [{}, {}, {}],
"additionalItems": false
},
"tests": [
{
"description": "no additional items present",
"data": [ 1, 2, 3 ],
"valid": true
},
{
"description": "additional items are not permitted",
"data": [ 1, 2, 3, 4 ],
"valid": false
}
]
},
{
"description": "additionalItems as false without items",
"schema": {"additionalItems": false},
"tests": [
{
"description":
"items defaults to empty schema so everything is valid",
"data": [ 1, 2, 3, 4, 5 ],
"valid": true
},
{
"description": "ignores non-arrays",
"data": {"foo" : "bar"},
"valid": true
}
]
},
{
"description": "additionalItems are allowed by default",
"schema": {"items": []},
"tests": [
{
"description": "only the first items are validated",
"data": [1, "foo", false],
"valid": true
}
]
}
]

View File

@ -0,0 +1,63 @@
[
{
"description":
"additionalProperties being false does not allow other properties",
"schema": {
"properties": {"foo": {}, "bar": {}},
"additionalProperties": false
},
"tests": [
{
"description": "no additional properties is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "an additional property is invalid",
"data": {"foo" : 1, "bar" : 2, "quux" : "boom"},
"valid": false
},
{
"description": "ignores non-objects",
"data": [1, 2, 3],
"valid": true
}
]
},
{
"description":
"additionalProperties allows a schema which should validate",
"schema": {
"properties": {"foo": {}, "bar": {}},
"additionalProperties": {"type": "boolean"}
},
"tests": [
{
"description": "no additional properties is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "an additional valid property is valid",
"data": {"foo" : 1, "bar" : 2, "quux" : true},
"valid": true
},
{
"description": "an additional invalid property is invalid",
"data": {"foo" : 1, "bar" : 2, "quux" : 12},
"valid": false
}
]
},
{
"description": "additionalProperties are allowed by default",
"schema": {"properties": {"foo": {}, "bar": {}}},
"tests": [
{
"description": "additional properties are allowed",
"data": {"foo": 1, "bar": 2, "quux": true},
"valid": true
}
]
}
]

View File

@ -0,0 +1,108 @@
[
{
"description": "dependencies",
"schema": {
"dependencies": {"bar": "foo"}
},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependant",
"data": {"foo": 1},
"valid": true
},
{
"description": "with dependency",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "missing dependency",
"data": {"bar": 2},
"valid": false
},
{
"description": "ignores non-objects",
"data": "foo",
"valid": true
}
]
},
{
"description": "multiple dependencies",
"schema": {
"dependencies": {"quux": ["foo", "bar"]}
},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependants",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "with dependencies",
"data": {"foo": 1, "bar": 2, "quux": 3},
"valid": true
},
{
"description": "missing dependency",
"data": {"foo": 1, "quux": 2},
"valid": false
},
{
"description": "missing other dependency",
"data": {"bar": 1, "quux": 2},
"valid": false
},
{
"description": "missing both dependencies",
"data": {"quux": 1},
"valid": false
}
]
},
{
"description": "multiple dependencies subschema",
"schema": {
"dependencies": {
"bar": {
"properties": {
"foo": {"type": "integer"},
"bar": {"type": "integer"}
}
}
}
},
"tests": [
{
"description": "valid",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "wrong type",
"data": {"foo": "quux", "bar": 2},
"valid": false
},
{
"description": "wrong type other",
"data": {"foo": 2, "bar": "quux"},
"valid": false
},
{
"description": "wrong type both",
"data": {"foo": "quux", "bar": "quux"},
"valid": false
}
]
}
]

View File

@ -0,0 +1,80 @@
[
{
"description": "disallow",
"schema": {
"disallow": "integer"
},
"tests": [
{
"description": "allowed",
"data": "foo",
"valid": true
},
{
"description": "disallowed",
"data": 1,
"valid": false
}
]
},
{
"description": "multiple disallow",
"schema": {
"disallow": ["integer", "boolean"]
},
"tests": [
{
"description": "valid",
"data": "foo",
"valid": true
},
{
"description": "mismatch",
"data": 1,
"valid": false
},
{
"description": "other mismatch",
"data": true,
"valid": false
}
]
},
{
"description": "multiple disallow subschema",
"schema": {
"disallow":
["string",
{
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}]
},
"tests": [
{
"description": "match",
"data": 1,
"valid": true
},
{
"description": "other match",
"data": {"foo": 1},
"valid": true
},
{
"description": "mismatch",
"data": "foo",
"valid": false
},
{
"description": "other mismatch",
"data": {"foo": "bar"},
"valid": false
}
]
}
]

View File

@ -0,0 +1,60 @@
[
{
"description": "by int",
"schema": {"divisibleBy": 2},
"tests": [
{
"description": "int by int",
"data": 10,
"valid": true
},
{
"description": "int by int fail",
"data": 7,
"valid": false
},
{
"description": "ignores non-numbers",
"data": "foo",
"valid": true
}
]
},
{
"description": "by number",
"schema": {"divisibleBy": 1.5},
"tests": [
{
"description": "zero is divisible by anything (except 0)",
"data": 0,
"valid": true
},
{
"description": "4.5 is divisible by 1.5",
"data": 4.5,
"valid": true
},
{
"description": "35 is not divisible by 1.5",
"data": 35,
"valid": false
}
]
},
{
"description": "by small number",
"schema": {"divisibleBy": 0.0001},
"tests": [
{
"description": "0.0075 is divisible by 0.0001",
"data": 0.0075,
"valid": true
},
{
"description": "0.00751 is not divisible by 0.0001",
"data": 0.00751,
"valid": false
}
]
}
]

View File

@ -0,0 +1,39 @@
[
{
"description": "simple enum validation",
"schema": {"enum": [1, 2, 3]},
"tests": [
{
"description": "one of the enum is valid",
"data": 1,
"valid": true
},
{
"description": "something else is invalid",
"data": 4,
"valid": false
}
]
},
{
"description": "heterogeneous enum validation",
"schema": {"enum": [6, "foo", [], true, {"foo": 12}]},
"tests": [
{
"description": "one of the enum is valid",
"data": [],
"valid": true
},
{
"description": "something else is invalid",
"data": null,
"valid": false
},
{
"description": "objects are deep compared",
"data": {"foo": false},
"valid": false
}
]
}
]

View File

@ -0,0 +1,94 @@
[
{
"description": "extends",
"schema": {
"properties": {"bar": {"type": "integer", "required": true}},
"extends": {
"properties": {
"foo": {"type": "string", "required": true}
}
}
},
"tests": [
{
"description": "extends",
"data": {"foo": "baz", "bar": 2},
"valid": true
},
{
"description": "mismatch extends",
"data": {"foo": "baz"},
"valid": false
},
{
"description": "mismatch extended",
"data": {"bar": 2},
"valid": false
},
{
"description": "wrong type",
"data": {"foo": "baz", "bar": "quux"},
"valid": false
}
]
},
{
"description": "multiple extends",
"schema": {
"properties": {"bar": {"type": "integer", "required": true}},
"extends" : [
{
"properties": {
"foo": {"type": "string", "required": true}
}
},
{
"properties": {
"baz": {"type": "null", "required": true}
}
}
]
},
"tests": [
{
"description": "valid",
"data": {"foo": "quux", "bar": 2, "baz": null},
"valid": true
},
{
"description": "mismatch first extends",
"data": {"bar": 2, "baz": null},
"valid": false
},
{
"description": "mismatch second extends",
"data": {"foo": "quux", "bar": 2},
"valid": false
},
{
"description": "mismatch both",
"data": {"bar": 2},
"valid": false
}
]
},
{
"description": "extends simple types",
"schema": {
"minimum": 20,
"extends": {"maximum": 30}
},
"tests": [
{
"description": "valid",
"data": 25,
"valid": true
},
{
"description": "mismatch extends",
"data": 35,
"valid": false
}
]
}
]

View File

@ -0,0 +1,46 @@
[
{
"description": "a schema given for items",
"schema": {
"items": {"type": "integer"}
},
"tests": [
{
"description": "valid items",
"data": [ 1, 2, 3 ],
"valid": true
},
{
"description": "wrong type of items",
"data": [1, "x"],
"valid": false
},
{
"description": "ignores non-arrays",
"data": {"foo" : "bar"},
"valid": true
}
]
},
{
"description": "an array of schemas for items",
"schema": {
"items": [
{"type": "integer"},
{"type": "string"}
]
},
"tests": [
{
"description": "correct types",
"data": [ 1, "foo" ],
"valid": true
},
{
"description": "wrong types",
"data": [ "foo", 1 ],
"valid": false
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "maxItems validation",
"schema": {"maxItems": 2},
"tests": [
{
"description": "shorter is valid",
"data": [1],
"valid": true
},
{
"description": "exact length is valid",
"data": [1, 2],
"valid": true
},
{
"description": "too long is invalid",
"data": [1, 2, 3],
"valid": false
},
{
"description": "ignores non-arrays",
"data": "foobar",
"valid": true
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "maxLength validation",
"schema": {"maxLength": 2},
"tests": [
{
"description": "shorter is valid",
"data": "f",
"valid": true
},
{
"description": "exact length is valid",
"data": "fo",
"valid": true
},
{
"description": "too long is invalid",
"data": "foo",
"valid": false
},
{
"description": "ignores non-strings",
"data": 10,
"valid": true
}
]
}
]

View File

@ -0,0 +1,42 @@
[
{
"description": "maximum validation",
"schema": {"maximum": 3.0},
"tests": [
{
"description": "below the maximum is valid",
"data": 2.6,
"valid": true
},
{
"description": "above the maximum is invalid",
"data": 3.5,
"valid": false
},
{
"description": "ignores non-numbers",
"data": "x",
"valid": true
}
]
},
{
"description": "exclusiveMaximum validation",
"schema": {
"maximum": 3.0,
"exclusiveMaximum": true
},
"tests": [
{
"description": "below the maximum is still valid",
"data": 2.2,
"valid": true
},
{
"description": "boundary point is invalid",
"data": 3.0,
"valid": false
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "minItems validation",
"schema": {"minItems": 1},
"tests": [
{
"description": "longer is valid",
"data": [1, 2],
"valid": true
},
{
"description": "exact length is valid",
"data": [1],
"valid": true
},
{
"description": "too short is invalid",
"data": [],
"valid": false
},
{
"description": "ignores non-arrays",
"data": "",
"valid": true
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "minLength validation",
"schema": {"minLength": 2},
"tests": [
{
"description": "longer is valid",
"data": "foo",
"valid": true
},
{
"description": "exact length is valid",
"data": "fo",
"valid": true
},
{
"description": "too short is invalid",
"data": "f",
"valid": false
},
{
"description": "ignores non-strings",
"data": 1,
"valid": true
}
]
}
]

View File

@ -0,0 +1,42 @@
[
{
"description": "minimum validation",
"schema": {"minimum": 1.1},
"tests": [
{
"description": "above the minimum is valid",
"data": 2.6,
"valid": true
},
{
"description": "below the minimum is invalid",
"data": 0.6,
"valid": false
},
{
"description": "ignores non-numbers",
"data": "x",
"valid": true
}
]
},
{
"description": "exclusiveMinimum validation",
"schema": {
"minimum": 1.1,
"exclusiveMinimum": true
},
"tests": [
{
"description": "above the minimum is still valid",
"data": 1.2,
"valid": true
},
{
"description": "boundary point is invalid",
"data": 1.1,
"valid": false
}
]
}
]

View File

@ -0,0 +1,49 @@
[
{
"description": "integer",
"schema": {"type": "integer"},
"tests": [
{
"description": "a bignum is an integer",
"data": 12345678910111213141516171819202122232425262728293031,
"valid": true
}
]
},
{
"description": "number",
"schema": {"type": "number"},
"tests": [
{
"description": "a bignum is a number",
"data": 98249283749234923498293171823948729348710298301928331,
"valid": true
}
]
},
{
"description": "string",
"schema": {"type": "string"},
"tests": [
{
"description": "a bignum is not a string",
"data": 98249283749234923498293171823948729348710298301928331,
"valid": false
}
]
},
{
"description": "float comparison with high precision",
"schema": {
"maximum": 972783798187987123879878123.18878137,
"exclusiveMaximum": true
},
"tests": [
{
"description": "comparison works for high numbers",
"data": 972783798187987123879878123.188781371,
"valid": false
}
]
}
]

View File

@ -0,0 +1,212 @@
[
{
"description": "validation of regular expressions",
"schema": {"format": "regex"},
"tests": [
{
"description": "a valid regular expression",
"data": "([abc])+\\s+$",
"valid": true
},
{
"description": "a regular expression with unclosed parens is invalid",
"data": "^(abc]",
"valid": false
}
]
},
{
"description": "validation of date-time strings",
"schema": {"format": "date-time"},
"tests": [
{
"description": "a valid date-time string",
"data": "1963-06-19T08:30:06.283185Z",
"valid": true
},
{
"description": "an invalid date-time string",
"data": "06/19/1963 08:30:06 PST",
"valid": false
},
{
"description": "only RFC3339 not all of ISO 8601 are valid",
"data": "2013-350T01:01:01",
"valid": false
}
]
},
{
"description": "validation of date strings",
"schema": {"format": "date"},
"tests": [
{
"description": "a valid date string",
"data": "1963-06-19",
"valid": true
},
{
"description": "an invalid date string",
"data": "06/19/1963",
"valid": false
}
]
},
{
"description": "validation of time strings",
"schema": {"format": "time"},
"tests": [
{
"description": "a valid time string",
"data": "08:30:06",
"valid": true
},
{
"description": "an invalid time string",
"data": "8:30 AM",
"valid": false
}
]
},
{
"description": "validation of URIs",
"schema": {"format": "uri"},
"tests": [
{
"description": "a valid URI",
"data": "http://foo.bar/?baz=qux#quux",
"valid": true
},
{
"description": "an invalid URI",
"data": "\\\\WINDOWS\\fileshare",
"valid": false
}
]
},
{
"description": "validation of e-mail addresses",
"schema": {"format": "email"},
"tests": [
{
"description": "a valid e-mail address",
"data": "joe.bloggs@example.com",
"valid": true
},
{
"description": "an invalid e-mail address",
"data": "2962",
"valid": false
}
]
},
{
"description": "validation of IP addresses",
"schema": {"format": "ip-address"},
"tests": [
{
"description": "a valid IP address",
"data": "192.168.0.1",
"valid": true
},
{
"description": "an IP address with too many components",
"data": "127.0.0.0.1",
"valid": false
},
{
"description": "an IP address with out-of-range values",
"data": "256.256.256.256",
"valid": false
}
]
},
{
"description": "validation of IPv6 addresses",
"schema": {"format": "ipv6"},
"tests": [
{
"description": "a valid IPv6 address",
"data": "::1",
"valid": true
},
{
"description": "an IPv6 address with out-of-range values",
"data": "12345::",
"valid": false
},
{
"description": "an IPv6 address with too many components",
"data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1",
"valid": false
},
{
"description": "an IPv6 address containing illegal characters",
"data": "::laptop",
"valid": false
}
]
},
{
"description": "validation of host names",
"schema": {"format": "host-name"},
"tests": [
{
"description": "a valid host name",
"data": "www.example.com",
"valid": true
},
{
"description": "a host name starting with an illegal character",
"data": "-a-host-name-that-starts-with--",
"valid": false
},
{
"description": "a host name containing illegal characters",
"data": "not_a_valid_host_name",
"valid": false
},
{
"description": "a host name with a component too long",
"data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component",
"valid": false
}
]
},
{
"description": "validation of CSS colors",
"schema": {"format": "color"},
"tests": [
{
"description": "a valid CSS color name",
"data": "fuchsia",
"valid": true
},
{
"description": "a valid six-digit CSS color code",
"data": "#CC8899",
"valid": true
},
{
"description": "a valid three-digit CSS color code",
"data": "#C89",
"valid": true
},
{
"description": "an invalid CSS color code",
"data": "#00332520",
"valid": false
},
{
"description": "an invalid CSS color name",
"data": "puce",
"valid": false
},
{
"description": "a CSS color name containing invalid characters",
"data": "light_grayish_red-violet",
"valid": false
}
]
}
]

View File

@ -0,0 +1,18 @@
[
{
"description": "ECMA 262 regex dialect recognition",
"schema": { "format": "regex" },
"tests": [
{
"description": "[^] is a valid regex",
"data": "[^]",
"valid": true
},
{
"description": "ECMA 262 has no support for lookbehind",
"data": "(?<=foo)bar",
"valid": false
}
]
}
]

View File

@ -0,0 +1,15 @@
[
{
"description": "some languages do not distinguish between different types of numeric value",
"schema": {
"type": "integer"
},
"tests": [
{
"description": "a float is not an integer even without fractional part",
"data": 1.0,
"valid": false
}
]
}
]

View File

@ -0,0 +1,23 @@
[
{
"description": "pattern validation",
"schema": {"pattern": "^a*$"},
"tests": [
{
"description": "a matching pattern is valid",
"data": "aaa",
"valid": true
},
{
"description": "a non-matching pattern is invalid",
"data": "abc",
"valid": false
},
{
"description": "ignores non-strings",
"data": true,
"valid": true
}
]
}
]

View File

@ -0,0 +1,110 @@
[
{
"description":
"patternProperties validates properties matching a regex",
"schema": {
"patternProperties": {
"f.*o": {"type": "integer"}
}
},
"tests": [
{
"description": "a single valid match is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "multiple valid matches is valid",
"data": {"foo": 1, "foooooo" : 2},
"valid": true
},
{
"description": "a single invalid match is invalid",
"data": {"foo": "bar", "fooooo": 2},
"valid": false
},
{
"description": "multiple invalid matches is invalid",
"data": {"foo": "bar", "foooooo" : "baz"},
"valid": false
},
{
"description": "ignores non-objects",
"data": 12,
"valid": true
}
]
},
{
"description": "multiple simultaneous patternProperties are validated",
"schema": {
"patternProperties": {
"a*": {"type": "integer"},
"aaa*": {"maximum": 20}
}
},
"tests": [
{
"description": "a single valid match is valid",
"data": {"a": 21},
"valid": true
},
{
"description": "a simultaneous match is valid",
"data": {"aaaa": 18},
"valid": true
},
{
"description": "multiple matches is valid",
"data": {"a": 21, "aaaa": 18},
"valid": true
},
{
"description": "an invalid due to one is invalid",
"data": {"a": "bar"},
"valid": false
},
{
"description": "an invalid due to the other is invalid",
"data": {"aaaa": 31},
"valid": false
},
{
"description": "an invalid due to both is invalid",
"data": {"aaa": "foo", "aaaa": 31},
"valid": false
}
]
},
{
"description": "regexes are not anchored by default and are case sensitive",
"schema": {
"patternProperties": {
"[0-9]{2,}": { "type": "boolean" },
"X_": { "type": "string" }
}
},
"tests": [
{
"description": "non recognized members are ignored",
"data": { "answer 1": "42" },
"valid": true
},
{
"description": "recognized members are accounted for",
"data": { "a31b": null },
"valid": false
},
{
"description": "regexes are case sensitive",
"data": { "a_x_3": 3 },
"valid": true
},
{
"description": "regexes are case sensitive, 2",
"data": { "a_X_3": 3 },
"valid": false
}
]
}
]

View File

@ -0,0 +1,92 @@
[
{
"description": "object properties validation",
"schema": {
"properties": {
"foo": {"type": "integer"},
"bar": {"type": "string"}
}
},
"tests": [
{
"description": "both properties present and valid is valid",
"data": {"foo": 1, "bar": "baz"},
"valid": true
},
{
"description": "one property invalid is invalid",
"data": {"foo": 1, "bar": {}},
"valid": false
},
{
"description": "both properties invalid is invalid",
"data": {"foo": [], "bar": {}},
"valid": false
},
{
"description": "doesn't invalidate other properties",
"data": {"quux": []},
"valid": true
},
{
"description": "ignores non-objects",
"data": [],
"valid": true
}
]
},
{
"description":
"properties, patternProperties, additionalProperties interaction",
"schema": {
"properties": {
"foo": {"type": "array", "maxItems": 3},
"bar": {"type": "array"}
},
"patternProperties": {"f.o": {"minItems": 2}},
"additionalProperties": {"type": "integer"}
},
"tests": [
{
"description": "property validates property",
"data": {"foo": [1, 2]},
"valid": true
},
{
"description": "property invalidates property",
"data": {"foo": [1, 2, 3, 4]},
"valid": false
},
{
"description": "patternProperty invalidates property",
"data": {"foo": []},
"valid": false
},
{
"description": "patternProperty validates nonproperty",
"data": {"fxo": [1, 2]},
"valid": true
},
{
"description": "patternProperty invalidates nonproperty",
"data": {"fxo": []},
"valid": false
},
{
"description": "additionalProperty ignores property",
"data": {"bar": []},
"valid": true
},
{
"description": "additionalProperty validates others",
"data": {"quux": 3},
"valid": true
},
{
"description": "additionalProperty invalidates others",
"data": {"quux": "foo"},
"valid": false
}
]
}
]

View File

@ -0,0 +1,144 @@
[
{
"description": "root pointer ref",
"schema": {
"properties": {
"foo": {"$ref": "#"}
},
"additionalProperties": false
},
"tests": [
{
"description": "match",
"data": {"foo": false},
"valid": true
},
{
"description": "recursive match",
"data": {"foo": {"foo": false}},
"valid": true
},
{
"description": "mismatch",
"data": {"bar": false},
"valid": false
},
{
"description": "recursive mismatch",
"data": {"foo": {"bar": false}},
"valid": false
}
]
},
{
"description": "relative pointer ref to object",
"schema": {
"properties": {
"foo": {"type": "integer"},
"bar": {"$ref": "#/properties/foo"}
}
},
"tests": [
{
"description": "match",
"data": {"bar": 3},
"valid": true
},
{
"description": "mismatch",
"data": {"bar": true},
"valid": false
}
]
},
{
"description": "relative pointer ref to array",
"schema": {
"items": [
{"type": "integer"},
{"$ref": "#/items/0"}
]
},
"tests": [
{
"description": "match array",
"data": [1, 2],
"valid": true
},
{
"description": "mismatch array",
"data": [1, "foo"],
"valid": false
}
]
},
{
"description": "escaped pointer ref",
"schema": {
"tilda~field": {"type": "integer"},
"slash/field": {"type": "integer"},
"percent%field": {"type": "integer"},
"properties": {
"tilda": {"$ref": "#/tilda~0field"},
"slash": {"$ref": "#/slash~1field"},
"percent": {"$ref": "#/percent%25field"}
}
},
"tests": [
{
"description": "slash",
"data": {"slash": "aoeu"},
"valid": false
},
{
"description": "tilda",
"data": {"tilda": "aoeu"},
"valid": false
},
{
"description": "percent",
"data": {"percent": "aoeu"},
"valid": false
}
]
},
{
"description": "nested refs",
"schema": {
"definitions": {
"a": {"type": "integer"},
"b": {"$ref": "#/definitions/a"},
"c": {"$ref": "#/definitions/b"}
},
"$ref": "#/definitions/c"
},
"tests": [
{
"description": "nested ref valid",
"data": 5,
"valid": true
},
{
"description": "nested ref invalid",
"data": "a",
"valid": false
}
]
},
{
"description": "remote ref, containing refs itself",
"schema": {"$ref": "http://json-schema.org/draft-03/schema#"},
"tests": [
{
"description": "remote ref valid",
"data": {"items": {"type": "integer"}},
"valid": true
},
{
"description": "remote ref invalid",
"data": {"items": {"type": 1}},
"valid": false
}
]
}
]

View File

@ -0,0 +1,74 @@
[
{
"description": "remote ref",
"schema": {"$ref": "http://localhost:1234/integer.json"},
"tests": [
{
"description": "remote ref valid",
"data": 1,
"valid": true
},
{
"description": "remote ref invalid",
"data": "a",
"valid": false
}
]
},
{
"description": "fragment within remote ref",
"schema": {"$ref": "http://localhost:1234/subSchemas.json#/integer"},
"tests": [
{
"description": "remote fragment valid",
"data": 1,
"valid": true
},
{
"description": "remote fragment invalid",
"data": "a",
"valid": false
}
]
},
{
"description": "ref within remote ref",
"schema": {
"$ref": "http://localhost:1234/subSchemas.json#/refToInteger"
},
"tests": [
{
"description": "ref within ref valid",
"data": 1,
"valid": true
},
{
"description": "ref within ref invalid",
"data": "a",
"valid": false
}
]
},
{
"description": "change resolution scope",
"schema": {
"id": "http://localhost:1234/",
"items": {
"id": "folder/",
"items": {"$ref": "folderInteger.json"}
}
},
"tests": [
{
"description": "changed scope ref valid",
"data": [[1]],
"valid": true
},
{
"description": "changed scope ref invalid",
"data": [["a"]],
"valid": false
}
]
}
]

View File

@ -0,0 +1,53 @@
[
{
"description": "required validation",
"schema": {
"properties": {
"foo": {"required" : true},
"bar": {}
}
},
"tests": [
{
"description": "present required property is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "non-present required property is invalid",
"data": {"bar": 1},
"valid": false
}
]
},
{
"description": "required default validation",
"schema": {
"properties": {
"foo": {}
}
},
"tests": [
{
"description": "not required by default",
"data": {},
"valid": true
}
]
},
{
"description": "required explicitly false validation",
"schema": {
"properties": {
"foo": {"required": false}
}
},
"tests": [
{
"description": "not required if required is false",
"data": {},
"valid": true
}
]
}
]

View File

@ -0,0 +1,474 @@
[
{
"description": "integer type matches integers",
"schema": {"type": "integer"},
"tests": [
{
"description": "an integer is an integer",
"data": 1,
"valid": true
},
{
"description": "a float is not an integer",
"data": 1.1,
"valid": false
},
{
"description": "a string is not an integer",
"data": "foo",
"valid": false
},
{
"description": "an object is not an integer",
"data": {},
"valid": false
},
{
"description": "an array is not an integer",
"data": [],
"valid": false
},
{
"description": "a boolean is not an integer",
"data": true,
"valid": false
},
{
"description": "null is not an integer",
"data": null,
"valid": false
}
]
},
{
"description": "number type matches numbers",
"schema": {"type": "number"},
"tests": [
{
"description": "an integer is a number",
"data": 1,
"valid": true
},
{
"description": "a float is a number",
"data": 1.1,
"valid": true
},
{
"description": "a string is not a number",
"data": "foo",
"valid": false
},
{
"description": "an object is not a number",
"data": {},
"valid": false
},
{
"description": "an array is not a number",
"data": [],
"valid": false
},
{
"description": "a boolean is not a number",
"data": true,
"valid": false
},
{
"description": "null is not a number",
"data": null,
"valid": false
}
]
},
{
"description": "string type matches strings",
"schema": {"type": "string"},
"tests": [
{
"description": "1 is not a string",
"data": 1,
"valid": false
},
{
"description": "a float is not a string",
"data": 1.1,
"valid": false
},
{
"description": "a string is a string",
"data": "foo",
"valid": true
},
{
"description": "an object is not a string",
"data": {},
"valid": false
},
{
"description": "an array is not a string",
"data": [],
"valid": false
},
{
"description": "a boolean is not a string",
"data": true,
"valid": false
},
{
"description": "null is not a string",
"data": null,
"valid": false
}
]
},
{
"description": "object type matches objects",
"schema": {"type": "object"},
"tests": [
{
"description": "an integer is not an object",
"data": 1,
"valid": false
},
{
"description": "a float is not an object",
"data": 1.1,
"valid": false
},
{
"description": "a string is not an object",
"data": "foo",
"valid": false
},
{
"description": "an object is an object",
"data": {},
"valid": true
},
{
"description": "an array is not an object",
"data": [],
"valid": false
},
{
"description": "a boolean is not an object",
"data": true,
"valid": false
},
{
"description": "null is not an object",
"data": null,
"valid": false
}
]
},
{
"description": "array type matches arrays",
"schema": {"type": "array"},
"tests": [
{
"description": "an integer is not an array",
"data": 1,
"valid": false
},
{
"description": "a float is not an array",
"data": 1.1,
"valid": false
},
{
"description": "a string is not an array",
"data": "foo",
"valid": false
},
{
"description": "an object is not an array",
"data": {},
"valid": false
},
{
"description": "an array is not an array",
"data": [],
"valid": true
},
{
"description": "a boolean is not an array",
"data": true,
"valid": false
},
{
"description": "null is not an array",
"data": null,
"valid": false
}
]
},
{
"description": "boolean type matches booleans",
"schema": {"type": "boolean"},
"tests": [
{
"description": "an integer is not a boolean",
"data": 1,
"valid": false
},
{
"description": "a float is not a boolean",
"data": 1.1,
"valid": false
},
{
"description": "a string is not a boolean",
"data": "foo",
"valid": false
},
{
"description": "an object is not a boolean",
"data": {},
"valid": false
},
{
"description": "an array is not a boolean",
"data": [],
"valid": false
},
{
"description": "a boolean is not a boolean",
"data": true,
"valid": true
},
{
"description": "null is not a boolean",
"data": null,
"valid": false
}
]
},
{
"description": "null type matches only the null object",
"schema": {"type": "null"},
"tests": [
{
"description": "an integer is not null",
"data": 1,
"valid": false
},
{
"description": "a float is not null",
"data": 1.1,
"valid": false
},
{
"description": "a string is not null",
"data": "foo",
"valid": false
},
{
"description": "an object is not null",
"data": {},
"valid": false
},
{
"description": "an array is not null",
"data": [],
"valid": false
},
{
"description": "a boolean is not null",
"data": true,
"valid": false
},
{
"description": "null is null",
"data": null,
"valid": true
}
]
},
{
"description": "any type matches any type",
"schema": {"type": "any"},
"tests": [
{
"description": "any type includes integers",
"data": 1,
"valid": true
},
{
"description": "any type includes float",
"data": 1.1,
"valid": true
},
{
"description": "any type includes string",
"data": "foo",
"valid": true
},
{
"description": "any type includes object",
"data": {},
"valid": true
},
{
"description": "any type includes array",
"data": [],
"valid": true
},
{
"description": "any type includes boolean",
"data": true,
"valid": true
},
{
"description": "any type includes null",
"data": null,
"valid": true
}
]
},
{
"description": "multiple types can be specified in an array",
"schema": {"type": ["integer", "string"]},
"tests": [
{
"description": "an integer is valid",
"data": 1,
"valid": true
},
{
"description": "a string is valid",
"data": "foo",
"valid": true
},
{
"description": "a float is invalid",
"data": 1.1,
"valid": false
},
{
"description": "an object is invalid",
"data": {},
"valid": false
},
{
"description": "an array is invalid",
"data": [],
"valid": false
},
{
"description": "a boolean is invalid",
"data": true,
"valid": false
},
{
"description": "null is invalid",
"data": null,
"valid": false
}
]
},
{
"description": "types can include schemas",
"schema": {
"type": [
"array",
{"type": "object"}
]
},
"tests": [
{
"description": "an integer is invalid",
"data": 1,
"valid": false
},
{
"description": "a string is invalid",
"data": "foo",
"valid": false
},
{
"description": "a float is invalid",
"data": 1.1,
"valid": false
},
{
"description": "an object is valid",
"data": {},
"valid": true
},
{
"description": "an array is valid",
"data": [],
"valid": true
},
{
"description": "a boolean is invalid",
"data": true,
"valid": false
},
{
"description": "null is invalid",
"data": null,
"valid": false
}
]
},
{
"description":
"when types includes a schema it should fully validate the schema",
"schema": {
"type": [
"integer",
{
"properties": {
"foo": {"type": "null"}
}
}
]
},
"tests": [
{
"description": "an integer is valid",
"data": 1,
"valid": true
},
{
"description": "an object is valid only if it is fully valid",
"data": {"foo": null},
"valid": true
},
{
"description": "an object is invalid otherwise",
"data": {"foo": "bar"},
"valid": false
}
]
},
{
"description": "types from separate schemas are merged",
"schema": {
"type": [
{"type": ["string"]},
{"type": ["array", "null"]}
]
},
"tests": [
{
"description": "an integer is invalid",
"data": 1,
"valid": false
},
{
"description": "a string is valid",
"data": "foo",
"valid": true
},
{
"description": "an array is valid",
"data": [1, 2, 3],
"valid": true
}
]
}
]

View File

@ -0,0 +1,79 @@
[
{
"description": "uniqueItems validation",
"schema": {"uniqueItems": true},
"tests": [
{
"description": "unique array of integers is valid",
"data": [1, 2],
"valid": true
},
{
"description": "non-unique array of integers is invalid",
"data": [1, 1],
"valid": false
},
{
"description": "numbers are unique if mathematically unequal",
"data": [1.0, 1.00, 1],
"valid": false
},
{
"description": "unique array of objects is valid",
"data": [{"foo": "bar"}, {"foo": "baz"}],
"valid": true
},
{
"description": "non-unique array of objects is invalid",
"data": [{"foo": "bar"}, {"foo": "bar"}],
"valid": false
},
{
"description": "unique array of nested objects is valid",
"data": [
{"foo": {"bar" : {"baz" : true}}},
{"foo": {"bar" : {"baz" : false}}}
],
"valid": true
},
{
"description": "non-unique array of nested objects is invalid",
"data": [
{"foo": {"bar" : {"baz" : true}}},
{"foo": {"bar" : {"baz" : true}}}
],
"valid": false
},
{
"description": "unique array of arrays is valid",
"data": [["foo"], ["bar"]],
"valid": true
},
{
"description": "non-unique array of arrays is invalid",
"data": [["foo"], ["foo"]],
"valid": false
},
{
"description": "1 and true are unique",
"data": [1, true],
"valid": true
},
{
"description": "0 and false are unique",
"data": [0, false],
"valid": true
},
{
"description": "unique heterogeneous types are valid",
"data": [{}, [1], true, null, 1],
"valid": true
},
{
"description": "non-unique heterogeneous types are invalid",
"data": [{}, [1], true, null, {}, 1],
"valid": false
}
]
}
]

View File

@ -0,0 +1,82 @@
[
{
"description": "additionalItems as schema",
"schema": {
"items": [{}],
"additionalItems": {"type": "integer"}
},
"tests": [
{
"description": "additional items match schema",
"data": [ null, 2, 3, 4 ],
"valid": true
},
{
"description": "additional items do not match schema",
"data": [ null, 2, 3, "foo" ],
"valid": false
}
]
},
{
"description": "items is schema, no additionalItems",
"schema": {
"items": {},
"additionalItems": false
},
"tests": [
{
"description": "all items match schema",
"data": [ 1, 2, 3, 4, 5 ],
"valid": true
}
]
},
{
"description": "array of items with no additionalItems",
"schema": {
"items": [{}, {}, {}],
"additionalItems": false
},
"tests": [
{
"description": "no additional items present",
"data": [ 1, 2, 3 ],
"valid": true
},
{
"description": "additional items are not permitted",
"data": [ 1, 2, 3, 4 ],
"valid": false
}
]
},
{
"description": "additionalItems as false without items",
"schema": {"additionalItems": false},
"tests": [
{
"description":
"items defaults to empty schema so everything is valid",
"data": [ 1, 2, 3, 4, 5 ],
"valid": true
},
{
"description": "ignores non-arrays",
"data": {"foo" : "bar"},
"valid": true
}
]
},
{
"description": "additionalItems are allowed by default",
"schema": {"items": [{"type": "integer"}]},
"tests": [
{
"description": "only the first item is validated",
"data": [1, "foo", false],
"valid": true
}
]
}
]

View File

@ -0,0 +1,63 @@
[
{
"description":
"additionalProperties being false does not allow other properties",
"schema": {
"properties": {"foo": {}, "bar": {}},
"additionalProperties": false
},
"tests": [
{
"description": "no additional properties is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "an additional property is invalid",
"data": {"foo" : 1, "bar" : 2, "quux" : "boom"},
"valid": false
},
{
"description": "ignores non-objects",
"data": [1, 2, 3],
"valid": true
}
]
},
{
"description":
"additionalProperties allows a schema which should validate",
"schema": {
"properties": {"foo": {}, "bar": {}},
"additionalProperties": {"type": "boolean"}
},
"tests": [
{
"description": "no additional properties is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "an additional valid property is valid",
"data": {"foo" : 1, "bar" : 2, "quux" : true},
"valid": true
},
{
"description": "an additional invalid property is invalid",
"data": {"foo" : 1, "bar" : 2, "quux" : 12},
"valid": false
}
]
},
{
"description": "additionalProperties are allowed by default",
"schema": {"properties": {"foo": {}, "bar": {}}},
"tests": [
{
"description": "additional properties are allowed",
"data": {"foo": 1, "bar": 2, "quux": true},
"valid": true
}
]
}
]

View File

@ -0,0 +1,112 @@
[
{
"description": "allOf",
"schema": {
"allOf": [
{
"properties": {
"bar": {"type": "integer"}
},
"required": ["bar"]
},
{
"properties": {
"foo": {"type": "string"}
},
"required": ["foo"]
}
]
},
"tests": [
{
"description": "allOf",
"data": {"foo": "baz", "bar": 2},
"valid": true
},
{
"description": "mismatch second",
"data": {"foo": "baz"},
"valid": false
},
{
"description": "mismatch first",
"data": {"bar": 2},
"valid": false
},
{
"description": "wrong type",
"data": {"foo": "baz", "bar": "quux"},
"valid": false
}
]
},
{
"description": "allOf with base schema",
"schema": {
"properties": {"bar": {"type": "integer"}},
"required": ["bar"],
"allOf" : [
{
"properties": {
"foo": {"type": "string"}
},
"required": ["foo"]
},
{
"properties": {
"baz": {"type": "null"}
},
"required": ["baz"]
}
]
},
"tests": [
{
"description": "valid",
"data": {"foo": "quux", "bar": 2, "baz": null},
"valid": true
},
{
"description": "mismatch base schema",
"data": {"foo": "quux", "baz": null},
"valid": false
},
{
"description": "mismatch first allOf",
"data": {"bar": 2, "baz": null},
"valid": false
},
{
"description": "mismatch second allOf",
"data": {"foo": "quux", "bar": 2},
"valid": false
},
{
"description": "mismatch both",
"data": {"bar": 2},
"valid": false
}
]
},
{
"description": "allOf simple types",
"schema": {
"allOf": [
{"maximum": 30},
{"minimum": 20}
]
},
"tests": [
{
"description": "valid",
"data": 25,
"valid": true
},
{
"description": "mismatch one",
"data": 35,
"valid": false
}
]
}
]

View File

@ -0,0 +1,68 @@
[
{
"description": "anyOf",
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"minimum": 2
}
]
},
"tests": [
{
"description": "first anyOf valid",
"data": 1,
"valid": true
},
{
"description": "second anyOf valid",
"data": 2.5,
"valid": true
},
{
"description": "both anyOf valid",
"data": 3,
"valid": true
},
{
"description": "neither anyOf valid",
"data": 1.5,
"valid": false
}
]
},
{
"description": "anyOf with base schema",
"schema": {
"type": "string",
"anyOf" : [
{
"maxLength": 2
},
{
"minLength": 4
}
]
},
"tests": [
{
"description": "mismatch base schema",
"data": 3,
"valid": false
},
{
"description": "one anyOf valid",
"data": "foobar",
"valid": true
},
{
"description": "both anyOf invalid",
"data": "foo",
"valid": false
}
]
}
]

View File

@ -0,0 +1,32 @@
[
{
"description": "valid definition",
"schema": {"$ref": "http://json-schema.org/draft-04/schema#"},
"tests": [
{
"description": "valid definition schema",
"data": {
"definitions": {
"foo": {"type": "integer"}
}
},
"valid": true
}
]
},
{
"description": "invalid definition",
"schema": {"$ref": "http://json-schema.org/draft-04/schema#"},
"tests": [
{
"description": "invalid definition schema",
"data": {
"definitions": {
"foo": {"type": 1}
}
},
"valid": false
}
]
}
]

View File

@ -0,0 +1,113 @@
[
{
"description": "dependencies",
"schema": {
"dependencies": {"bar": ["foo"]}
},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependant",
"data": {"foo": 1},
"valid": true
},
{
"description": "with dependency",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "missing dependency",
"data": {"bar": 2},
"valid": false
},
{
"description": "ignores non-objects",
"data": "foo",
"valid": true
}
]
},
{
"description": "multiple dependencies",
"schema": {
"dependencies": {"quux": ["foo", "bar"]}
},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependants",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "with dependencies",
"data": {"foo": 1, "bar": 2, "quux": 3},
"valid": true
},
{
"description": "missing dependency",
"data": {"foo": 1, "quux": 2},
"valid": false
},
{
"description": "missing other dependency",
"data": {"bar": 1, "quux": 2},
"valid": false
},
{
"description": "missing both dependencies",
"data": {"quux": 1},
"valid": false
}
]
},
{
"description": "multiple dependencies subschema",
"schema": {
"dependencies": {
"bar": {
"properties": {
"foo": {"type": "integer"},
"bar": {"type": "integer"}
}
}
}
},
"tests": [
{
"description": "valid",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "no dependency",
"data": {"foo": "quux"},
"valid": true
},
{
"description": "wrong type",
"data": {"foo": "quux", "bar": 2},
"valid": false
},
{
"description": "wrong type other",
"data": {"foo": 2, "bar": "quux"},
"valid": false
},
{
"description": "wrong type both",
"data": {"foo": "quux", "bar": "quux"},
"valid": false
}
]
}
]

View File

@ -0,0 +1,39 @@
[
{
"description": "simple enum validation",
"schema": {"enum": [1, 2, 3]},
"tests": [
{
"description": "one of the enum is valid",
"data": 1,
"valid": true
},
{
"description": "something else is invalid",
"data": 4,
"valid": false
}
]
},
{
"description": "heterogeneous enum validation",
"schema": {"enum": [6, "foo", [], true, {"foo": 12}]},
"tests": [
{
"description": "one of the enum is valid",
"data": [],
"valid": true
},
{
"description": "something else is invalid",
"data": null,
"valid": false
},
{
"description": "objects are deep compared",
"data": {"foo": false},
"valid": false
}
]
}
]

View File

@ -0,0 +1,46 @@
[
{
"description": "a schema given for items",
"schema": {
"items": {"type": "integer"}
},
"tests": [
{
"description": "valid items",
"data": [ 1, 2, 3 ],
"valid": true
},
{
"description": "wrong type of items",
"data": [1, "x"],
"valid": false
},
{
"description": "ignores non-arrays",
"data": {"foo" : "bar"},
"valid": true
}
]
},
{
"description": "an array of schemas for items",
"schema": {
"items": [
{"type": "integer"},
{"type": "string"}
]
},
"tests": [
{
"description": "correct types",
"data": [ 1, "foo" ],
"valid": true
},
{
"description": "wrong types",
"data": [ "foo", 1 ],
"valid": false
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "maxItems validation",
"schema": {"maxItems": 2},
"tests": [
{
"description": "shorter is valid",
"data": [1],
"valid": true
},
{
"description": "exact length is valid",
"data": [1, 2],
"valid": true
},
{
"description": "too long is invalid",
"data": [1, 2, 3],
"valid": false
},
{
"description": "ignores non-arrays",
"data": "foobar",
"valid": true
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "maxLength validation",
"schema": {"maxLength": 2},
"tests": [
{
"description": "shorter is valid",
"data": "f",
"valid": true
},
{
"description": "exact length is valid",
"data": "fo",
"valid": true
},
{
"description": "too long is invalid",
"data": "foo",
"valid": false
},
{
"description": "ignores non-strings",
"data": 10,
"valid": true
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "maxProperties validation",
"schema": {"maxProperties": 2},
"tests": [
{
"description": "shorter is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "exact length is valid",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "too long is invalid",
"data": {"foo": 1, "bar": 2, "baz": 3},
"valid": false
},
{
"description": "ignores non-objects",
"data": "foobar",
"valid": true
}
]
}
]

View File

@ -0,0 +1,42 @@
[
{
"description": "maximum validation",
"schema": {"maximum": 3.0},
"tests": [
{
"description": "below the maximum is valid",
"data": 2.6,
"valid": true
},
{
"description": "above the maximum is invalid",
"data": 3.5,
"valid": false
},
{
"description": "ignores non-numbers",
"data": "x",
"valid": true
}
]
},
{
"description": "exclusiveMaximum validation",
"schema": {
"maximum": 3.0,
"exclusiveMaximum": true
},
"tests": [
{
"description": "below the maximum is still valid",
"data": 2.2,
"valid": true
},
{
"description": "boundary point is invalid",
"data": 3.0,
"valid": false
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "minItems validation",
"schema": {"minItems": 1},
"tests": [
{
"description": "longer is valid",
"data": [1, 2],
"valid": true
},
{
"description": "exact length is valid",
"data": [1],
"valid": true
},
{
"description": "too short is invalid",
"data": [],
"valid": false
},
{
"description": "ignores non-arrays",
"data": "",
"valid": true
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "minLength validation",
"schema": {"minLength": 2},
"tests": [
{
"description": "longer is valid",
"data": "foo",
"valid": true
},
{
"description": "exact length is valid",
"data": "fo",
"valid": true
},
{
"description": "too short is invalid",
"data": "f",
"valid": false
},
{
"description": "ignores non-strings",
"data": 1,
"valid": true
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "minProperties validation",
"schema": {"minProperties": 1},
"tests": [
{
"description": "longer is valid",
"data": {"foo": 1, "bar": 2},
"valid": true
},
{
"description": "exact length is valid",
"data": {"foo": 1},
"valid": true
},
{
"description": "too short is invalid",
"data": {},
"valid": false
},
{
"description": "ignores non-objects",
"data": "",
"valid": true
}
]
}
]

View File

@ -0,0 +1,42 @@
[
{
"description": "minimum validation",
"schema": {"minimum": 1.1},
"tests": [
{
"description": "above the minimum is valid",
"data": 2.6,
"valid": true
},
{
"description": "below the minimum is invalid",
"data": 0.6,
"valid": false
},
{
"description": "ignores non-numbers",
"data": "x",
"valid": true
}
]
},
{
"description": "exclusiveMinimum validation",
"schema": {
"minimum": 1.1,
"exclusiveMinimum": true
},
"tests": [
{
"description": "above the minimum is still valid",
"data": 1.2,
"valid": true
},
{
"description": "boundary point is invalid",
"data": 1.1,
"valid": false
}
]
}
]

Some files were not shown because too many files have changed in this diff Show More