mirror of
https://git.mirrors.martin98.com/https://github.com/syoyo/tinygltf.git
synced 2025-07-09 05:51:49 +08:00
Remove validator example scine it requires nlohmann's json.hpp
Embed json11.cpp/json11.hpp into `tiny_gltf.h`
This commit is contained in:
parent
9e4a4d5b94
commit
06f93c859d
@ -10,10 +10,8 @@ ADD_EXECUTABLE ( loader_example
|
|||||||
|
|
||||||
ADD_SUBDIRECTORY ( examples/gltfutil )
|
ADD_SUBDIRECTORY ( examples/gltfutil )
|
||||||
ADD_SUBDIRECTORY ( examples/glview )
|
ADD_SUBDIRECTORY ( examples/glview )
|
||||||
ADD_SUBDIRECTORY ( examples/validator )
|
|
||||||
|
|
||||||
INSTALL ( FILES
|
INSTALL ( FILES
|
||||||
json.hpp
|
|
||||||
stb_image.h
|
stb_image.h
|
||||||
stb_image_write.h
|
stb_image_write.h
|
||||||
tiny_gltf.h
|
tiny_gltf.h
|
||||||
|
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
|||||||
#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -Wno-c++98-compat
|
#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -Wno-c++98-compat
|
||||||
|
|
||||||
all:
|
all:
|
||||||
clang++ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o loader_example loader_example.cc json11.cpp
|
clang++ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o loader_example loader_example.cc
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
deps/cpplint.py tiny_gltf.h
|
deps/cpplint.py tiny_gltf.h
|
||||||
|
10
README.md
10
README.md
@ -28,6 +28,7 @@ v2.0.0 release(22 Aug, 2018)!
|
|||||||
* [x] Android NDK
|
* [x] Android NDK
|
||||||
* [x] Android + CrystaX(NDK drop-in replacement) GCC
|
* [x] Android + CrystaX(NDK drop-in replacement) GCC
|
||||||
* [x] Web using Emscripten(LLVM)
|
* [x] Web using Emscripten(LLVM)
|
||||||
|
* [x] Can be compiled with no RTTI, noexception.
|
||||||
* Moderate parsing time and memory consumption.
|
* Moderate parsing time and memory consumption.
|
||||||
* glTF specification v2.0.0
|
* glTF specification v2.0.0
|
||||||
* [x] ASCII glTF
|
* [x] ASCII glTF
|
||||||
@ -91,7 +92,8 @@ TinyGLTF uses the following third party libraries.
|
|||||||
|
|
||||||
## Build and example
|
## Build and example
|
||||||
|
|
||||||
Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your project.
|
Copy `stb_image.h`, `stb_image_write.h`, and `tiny_gltf.h` to your project.
|
||||||
|
json11 code is embeded into `tiny_gltf.h`
|
||||||
|
|
||||||
### Loading glTF 2.0 model
|
### Loading glTF 2.0 model
|
||||||
|
|
||||||
@ -100,7 +102,9 @@ Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your pr
|
|||||||
#define TINYGLTF_IMPLEMENTATION
|
#define TINYGLTF_IMPLEMENTATION
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
// #define TINYGLTF_NOEXCEPTION // optional. disable exception handling.
|
// Optional. Uncomment these if you want external json11.cpp instead of embedded json11.cpp code
|
||||||
|
//#define TINYGLTF_USE_EXTERNAL_JSON11_CPP
|
||||||
|
//#include "json11.hpp"
|
||||||
#include "tiny_gltf.h"
|
#include "tiny_gltf.h"
|
||||||
|
|
||||||
using namespace tinygltf;
|
using namespace tinygltf;
|
||||||
@ -129,12 +133,12 @@ if (!ret) {
|
|||||||
|
|
||||||
## Compile options
|
## Compile options
|
||||||
|
|
||||||
* `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF.
|
|
||||||
* `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
|
* `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
|
||||||
* `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
|
* `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
|
||||||
* `TINYGLTF_NO_EXTERNAL_IMAGE` : Do not try to load external image file. This option woulde be helpful if you do not want load image file during glTF parsing.
|
* `TINYGLTF_NO_EXTERNAL_IMAGE` : Do not try to load external image file. This option woulde be helpful if you do not want load image file during glTF parsing.
|
||||||
* `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand.
|
* `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand.
|
||||||
* `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file.
|
* `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file.
|
||||||
|
* `TINYGLTF_USE_EXTERNAL_JSON11_CPP` : Use externally supplied json11.cpp/json11.hpp. This option may be helpful if you use json11.cpp in other source codes of your project.
|
||||||
|
|
||||||
### Saving gltTF 2.0 model
|
### Saving gltTF 2.0 model
|
||||||
* [ ] Buffers.
|
* [ ] Buffers.
|
||||||
|
@ -312,7 +312,11 @@ int main(int argc, char **argv) {
|
|||||||
glewExperimental = GL_TRUE;
|
glewExperimental = GL_TRUE;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
glewInit();
|
if (glewInit() != GLEW_OK) {
|
||||||
|
std::cerr << "glew init error." << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
|
std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ include_directories(
|
|||||||
|
|
||||||
add_executable(glview
|
add_executable(glview
|
||||||
glview.cc
|
glview.cc
|
||||||
../../json11.cpp
|
|
||||||
../common/trackball.cc
|
../common/trackball.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
project(tinygltf-validator CXX)
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.2)
|
|
||||||
|
|
||||||
# exe
|
|
||||||
add_executable(tinygltf-validator
|
|
||||||
app/tinygltf-validate.cc
|
|
||||||
src/json-schema.hpp
|
|
||||||
src/json-schema-draft4.json.cpp
|
|
||||||
src/json-uri.cpp
|
|
||||||
src/json-validator.cpp)
|
|
||||||
|
|
||||||
target_include_directories(tinygltf-validator
|
|
||||||
PUBLIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
||||||
|
|
||||||
target_compile_features(tinygltf-validator
|
|
||||||
PUBLIC
|
|
||||||
cxx_range_for) # for C++11 - flags
|
|
||||||
# Enable more compiler warnings, except when using Visual Studio compiler
|
|
||||||
if(NOT MSVC)
|
|
||||||
target_compile_options(tinygltf-validator
|
|
||||||
PUBLIC
|
|
||||||
-Wall -Wextra)
|
|
||||||
endif()
|
|
||||||
target_compile_definitions(tinygltf-validator
|
|
||||||
PRIVATE
|
|
||||||
-DJSON_SCHEMA_VALIDATOR_EXPORTS)
|
|
||||||
|
|
||||||
# regex with boost if gcc < 4.8 - default is std::regex
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
||||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9.0")
|
|
||||||
find_package(Boost COMPONENTS regex)
|
|
||||||
if(NOT Boost_FOUND)
|
|
||||||
message(STATUS "GCC less then 4.9 and boost-regex NOT found - no regex used")
|
|
||||||
target_compile_definitions(tinygltf-validator PRIVATE -DJSON_SCHEMA_NO_REGEX)
|
|
||||||
else()
|
|
||||||
message(STATUS "GCC less then 4.9 and boost-regex FOUND - using boost::regex")
|
|
||||||
target_compile_definitions(tinygltf-validator PRIVATE -DJSON_SCHEMA_BOOST_REGEX)
|
|
||||||
target_include_directories(tinygltf-validator PRIVATE ${Boost_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(tinygltf-validator PRIVATE ${Boost_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# test-zone
|
|
||||||
# enable_testing()
|
|
||||||
|
|
||||||
install ( TARGETS
|
|
||||||
tinygltf-validator
|
|
||||||
DESTINATION
|
|
||||||
bin
|
|
||||||
)
|
|
@ -1,22 +0,0 @@
|
|||||||
Modern C++ JSON schema validator is licensed under the MIT License
|
|
||||||
<http://opensource.org/licenses/MIT>:
|
|
||||||
|
|
||||||
Copyright (c) 2016 Patrick Boettcher
|
|
||||||
|
|
||||||
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.
|
|
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2013-2017 Niels Lohmann
|
|
||||||
|
|
||||||
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.
|
|
@ -1,32 +0,0 @@
|
|||||||
# tinygltf-validator
|
|
||||||
|
|
||||||
TinyGLTF validator based on Modern C++ JSON schema validator https://github.com/pboettch/json-schema-validator
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Experimental. W.I.P.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
* C++11 compiler
|
|
||||||
* CMake
|
|
||||||
|
|
||||||
## How to build
|
|
||||||
|
|
||||||
```
|
|
||||||
$ mkdir build
|
|
||||||
$ cd build
|
|
||||||
$ cmake ..
|
|
||||||
$ make
|
|
||||||
```
|
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
```
|
|
||||||
$ gltf-validator /path/to/file.gltf /path/to/gltf-schema
|
|
||||||
```
|
|
||||||
|
|
||||||
## Third party licenses
|
|
||||||
|
|
||||||
* json.hpp https://github.com/nlohmann/json : MIT
|
|
||||||
* json-schema-validator https://github.com/pboettch/json-schema-validator : MIT
|
|
@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Modern C++ JSON schema validator
|
|
||||||
*
|
|
||||||
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#include <json-schema.hpp>
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using nlohmann::json;
|
|
||||||
using nlohmann::json_uri;
|
|
||||||
using nlohmann::json_schema_draft4::json_validator;
|
|
||||||
|
|
||||||
static void usage(const char *name)
|
|
||||||
{
|
|
||||||
std::cerr << "Usage: " << name << " <gltf file> <gltf schema dir>\n";
|
|
||||||
std::cerr << " schema dir : $glTF/specification/2.0/schema\n";
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
resolver r(nlohmann::json_schema_draft4::root_schema,
|
|
||||||
nlohmann::json_schema_draft4::root_schema["id"]);
|
|
||||||
schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
|
|
||||||
assert(r.undefined_refs.size() == 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
static void loader(const json_uri &uri, json &schema)
|
|
||||||
{
|
|
||||||
std::fstream lf("." + uri.path());
|
|
||||||
if (!lf.good())
|
|
||||||
throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path());
|
|
||||||
|
|
||||||
try {
|
|
||||||
lf >> schema;
|
|
||||||
} catch (std::exception &e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool validate(const std::string &schema_dir, const std::string &filename)
|
|
||||||
{
|
|
||||||
std::string gltf_schema = schema_dir + "/glTF.schema.json";
|
|
||||||
|
|
||||||
std::fstream f(gltf_schema);
|
|
||||||
if (!f.good()) {
|
|
||||||
std::cerr << "could not open " << gltf_schema << " for reading\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1) Read the schema for the document you want to validate
|
|
||||||
json schema;
|
|
||||||
try {
|
|
||||||
f >> schema;
|
|
||||||
} catch (std::exception &e) {
|
|
||||||
std::cerr << e.what() << " at " << f.tellp() << " - while parsing the schema\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) create the validator and
|
|
||||||
json_validator validator([&schema_dir](const json_uri &uri, json &schema) {
|
|
||||||
std::cout << "uri.url : " << uri.url() << std::endl;
|
|
||||||
std::cout << "uri.path : " << uri.path() << std::endl;
|
|
||||||
|
|
||||||
std::fstream lf(schema_dir + "/" + uri.path());
|
|
||||||
if (!lf.good())
|
|
||||||
throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path());
|
|
||||||
|
|
||||||
try {
|
|
||||||
lf >> schema;
|
|
||||||
} catch (std::exception &e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}, [](const std::string &, const std::string &) {});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// insert this schema as the root to the validator
|
|
||||||
// this resolves remote-schemas, sub-schemas and references via the given loader-function
|
|
||||||
validator.set_root_schema(schema);
|
|
||||||
} catch (std::exception &e) {
|
|
||||||
std::cerr << "setting root schema failed\n";
|
|
||||||
std::cerr << e.what() << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) do the actual validation of the document
|
|
||||||
json document;
|
|
||||||
|
|
||||||
std::fstream d(filename);
|
|
||||||
if (!d.good()) {
|
|
||||||
std::cerr << "could not open " << filename << " for reading\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
d >> document;
|
|
||||||
validator.validate(document);
|
|
||||||
} catch (std::exception &e) {
|
|
||||||
std::cerr << "schema validation failed\n";
|
|
||||||
std::cerr << e.what() << " at offset: " << d.tellg() << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cerr << "document is valid\n";
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
if (argc != 3)
|
|
||||||
usage(argv[0]);
|
|
||||||
|
|
||||||
bool ret = validate(argv[1], argv[2]);
|
|
||||||
|
|
||||||
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
@ -1,160 +0,0 @@
|
|||||||
#include <json-schema.hpp>
|
|
||||||
|
|
||||||
namespace nlohmann
|
|
||||||
{
|
|
||||||
namespace json_schema_draft4
|
|
||||||
{
|
|
||||||
|
|
||||||
json draft4_schema_builtin = R"( {
|
|
||||||
"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": {}
|
|
||||||
} )"_json;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,201 +0,0 @@
|
|||||||
/*
|
|
||||||
* Modern C++ JSON schema validator
|
|
||||||
*
|
|
||||||
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#ifndef NLOHMANN_JSON_SCHEMA_HPP__
|
|
||||||
#define NLOHMANN_JSON_SCHEMA_HPP__
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# ifdef JSON_SCHEMA_VALIDATOR_EXPORTS
|
|
||||||
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllexport)
|
|
||||||
# else
|
|
||||||
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllimport)
|
|
||||||
# endif
|
|
||||||
#else
|
|
||||||
# define JSON_SCHEMA_VALIDATOR_API
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <json.hpp>
|
|
||||||
|
|
||||||
// make yourself a home - welcome to nlohmann's namespace
|
|
||||||
namespace nlohmann
|
|
||||||
{
|
|
||||||
|
|
||||||
// a class representing a JSON-pointer RFC6901
|
|
||||||
//
|
|
||||||
// examples of JSON pointers
|
|
||||||
//
|
|
||||||
// # - root of the current document
|
|
||||||
// #item - refers to the object which is identified ("id") by `item`
|
|
||||||
// in the current document
|
|
||||||
// #/path/to/element
|
|
||||||
// - refers to the element in /path/to from the root-document
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// The json_pointer-class stores everything in a string, which might seem bizarre
|
|
||||||
// as parsing is done from a string to a string, but from_string() is also
|
|
||||||
// doing some formatting.
|
|
||||||
//
|
|
||||||
// TODO
|
|
||||||
// ~ and % - codec
|
|
||||||
// needs testing and clarification regarding the '#' at the beginning
|
|
||||||
|
|
||||||
class json_pointer
|
|
||||||
{
|
|
||||||
std::string str_;
|
|
||||||
|
|
||||||
void from_string(const std::string &r);
|
|
||||||
|
|
||||||
public:
|
|
||||||
json_pointer(const std::string &s = "")
|
|
||||||
{
|
|
||||||
from_string(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void append(const std::string &elem)
|
|
||||||
{
|
|
||||||
str_.append(elem);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &to_string() const
|
|
||||||
{
|
|
||||||
return str_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// A class representing a JSON-URI for schemas derived from
|
|
||||||
// section 8 of JSON Schema: A Media Type for Describing JSON Documents
|
|
||||||
// draft-wright-json-schema-00
|
|
||||||
//
|
|
||||||
// New URIs can be derived from it using the derive()-method.
|
|
||||||
// This is useful for resolving refs or subschema-IDs in json-schemas.
|
|
||||||
//
|
|
||||||
// This is done implement the requirements described in section 8.2.
|
|
||||||
//
|
|
||||||
class JSON_SCHEMA_VALIDATOR_API json_uri
|
|
||||||
{
|
|
||||||
std::string urn_;
|
|
||||||
|
|
||||||
std::string proto_;
|
|
||||||
std::string hostname_;
|
|
||||||
std::string path_;
|
|
||||||
json_pointer pointer_;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// decodes a JSON uri and replaces all or part of the currently stored values
|
|
||||||
void from_string(const std::string &uri);
|
|
||||||
|
|
||||||
std::tuple<std::string, std::string, std::string, std::string, std::string> tie() const
|
|
||||||
{
|
|
||||||
return std::tie(urn_, proto_, hostname_, path_, pointer_.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
json_uri(const std::string &uri)
|
|
||||||
{
|
|
||||||
from_string(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string protocol() const { return proto_; }
|
|
||||||
const std::string hostname() const { return hostname_; }
|
|
||||||
const std::string path() const { return path_; }
|
|
||||||
const json_pointer pointer() const { return pointer_; }
|
|
||||||
|
|
||||||
const std::string url() const;
|
|
||||||
|
|
||||||
// decode and encode strings for ~ and % escape sequences
|
|
||||||
static std::string unescape(const std::string &);
|
|
||||||
static std::string escape(const std::string &);
|
|
||||||
|
|
||||||
// create a new json_uri based in this one and the given uri
|
|
||||||
// resolves relative changes (pathes or pointers) and resets part if proto or hostname changes
|
|
||||||
json_uri derive(const std::string &uri) const
|
|
||||||
{
|
|
||||||
json_uri u = *this;
|
|
||||||
u.from_string(uri);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
// append a pointer-field to the pointer-part of this uri
|
|
||||||
json_uri append(const std::string &field) const
|
|
||||||
{
|
|
||||||
json_uri u = *this;
|
|
||||||
u.pointer_.append("/" + field);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string to_string() const;
|
|
||||||
|
|
||||||
friend bool operator<(const json_uri &l, const json_uri &r)
|
|
||||||
{
|
|
||||||
return l.tie() < r.tie();
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(const json_uri &l, const json_uri &r)
|
|
||||||
{
|
|
||||||
return l.tie() == r.tie();
|
|
||||||
}
|
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &os, const json_uri &u);
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace json_schema_draft4
|
|
||||||
{
|
|
||||||
|
|
||||||
extern json draft4_schema_builtin;
|
|
||||||
|
|
||||||
class JSON_SCHEMA_VALIDATOR_API json_validator
|
|
||||||
{
|
|
||||||
std::vector<std::shared_ptr<json>> schema_store_;
|
|
||||||
std::shared_ptr<json> root_schema_;
|
|
||||||
std::function<void(const json_uri &, json &)> schema_loader_ = nullptr;
|
|
||||||
std::function<void(const std::string &, const std::string &)> format_check_ = nullptr;
|
|
||||||
|
|
||||||
std::map<json_uri, const json *> schema_refs_;
|
|
||||||
|
|
||||||
void validate(const json &instance, const json &schema_, const std::string &name);
|
|
||||||
void validate_array(const json &instance, const json &schema_, const std::string &name);
|
|
||||||
void validate_object(const json &instance, const json &schema_, const std::string &name);
|
|
||||||
void validate_string(const json &instance, const json &schema, const std::string &name);
|
|
||||||
|
|
||||||
void insert_schema(const json &input, const json_uri &id);
|
|
||||||
|
|
||||||
public:
|
|
||||||
json_validator(std::function<void(const json_uri &, json &)> loader = nullptr,
|
|
||||||
std::function<void(const std::string &, const std::string &)> format = nullptr)
|
|
||||||
: schema_loader_(loader), format_check_(format)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert and set a root-schema
|
|
||||||
void set_root_schema(const json &);
|
|
||||||
|
|
||||||
// validate a json-document based on the root-schema
|
|
||||||
void validate(const json &instance);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // json_schema_draft4
|
|
||||||
} // nlohmann
|
|
||||||
|
|
||||||
#endif /* NLOHMANN_JSON_SCHEMA_HPP__ */
|
|
@ -1,190 +0,0 @@
|
|||||||
/*
|
|
||||||
* Modern C++ JSON schema validator
|
|
||||||
*
|
|
||||||
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#include "json-schema.hpp"
|
|
||||||
|
|
||||||
namespace nlohmann
|
|
||||||
{
|
|
||||||
|
|
||||||
void json_pointer::from_string(const std::string &r)
|
|
||||||
{
|
|
||||||
str_ = "#";
|
|
||||||
|
|
||||||
if (r.size() == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (r[0] != '#')
|
|
||||||
throw std::invalid_argument("not a valid JSON pointer - missing # at the beginning");
|
|
||||||
|
|
||||||
if (r.size() == 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::size_t pos = 1;
|
|
||||||
|
|
||||||
do {
|
|
||||||
std::size_t next = r.find('/', pos + 1);
|
|
||||||
str_.append(r.substr(pos, next - pos));
|
|
||||||
pos = next;
|
|
||||||
} while (pos != std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_uri::from_string(const std::string &uri)
|
|
||||||
{
|
|
||||||
// if it is an urn take it as it is - maybe there is more to be done
|
|
||||||
if (uri.find("urn:") == 0) {
|
|
||||||
urn_ = uri;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string pointer = "#"; // default pointer is the root
|
|
||||||
|
|
||||||
// first split the URI into URL and JSON-pointer
|
|
||||||
auto pointer_separator = uri.find('#');
|
|
||||||
if (pointer_separator != std::string::npos) // and extract the JSON-pointer-string if found
|
|
||||||
pointer = uri.substr(pointer_separator);
|
|
||||||
|
|
||||||
// the rest is an URL
|
|
||||||
std::string url = uri.substr(0, pointer_separator);
|
|
||||||
if (url.size()) { // if an URL is part of the URI
|
|
||||||
|
|
||||||
std::size_t pos = 0;
|
|
||||||
auto proto = url.find("://", pos);
|
|
||||||
if (proto != std::string::npos) { // extract the protocol
|
|
||||||
proto_ = url.substr(pos, proto - pos);
|
|
||||||
pos = 3 + proto; // 3 == "://"
|
|
||||||
|
|
||||||
auto hostname = url.find("/", pos);
|
|
||||||
if (hostname != std::string::npos) { // and the hostname (no proto without hostname)
|
|
||||||
hostname_ = url.substr(pos, hostname - pos);
|
|
||||||
pos = hostname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the rest is the path
|
|
||||||
auto path = url.substr(pos);
|
|
||||||
if (path[0] == '/') // if it starts with a / it is root-path
|
|
||||||
path_ = path;
|
|
||||||
else { // otherwise it is a subfolder
|
|
||||||
// HACK(syoyo): Force append '/' for glTF json schemas
|
|
||||||
path_ = path;
|
|
||||||
//path_.append(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer_ = json_pointer("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pointer.size() > 0)
|
|
||||||
pointer_ = pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string json_uri::url() const
|
|
||||||
{
|
|
||||||
std::stringstream s;
|
|
||||||
|
|
||||||
if (proto_.size() > 0)
|
|
||||||
s << proto_ << "://";
|
|
||||||
|
|
||||||
s << hostname_
|
|
||||||
<< path_;
|
|
||||||
|
|
||||||
return s.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string json_uri::to_string() const
|
|
||||||
{
|
|
||||||
std::stringstream s;
|
|
||||||
|
|
||||||
s << urn_
|
|
||||||
<< url()
|
|
||||||
<< pointer_.to_string();
|
|
||||||
|
|
||||||
return s.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &os, const json_uri &u)
|
|
||||||
{
|
|
||||||
return os << u.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string json_uri::unescape(const std::string &src)
|
|
||||||
{
|
|
||||||
std::string l = src;
|
|
||||||
std::size_t pos = src.size() - 1;
|
|
||||||
|
|
||||||
do {
|
|
||||||
pos = l.rfind('~', pos);
|
|
||||||
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (pos < l.size() - 1) {
|
|
||||||
switch (l[pos + 1]) {
|
|
||||||
case '0':
|
|
||||||
l.replace(pos, 2, "~");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '1':
|
|
||||||
l.replace(pos, 2, "/");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos == 0)
|
|
||||||
break;
|
|
||||||
pos--;
|
|
||||||
} while (pos != std::string::npos);
|
|
||||||
|
|
||||||
// TODO - percent handling
|
|
||||||
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string json_uri::escape(const std::string &src)
|
|
||||||
{
|
|
||||||
std::vector<std::pair<std::string, std::string>> chars = {
|
|
||||||
{"~", "~0"},
|
|
||||||
{"/", "~1"},
|
|
||||||
{"%", "%25"}};
|
|
||||||
|
|
||||||
std::string l = src;
|
|
||||||
|
|
||||||
for (const auto &c : chars) {
|
|
||||||
std::size_t pos = 0;
|
|
||||||
do {
|
|
||||||
pos = l.find(c.first, pos);
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
break;
|
|
||||||
l.replace(pos, 1, c.second);
|
|
||||||
pos += c.second.size();
|
|
||||||
} while (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // nlohmann
|
|
@ -1,738 +0,0 @@
|
|||||||
/*
|
|
||||||
* Modern C++ JSON schema validator
|
|
||||||
*
|
|
||||||
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#include <json-schema.hpp>
|
|
||||||
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
using nlohmann::json;
|
|
||||||
using nlohmann::json_uri;
|
|
||||||
|
|
||||||
#ifdef JSON_SCHEMA_BOOST_REGEX
|
|
||||||
#include <boost/regex.hpp>
|
|
||||||
#define REGEX_NAMESPACE boost
|
|
||||||
#elif defined(JSON_SCHEMA_NO_REGEX)
|
|
||||||
#define NO_STD_REGEX
|
|
||||||
#else
|
|
||||||
#include <regex>
|
|
||||||
#define REGEX_NAMESPACE std
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
class resolver
|
|
||||||
{
|
|
||||||
void resolve(json &schema, json_uri id)
|
|
||||||
{
|
|
||||||
// look for the id-field in this schema
|
|
||||||
auto fid = schema.find("id");
|
|
||||||
|
|
||||||
// found?
|
|
||||||
if (fid != schema.end() &&
|
|
||||||
fid.value().type() == json::value_t::string)
|
|
||||||
id = id.derive(fid.value()); // resolve to a full id with URL + path based on the parent
|
|
||||||
|
|
||||||
// already existing - error
|
|
||||||
if (schema_refs.find(id) != schema_refs.end())
|
|
||||||
throw std::invalid_argument("schema " + id.to_string() + " already present in local resolver");
|
|
||||||
|
|
||||||
// store a raw pointer to this (sub-)schema referenced by its absolute json_uri
|
|
||||||
// this (sub-)schema is part of a schema stored inside schema_store_ so we can the a raw-pointer-ref
|
|
||||||
schema_refs[id] = &schema;
|
|
||||||
|
|
||||||
for (auto i = schema.begin(), end = schema.end(); i != end; ++i) {
|
|
||||||
// FIXME: this inhibits the user adding properties with the key "default"
|
|
||||||
if (i.key() == "default") /* default value can be objects, but are not schemas */
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (i.value().type()) {
|
|
||||||
|
|
||||||
case json::value_t::object: // child is object, it is a schema
|
|
||||||
resolve(i.value(), id.append(json_uri::escape(i.key())));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::array: {
|
|
||||||
std::size_t index = 0;
|
|
||||||
auto child_id = id.append(json_uri::escape(i.key()));
|
|
||||||
for (auto &v : i.value()) {
|
|
||||||
if (v.type() == json::value_t::object) // array element is object
|
|
||||||
resolve(v, child_id.append(std::to_string(index)));
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case json::value_t::string:
|
|
||||||
if (i.key() == "$ref") {
|
|
||||||
json_uri ref = id.derive(i.value());
|
|
||||||
i.value() = ref.to_string();
|
|
||||||
refs.insert(ref);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<json_uri> refs;
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::set<json_uri> undefined_refs;
|
|
||||||
|
|
||||||
std::map<json_uri, const json *> schema_refs;
|
|
||||||
|
|
||||||
resolver(json &schema, json_uri id)
|
|
||||||
{
|
|
||||||
// if schema has an id use it as name and to retrieve the namespace (URL)
|
|
||||||
auto fid = schema.find("id");
|
|
||||||
if (fid != schema.end())
|
|
||||||
id = id.derive(fid.value());
|
|
||||||
|
|
||||||
resolve(schema, id);
|
|
||||||
|
|
||||||
// refs now contains all references
|
|
||||||
//
|
|
||||||
// local references should be resolvable inside the same URL
|
|
||||||
//
|
|
||||||
// undefined_refs will only contain external references
|
|
||||||
for (auto r : refs) {
|
|
||||||
if (schema_refs.find(r) == schema_refs.end()) {
|
|
||||||
if (r.url() == id.url()) // same url means referencing a sub-schema
|
|
||||||
// of the same document, which has not been found
|
|
||||||
throw std::invalid_argument("sub-schema " + r.pointer().to_string() +
|
|
||||||
" in schema " + id.to_string() + " not found");
|
|
||||||
undefined_refs.insert(r.url());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void validate_type(const json &schema, const std::string &expected_type, const std::string &name)
|
|
||||||
{
|
|
||||||
const auto &type_it = schema.find("type");
|
|
||||||
if (type_it == schema.end())
|
|
||||||
/* TODO something needs to be done here, I think */
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto &type_instance = type_it.value();
|
|
||||||
|
|
||||||
// any of the types in this array
|
|
||||||
if (type_instance.type() == json::value_t::array) {
|
|
||||||
if ((std::find(type_instance.begin(),
|
|
||||||
type_instance.end(),
|
|
||||||
expected_type) != type_instance.end()) ||
|
|
||||||
(expected_type == "integer" &&
|
|
||||||
std::find(type_instance.begin(),
|
|
||||||
type_instance.end(),
|
|
||||||
"number") != type_instance.end()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::ostringstream s;
|
|
||||||
s << expected_type << " is not any of " << type_instance << " for " << name;
|
|
||||||
throw std::invalid_argument(s.str());
|
|
||||||
|
|
||||||
} else { // type_instance is a string
|
|
||||||
if (type_instance == expected_type ||
|
|
||||||
(type_instance == "number" && expected_type == "integer"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
throw std::invalid_argument(name + " is " + expected_type +
|
|
||||||
", but required type is " + type_instance.get<std::string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_enum(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
const auto &enum_value = schema.find("enum");
|
|
||||||
if (enum_value == schema.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (std::find(enum_value.value().begin(), enum_value.value().end(), instance) != enum_value.value().end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::ostringstream s;
|
|
||||||
s << "invalid enum-value '" << instance << "' "
|
|
||||||
<< "for instance '" << name << "'. Candidates are " << enum_value.value() << ".";
|
|
||||||
|
|
||||||
throw std::invalid_argument(s.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_boolean(const json & /*instance*/, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "boolean", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_numeric(const json &schema, const std::string &name, double value)
|
|
||||||
{
|
|
||||||
// multipleOf - if the rest of the division is 0 -> OK
|
|
||||||
const auto &multipleOf = schema.find("multipleOf");
|
|
||||||
if (multipleOf != schema.end()) {
|
|
||||||
if (multipleOf.value().get<double>() != 0.0) {
|
|
||||||
|
|
||||||
double v = value;
|
|
||||||
v /= multipleOf.value().get<double>();
|
|
||||||
|
|
||||||
if (v != (double) (long) v)
|
|
||||||
throw std::out_of_range(name + " is not a multiple ...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &maximum = schema.find("maximum");
|
|
||||||
if (maximum != schema.end()) {
|
|
||||||
double maxi = maximum.value();
|
|
||||||
auto ex = std::out_of_range(name + " exceeds maximum of " + std::to_string(maxi));
|
|
||||||
if (schema.find("exclusiveMaximum") != schema.end()) {
|
|
||||||
if (value >= maxi)
|
|
||||||
throw ex;
|
|
||||||
} else {
|
|
||||||
if (value > maxi)
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &minimum = schema.find("minimum");
|
|
||||||
if (minimum != schema.end()) {
|
|
||||||
double mini = minimum.value();
|
|
||||||
auto ex = std::out_of_range(name + " exceeds minimum of " + std::to_string(mini));
|
|
||||||
if (schema.find("exclusiveMinimum") != schema.end()) {
|
|
||||||
if (value <= mini)
|
|
||||||
throw ex;
|
|
||||||
} else {
|
|
||||||
if (value < mini)
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_integer(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "integer", name);
|
|
||||||
validate_numeric(schema, name, instance.get<int>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_unsigned(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "integer", name);
|
|
||||||
validate_numeric(schema, name, instance.get<unsigned>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_float(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "number", name);
|
|
||||||
validate_numeric(schema, name, instance.get<double>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_null(const json & /*instance*/, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "null", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
|
|
||||||
namespace nlohmann
|
|
||||||
{
|
|
||||||
namespace json_schema_draft4
|
|
||||||
{
|
|
||||||
|
|
||||||
void json_validator::insert_schema(const json &input, const json_uri &id)
|
|
||||||
{
|
|
||||||
// allocate create a copy for later storage - if resolving reference works
|
|
||||||
std::shared_ptr<json> schema = std::make_shared<json>(input);
|
|
||||||
|
|
||||||
do {
|
|
||||||
// resolve all local schemas and references
|
|
||||||
resolver r(*schema, id);
|
|
||||||
|
|
||||||
// check whether all undefined schema references can be resolved with existing ones
|
|
||||||
std::set<json_uri> undefined;
|
|
||||||
for (auto &ref : r.undefined_refs)
|
|
||||||
if (schema_refs_.find(ref) == schema_refs_.end()) // exact schema reference not found
|
|
||||||
undefined.insert(ref);
|
|
||||||
|
|
||||||
if (undefined.size() == 0) { // no undefined references
|
|
||||||
// now insert all schema-references
|
|
||||||
// check whether all schema-references are new
|
|
||||||
for (auto &sref : r.schema_refs) {
|
|
||||||
if (schema_refs_.find(sref.first) != schema_refs_.end())
|
|
||||||
// HACK(syoyo): Skip duplicated schema.
|
|
||||||
break;
|
|
||||||
//throw std::invalid_argument("schema " + sref.first.to_string() + " already present in validator.");
|
|
||||||
}
|
|
||||||
// no undefined references and no duplicated schema - store the schema
|
|
||||||
schema_store_.push_back(schema);
|
|
||||||
|
|
||||||
// and insert all references
|
|
||||||
schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema_loader_ == nullptr)
|
|
||||||
throw std::invalid_argument("schema contains undefined references to other schemas, needed schema-loader.");
|
|
||||||
|
|
||||||
for (auto undef : undefined) {
|
|
||||||
json ext;
|
|
||||||
|
|
||||||
schema_loader_(undef, ext);
|
|
||||||
insert_schema(ext, undef.url());
|
|
||||||
}
|
|
||||||
} while (1);
|
|
||||||
|
|
||||||
// store the document root-schema
|
|
||||||
if (id == json_uri("#"))
|
|
||||||
root_schema_ = schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_validator::validate(const json &instance)
|
|
||||||
{
|
|
||||||
if (root_schema_ == nullptr)
|
|
||||||
throw std::invalid_argument("no root-schema has been inserted. Cannot validate an instance without it.");
|
|
||||||
|
|
||||||
validate(instance, *root_schema_, "root");
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_validator::set_root_schema(const json &schema)
|
|
||||||
{
|
|
||||||
insert_schema(schema, json_uri("#"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_validator::validate(const json &instance, const json &schema_, const std::string &name)
|
|
||||||
{
|
|
||||||
const json *schema = &schema_;
|
|
||||||
|
|
||||||
// $ref resolution
|
|
||||||
do {
|
|
||||||
const auto &ref = schema->find("$ref");
|
|
||||||
if (ref == schema->end())
|
|
||||||
break;
|
|
||||||
|
|
||||||
auto it = schema_refs_.find(ref.value().get<std::string>());
|
|
||||||
|
|
||||||
if (it == schema_refs_.end())
|
|
||||||
throw std::invalid_argument("schema reference " + ref.value().get<std::string>() + " not found. Make sure all schemas have been inserted before validation.");
|
|
||||||
|
|
||||||
schema = it->second;
|
|
||||||
} while (1); // loop in case of nested refs
|
|
||||||
|
|
||||||
// not
|
|
||||||
const auto attr = schema->find("not");
|
|
||||||
if (attr != schema->end()) {
|
|
||||||
bool ok;
|
|
||||||
|
|
||||||
try {
|
|
||||||
validate(instance, attr.value(), name);
|
|
||||||
ok = false;
|
|
||||||
} catch (std::exception &) {
|
|
||||||
ok = true;
|
|
||||||
}
|
|
||||||
if (!ok)
|
|
||||||
throw std::invalid_argument("schema match for " + name + " but a not-match is defined by schema.");
|
|
||||||
return; // return here - not cannot be mixed with based-schemas?
|
|
||||||
}
|
|
||||||
|
|
||||||
// allOf, anyOf, oneOf
|
|
||||||
const json *combined_schemas = nullptr;
|
|
||||||
enum {
|
|
||||||
none,
|
|
||||||
allOf,
|
|
||||||
anyOf,
|
|
||||||
oneOf
|
|
||||||
} combine_logic = none;
|
|
||||||
|
|
||||||
{
|
|
||||||
const auto &attr = schema->find("allOf");
|
|
||||||
if (attr != schema->end()) {
|
|
||||||
combine_logic = allOf;
|
|
||||||
combined_schemas = &attr.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto &attr = schema->find("anyOf");
|
|
||||||
if (attr != schema->end()) {
|
|
||||||
combine_logic = anyOf;
|
|
||||||
combined_schemas = &attr.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto &attr = schema->find("oneOf");
|
|
||||||
if (attr != schema->end()) {
|
|
||||||
combine_logic = oneOf;
|
|
||||||
combined_schemas = &attr.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (combine_logic != none) {
|
|
||||||
std::size_t count = 0;
|
|
||||||
std::ostringstream sub_schema_err;
|
|
||||||
|
|
||||||
for (const auto &s : *combined_schemas) {
|
|
||||||
try {
|
|
||||||
validate(instance, s, name);
|
|
||||||
count++;
|
|
||||||
} catch (std::exception &e) {
|
|
||||||
sub_schema_err << " one schema failed because: " << e.what() << "\n";
|
|
||||||
|
|
||||||
if (combine_logic == allOf)
|
|
||||||
throw std::out_of_range("At least one schema has failed for " + name + " where allOf them were requested.\n" + sub_schema_err.str());
|
|
||||||
}
|
|
||||||
if (combine_logic == oneOf && count > 1)
|
|
||||||
throw std::out_of_range("More than one schema has succeeded for " + name + " where only oneOf them was requested.\n" + sub_schema_err.str());
|
|
||||||
}
|
|
||||||
if ((combine_logic == anyOf || combine_logic == oneOf) && count == 0)
|
|
||||||
throw std::out_of_range("No schema has succeeded for " + name + " but anyOf/oneOf them should have worked.\n" + sub_schema_err.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// check (base) schema
|
|
||||||
validate_enum(instance, *schema, name);
|
|
||||||
|
|
||||||
switch (instance.type()) {
|
|
||||||
case json::value_t::object:
|
|
||||||
validate_object(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::array:
|
|
||||||
validate_array(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::string:
|
|
||||||
validate_string(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::number_unsigned:
|
|
||||||
validate_unsigned(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::number_integer:
|
|
||||||
validate_integer(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::number_float:
|
|
||||||
validate_float(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::boolean:
|
|
||||||
validate_boolean(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::null:
|
|
||||||
validate_null(instance, *schema, name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(0 && "unexpected instance type for validation");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_validator::validate_array(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "array", name);
|
|
||||||
|
|
||||||
// maxItems
|
|
||||||
const auto &maxItems = schema.find("maxItems");
|
|
||||||
if (maxItems != schema.end())
|
|
||||||
if (instance.size() > maxItems.value().get<size_t>())
|
|
||||||
throw std::out_of_range(name + " has too many items.");
|
|
||||||
|
|
||||||
// minItems
|
|
||||||
const auto &minItems = schema.find("minItems");
|
|
||||||
if (minItems != schema.end())
|
|
||||||
if (instance.size() < minItems.value().get<size_t>())
|
|
||||||
throw std::out_of_range(name + " has too few items.");
|
|
||||||
|
|
||||||
// uniqueItems
|
|
||||||
const auto &uniqueItems = schema.find("uniqueItems");
|
|
||||||
if (uniqueItems != schema.end())
|
|
||||||
if (uniqueItems.value().get<bool>() == true) {
|
|
||||||
std::set<json> array_to_set;
|
|
||||||
for (auto v : instance) {
|
|
||||||
auto ret = array_to_set.insert(v);
|
|
||||||
if (ret.second == false)
|
|
||||||
throw std::out_of_range(name + " should have only unique items.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// items and additionalItems
|
|
||||||
// default to empty schemas
|
|
||||||
auto items_iter = schema.find("items");
|
|
||||||
json items = {};
|
|
||||||
if (items_iter != schema.end())
|
|
||||||
items = items_iter.value();
|
|
||||||
|
|
||||||
auto additionalItems_iter = schema.find("additionalItems");
|
|
||||||
json additionalItems = {};
|
|
||||||
if (additionalItems_iter != schema.end())
|
|
||||||
additionalItems = additionalItems_iter.value();
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
bool validation_done = false;
|
|
||||||
|
|
||||||
for (auto &value : instance) {
|
|
||||||
std::string sub_name = name + "[" + std::to_string(i) + "]";
|
|
||||||
|
|
||||||
switch (items.type()) {
|
|
||||||
|
|
||||||
case json::value_t::array:
|
|
||||||
|
|
||||||
if (i < items.size())
|
|
||||||
validate(value, items[i], sub_name);
|
|
||||||
else {
|
|
||||||
switch (additionalItems.type()) { // items is an array
|
|
||||||
// we need to take into consideration additionalItems
|
|
||||||
case json::value_t::object:
|
|
||||||
validate(value, additionalItems, sub_name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::boolean:
|
|
||||||
if (additionalItems.get<bool>() == false)
|
|
||||||
throw std::out_of_range("additional values in array are not allowed for " + sub_name);
|
|
||||||
else
|
|
||||||
validation_done = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::object: // items is a schema
|
|
||||||
validate(value, items, sub_name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (validation_done)
|
|
||||||
break;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_validator::validate_object(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "object", name);
|
|
||||||
|
|
||||||
json properties = {};
|
|
||||||
if (schema.find("properties") != schema.end())
|
|
||||||
properties = schema["properties"];
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// check for default values of properties
|
|
||||||
// and insert them into this object, if they don't exists
|
|
||||||
// works only for object properties for the moment
|
|
||||||
if (default_value_insertion)
|
|
||||||
for (auto it = properties.begin(); it != properties.end(); ++it) {
|
|
||||||
|
|
||||||
const auto &default_value = it.value().find("default");
|
|
||||||
if (default_value == it.value().end())
|
|
||||||
continue; /* no default value -> continue */
|
|
||||||
|
|
||||||
if (instance.find(it.key()) != instance.end())
|
|
||||||
continue; /* value is present */
|
|
||||||
|
|
||||||
/* create element from default value */
|
|
||||||
instance[it.key()] = default_value.value();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// maxProperties
|
|
||||||
const auto &maxProperties = schema.find("maxProperties");
|
|
||||||
if (maxProperties != schema.end())
|
|
||||||
if (instance.size() > maxProperties.value().get<size_t>())
|
|
||||||
throw std::out_of_range(name + " has too many properties.");
|
|
||||||
|
|
||||||
// minProperties
|
|
||||||
const auto &minProperties = schema.find("minProperties");
|
|
||||||
if (minProperties != schema.end())
|
|
||||||
if (instance.size() < minProperties.value().get<size_t>())
|
|
||||||
throw std::out_of_range(name + " has too few properties.");
|
|
||||||
|
|
||||||
// additionalProperties
|
|
||||||
enum {
|
|
||||||
True,
|
|
||||||
False,
|
|
||||||
Object
|
|
||||||
} additionalProperties = True;
|
|
||||||
|
|
||||||
const auto &additionalPropertiesVal = schema.find("additionalProperties");
|
|
||||||
if (additionalPropertiesVal != schema.end()) {
|
|
||||||
if (additionalPropertiesVal.value().type() == json::value_t::boolean)
|
|
||||||
additionalProperties = additionalPropertiesVal.value().get<bool>() == true ? True : False;
|
|
||||||
else
|
|
||||||
additionalProperties = Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
// patternProperties
|
|
||||||
json patternProperties = {};
|
|
||||||
if (schema.find("patternProperties") != schema.end())
|
|
||||||
patternProperties = schema["patternProperties"];
|
|
||||||
|
|
||||||
// check all elements in object
|
|
||||||
for (auto child = instance.begin(); child != instance.end(); ++child) {
|
|
||||||
std::string child_name = name + "." + child.key();
|
|
||||||
|
|
||||||
bool property_or_patternProperties_has_validated = false;
|
|
||||||
// is this a property which is described in the schema
|
|
||||||
const auto &object_prop = properties.find(child.key());
|
|
||||||
if (object_prop != properties.end()) {
|
|
||||||
// validate the element with its schema
|
|
||||||
validate(child.value(), object_prop.value(), child_name);
|
|
||||||
property_or_patternProperties_has_validated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto pp = patternProperties.begin();
|
|
||||||
pp != patternProperties.end(); ++pp) {
|
|
||||||
#ifndef NO_STD_REGEX
|
|
||||||
REGEX_NAMESPACE::regex re(pp.key(), REGEX_NAMESPACE::regex::ECMAScript);
|
|
||||||
|
|
||||||
if (REGEX_NAMESPACE::regex_search(child.key(), re)) {
|
|
||||||
validate(child.value(), pp.value(), child_name);
|
|
||||||
property_or_patternProperties_has_validated = true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// accept everything in case of a patternProperty
|
|
||||||
property_or_patternProperties_has_validated = true;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (property_or_patternProperties_has_validated)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (additionalProperties) {
|
|
||||||
case True:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Object:
|
|
||||||
validate(child.value(), additionalPropertiesVal.value(), child_name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case False:
|
|
||||||
throw std::invalid_argument("unknown property '" + child.key() + "' in object '" + name + "'");
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// required
|
|
||||||
const auto &required = schema.find("required");
|
|
||||||
if (required != schema.end())
|
|
||||||
for (const auto &element : required.value()) {
|
|
||||||
if (instance.find(element) == instance.end()) {
|
|
||||||
throw std::invalid_argument("required element '" + element.get<std::string>() +
|
|
||||||
"' not found in object '" + name + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dependencies
|
|
||||||
const auto &dependencies = schema.find("dependencies");
|
|
||||||
if (dependencies == schema.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (auto dep = dependencies.value().cbegin();
|
|
||||||
dep != dependencies.value().cend();
|
|
||||||
++dep) {
|
|
||||||
|
|
||||||
// property not present in this instance - next
|
|
||||||
if (instance.find(dep.key()) == instance.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string sub_name = name + ".dependency-of-" + dep.key();
|
|
||||||
|
|
||||||
switch (dep.value().type()) {
|
|
||||||
|
|
||||||
case json::value_t::object:
|
|
||||||
validate(instance, dep.value(), sub_name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case json::value_t::array:
|
|
||||||
for (const auto &prop : dep.value())
|
|
||||||
if (instance.find(prop) == instance.end())
|
|
||||||
throw std::invalid_argument("failed dependency for " + sub_name + ". Need property " + prop.get<std::string>());
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::size_t utf8_length(const std::string &s)
|
|
||||||
{
|
|
||||||
size_t len = 0;
|
|
||||||
for (const unsigned char &c : s)
|
|
||||||
if ((c & 0xc0) != 0x80)
|
|
||||||
len++;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void json_validator::validate_string(const json &instance, const json &schema, const std::string &name)
|
|
||||||
{
|
|
||||||
validate_type(schema, "string", name);
|
|
||||||
|
|
||||||
// minLength
|
|
||||||
auto attr = schema.find("minLength");
|
|
||||||
if (attr != schema.end())
|
|
||||||
if (utf8_length(instance) < attr.value().get<size_t>()) {
|
|
||||||
std::ostringstream s;
|
|
||||||
s << "'" << name << "' of value '" << instance << "' is too short as per minLength ("
|
|
||||||
<< attr.value() << ")";
|
|
||||||
throw std::out_of_range(s.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxLength
|
|
||||||
attr = schema.find("maxLength");
|
|
||||||
if (attr != schema.end())
|
|
||||||
if (utf8_length(instance) > attr.value().get<size_t>()) {
|
|
||||||
std::ostringstream s;
|
|
||||||
s << "'" << name << "' of value '" << instance << "' is too long as per maxLength ("
|
|
||||||
<< attr.value() << ")";
|
|
||||||
throw std::out_of_range(s.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NO_STD_REGEX
|
|
||||||
// pattern
|
|
||||||
attr = schema.find("pattern");
|
|
||||||
if (attr != schema.end()) {
|
|
||||||
REGEX_NAMESPACE::regex re(attr.value().get<std::string>(), REGEX_NAMESPACE::regex::ECMAScript);
|
|
||||||
if (!REGEX_NAMESPACE::regex_search(instance.get<std::string>(), re))
|
|
||||||
throw std::invalid_argument(instance.get<std::string>() + " does not match regex pattern: " + attr.value().get<std::string>() + " for " + name);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// format
|
|
||||||
attr = schema.find("format");
|
|
||||||
if (attr != schema.end()) {
|
|
||||||
if (format_check_ == nullptr)
|
|
||||||
throw std::logic_error("A format checker was not provided but a format-attribute for this string is present. " +
|
|
||||||
name + " cannot be validated for " + attr.value().get<std::string>());
|
|
||||||
format_check_(attr.value(), instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
788
json11.cpp
788
json11.cpp
@ -1,788 +0,0 @@
|
|||||||
/* Copyright (c) 2013 Dropbox, Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "json11.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
namespace json11 {
|
|
||||||
|
|
||||||
static const int max_depth = 200;
|
|
||||||
|
|
||||||
using std::string;
|
|
||||||
using std::vector;
|
|
||||||
using std::map;
|
|
||||||
using std::make_shared;
|
|
||||||
using std::initializer_list;
|
|
||||||
using std::move;
|
|
||||||
|
|
||||||
/* Helper for representing null - just a do-nothing struct, plus comparison
|
|
||||||
* operators so the helpers in JsonValue work. We can't use nullptr_t because
|
|
||||||
* it may not be orderable.
|
|
||||||
*/
|
|
||||||
struct NullStruct {
|
|
||||||
bool operator==(NullStruct) const { return true; }
|
|
||||||
bool operator<(NullStruct) const { return false; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Serialization
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void dump(NullStruct, string &out) {
|
|
||||||
out += "null";
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump(double value, string &out) {
|
|
||||||
if (std::isfinite(value)) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof buf, "%.17g", value);
|
|
||||||
out += buf;
|
|
||||||
} else {
|
|
||||||
out += "null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump(int value, string &out) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof buf, "%d", value);
|
|
||||||
out += buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump(bool value, string &out) {
|
|
||||||
out += value ? "true" : "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump(const string &value, string &out) {
|
|
||||||
out += '"';
|
|
||||||
for (size_t i = 0; i < value.length(); i++) {
|
|
||||||
const char ch = value[i];
|
|
||||||
if (ch == '\\') {
|
|
||||||
out += "\\\\";
|
|
||||||
} else if (ch == '"') {
|
|
||||||
out += "\\\"";
|
|
||||||
} else if (ch == '\b') {
|
|
||||||
out += "\\b";
|
|
||||||
} else if (ch == '\f') {
|
|
||||||
out += "\\f";
|
|
||||||
} else if (ch == '\n') {
|
|
||||||
out += "\\n";
|
|
||||||
} else if (ch == '\r') {
|
|
||||||
out += "\\r";
|
|
||||||
} else if (ch == '\t') {
|
|
||||||
out += "\\t";
|
|
||||||
} else if (static_cast<uint8_t>(ch) <= 0x1f) {
|
|
||||||
char buf[8];
|
|
||||||
snprintf(buf, sizeof buf, "\\u%04x", ch);
|
|
||||||
out += buf;
|
|
||||||
} else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
|
|
||||||
&& static_cast<uint8_t>(value[i+2]) == 0xa8) {
|
|
||||||
out += "\\u2028";
|
|
||||||
i += 2;
|
|
||||||
} else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
|
|
||||||
&& static_cast<uint8_t>(value[i+2]) == 0xa9) {
|
|
||||||
out += "\\u2029";
|
|
||||||
i += 2;
|
|
||||||
} else {
|
|
||||||
out += ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out += '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump(const Json::array &values, string &out) {
|
|
||||||
bool first = true;
|
|
||||||
out += "[";
|
|
||||||
for (const auto &value : values) {
|
|
||||||
if (!first)
|
|
||||||
out += ", ";
|
|
||||||
value.dump(out);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
out += "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump(const Json::object &values, string &out) {
|
|
||||||
bool first = true;
|
|
||||||
out += "{";
|
|
||||||
for (const auto &kv : values) {
|
|
||||||
if (!first)
|
|
||||||
out += ", ";
|
|
||||||
dump(kv.first, out);
|
|
||||||
out += ": ";
|
|
||||||
kv.second.dump(out);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
out += "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Json::dump(string &out) const {
|
|
||||||
m_ptr->dump(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Value wrappers
|
|
||||||
*/
|
|
||||||
|
|
||||||
template <Json::Type tag, typename T>
|
|
||||||
class Value : public JsonValue {
|
|
||||||
protected:
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
explicit Value(const T &value) : m_value(value) {}
|
|
||||||
explicit Value(T &&value) : m_value(move(value)) {}
|
|
||||||
|
|
||||||
// Get type tag
|
|
||||||
Json::Type type() const override {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparisons
|
|
||||||
bool equals(const JsonValue * other) const override {
|
|
||||||
return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
|
|
||||||
}
|
|
||||||
bool less(const JsonValue * other) const override {
|
|
||||||
return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const T m_value;
|
|
||||||
void dump(string &out) const override { json11::dump(m_value, out); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonDouble final : public Value<Json::NUMBER, double> {
|
|
||||||
double number_value() const override { return m_value; }
|
|
||||||
int int_value() const override { return static_cast<int>(m_value); }
|
|
||||||
bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
|
|
||||||
bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
|
|
||||||
public:
|
|
||||||
explicit JsonDouble(double value) : Value(value) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonInt final : public Value<Json::NUMBER, int> {
|
|
||||||
double number_value() const override { return m_value; }
|
|
||||||
int int_value() const override { return m_value; }
|
|
||||||
bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
|
|
||||||
bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
|
|
||||||
public:
|
|
||||||
explicit JsonInt(int value) : Value(value) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonBoolean final : public Value<Json::BOOL, bool> {
|
|
||||||
bool bool_value() const override { return m_value; }
|
|
||||||
public:
|
|
||||||
explicit JsonBoolean(bool value) : Value(value) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonString final : public Value<Json::STRING, string> {
|
|
||||||
const string &string_value() const override { return m_value; }
|
|
||||||
public:
|
|
||||||
explicit JsonString(const string &value) : Value(value) {}
|
|
||||||
explicit JsonString(string &&value) : Value(move(value)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonArray final : public Value<Json::ARRAY, Json::array> {
|
|
||||||
const Json::array &array_items() const override { return m_value; }
|
|
||||||
const Json & operator[](size_t i) const override;
|
|
||||||
public:
|
|
||||||
explicit JsonArray(const Json::array &value) : Value(value) {}
|
|
||||||
explicit JsonArray(Json::array &&value) : Value(move(value)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonObject final : public Value<Json::OBJECT, Json::object> {
|
|
||||||
const Json::object &object_items() const override { return m_value; }
|
|
||||||
const Json & operator[](const string &key) const override;
|
|
||||||
public:
|
|
||||||
explicit JsonObject(const Json::object &value) : Value(value) {}
|
|
||||||
explicit JsonObject(Json::object &&value) : Value(move(value)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonNull final : public Value<Json::NUL, NullStruct> {
|
|
||||||
public:
|
|
||||||
JsonNull() : Value({}) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Static globals - static-init-safe
|
|
||||||
*/
|
|
||||||
struct Statics {
|
|
||||||
const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
|
|
||||||
const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
|
|
||||||
const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
|
|
||||||
const string empty_string;
|
|
||||||
const vector<Json> empty_vector;
|
|
||||||
const map<string, Json> empty_map;
|
|
||||||
Statics() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
static const Statics & statics() {
|
|
||||||
static const Statics s {};
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Json & static_null() {
|
|
||||||
// This has to be separate, not in Statics, because Json() accesses statics().null.
|
|
||||||
static const Json json_null;
|
|
||||||
return json_null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Constructors
|
|
||||||
*/
|
|
||||||
|
|
||||||
Json::Json() noexcept : m_ptr(statics().null) {}
|
|
||||||
Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
|
|
||||||
Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {}
|
|
||||||
Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {}
|
|
||||||
Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
|
|
||||||
Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {}
|
|
||||||
Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {}
|
|
||||||
Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {}
|
|
||||||
Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {}
|
|
||||||
Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {}
|
|
||||||
Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
|
|
||||||
Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Accessors
|
|
||||||
*/
|
|
||||||
|
|
||||||
Json::Type Json::type() const { return m_ptr->type(); }
|
|
||||||
double Json::number_value() const { return m_ptr->number_value(); }
|
|
||||||
int Json::int_value() const { return m_ptr->int_value(); }
|
|
||||||
bool Json::bool_value() const { return m_ptr->bool_value(); }
|
|
||||||
const string & Json::string_value() const { return m_ptr->string_value(); }
|
|
||||||
const vector<Json> & Json::array_items() const { return m_ptr->array_items(); }
|
|
||||||
const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); }
|
|
||||||
const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
|
|
||||||
const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }
|
|
||||||
|
|
||||||
double JsonValue::number_value() const { return 0; }
|
|
||||||
int JsonValue::int_value() const { return 0; }
|
|
||||||
bool JsonValue::bool_value() const { return false; }
|
|
||||||
const string & JsonValue::string_value() const { return statics().empty_string; }
|
|
||||||
const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; }
|
|
||||||
const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; }
|
|
||||||
const Json & JsonValue::operator[] (size_t) const { return static_null(); }
|
|
||||||
const Json & JsonValue::operator[] (const string &) const { return static_null(); }
|
|
||||||
|
|
||||||
const Json & JsonObject::operator[] (const string &key) const {
|
|
||||||
auto iter = m_value.find(key);
|
|
||||||
return (iter == m_value.end()) ? static_null() : iter->second;
|
|
||||||
}
|
|
||||||
const Json & JsonArray::operator[] (size_t i) const {
|
|
||||||
if (i >= m_value.size()) return static_null();
|
|
||||||
else return m_value[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Comparison
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool Json::operator== (const Json &other) const {
|
|
||||||
if (m_ptr == other.m_ptr)
|
|
||||||
return true;
|
|
||||||
if (m_ptr->type() != other.m_ptr->type())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return m_ptr->equals(other.m_ptr.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Json::operator< (const Json &other) const {
|
|
||||||
if (m_ptr == other.m_ptr)
|
|
||||||
return false;
|
|
||||||
if (m_ptr->type() != other.m_ptr->type())
|
|
||||||
return m_ptr->type() < other.m_ptr->type();
|
|
||||||
|
|
||||||
return m_ptr->less(other.m_ptr.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Parsing
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* esc(c)
|
|
||||||
*
|
|
||||||
* Format char c suitable for printing in an error message.
|
|
||||||
*/
|
|
||||||
static inline string esc(char c) {
|
|
||||||
char buf[12];
|
|
||||||
if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
|
|
||||||
snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
|
|
||||||
} else {
|
|
||||||
snprintf(buf, sizeof buf, "(%d)", c);
|
|
||||||
}
|
|
||||||
return string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool in_range(long x, long lower, long upper) {
|
|
||||||
return (x >= lower && x <= upper);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
/* JsonParser
|
|
||||||
*
|
|
||||||
* Object that tracks all state of an in-progress parse.
|
|
||||||
*/
|
|
||||||
struct JsonParser final {
|
|
||||||
|
|
||||||
/* State
|
|
||||||
*/
|
|
||||||
const string &str;
|
|
||||||
size_t i;
|
|
||||||
string &err;
|
|
||||||
bool failed;
|
|
||||||
const JsonParse strategy;
|
|
||||||
|
|
||||||
/* fail(msg, err_ret = Json())
|
|
||||||
*
|
|
||||||
* Mark this parse as failed.
|
|
||||||
*/
|
|
||||||
Json fail(string &&msg) {
|
|
||||||
return fail(move(msg), Json());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T fail(string &&msg, const T err_ret) {
|
|
||||||
if (!failed)
|
|
||||||
err = std::move(msg);
|
|
||||||
failed = true;
|
|
||||||
return err_ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* consume_whitespace()
|
|
||||||
*
|
|
||||||
* Advance until the current character is non-whitespace.
|
|
||||||
*/
|
|
||||||
void consume_whitespace() {
|
|
||||||
while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* consume_comment()
|
|
||||||
*
|
|
||||||
* Advance comments (c-style inline and multiline).
|
|
||||||
*/
|
|
||||||
bool consume_comment() {
|
|
||||||
bool comment_found = false;
|
|
||||||
if (str[i] == '/') {
|
|
||||||
i++;
|
|
||||||
if (i == str.size())
|
|
||||||
return fail("unexpected end of input after start of comment", false);
|
|
||||||
if (str[i] == '/') { // inline comment
|
|
||||||
i++;
|
|
||||||
// advance until next line, or end of input
|
|
||||||
while (i < str.size() && str[i] != '\n') {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
comment_found = true;
|
|
||||||
}
|
|
||||||
else if (str[i] == '*') { // multiline comment
|
|
||||||
i++;
|
|
||||||
if (i > str.size()-2)
|
|
||||||
return fail("unexpected end of input inside multi-line comment", false);
|
|
||||||
// advance until closing tokens
|
|
||||||
while (!(str[i] == '*' && str[i+1] == '/')) {
|
|
||||||
i++;
|
|
||||||
if (i > str.size()-2)
|
|
||||||
return fail(
|
|
||||||
"unexpected end of input inside multi-line comment", false);
|
|
||||||
}
|
|
||||||
i += 2;
|
|
||||||
comment_found = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return fail("malformed comment", false);
|
|
||||||
}
|
|
||||||
return comment_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* consume_garbage()
|
|
||||||
*
|
|
||||||
* Advance until the current character is non-whitespace and non-comment.
|
|
||||||
*/
|
|
||||||
void consume_garbage() {
|
|
||||||
consume_whitespace();
|
|
||||||
if(strategy == JsonParse::COMMENTS) {
|
|
||||||
bool comment_found = false;
|
|
||||||
do {
|
|
||||||
comment_found = consume_comment();
|
|
||||||
if (failed) return;
|
|
||||||
consume_whitespace();
|
|
||||||
}
|
|
||||||
while(comment_found);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* get_next_token()
|
|
||||||
*
|
|
||||||
* Return the next non-whitespace character. If the end of the input is reached,
|
|
||||||
* flag an error and return 0.
|
|
||||||
*/
|
|
||||||
char get_next_token() {
|
|
||||||
consume_garbage();
|
|
||||||
if (failed) return static_cast<char>(0);
|
|
||||||
if (i == str.size())
|
|
||||||
return fail("unexpected end of input", static_cast<char>(0));
|
|
||||||
|
|
||||||
return str[i++];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* encode_utf8(pt, out)
|
|
||||||
*
|
|
||||||
* Encode pt as UTF-8 and add it to out.
|
|
||||||
*/
|
|
||||||
void encode_utf8(long pt, string & out) {
|
|
||||||
if (pt < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (pt < 0x80) {
|
|
||||||
out += static_cast<char>(pt);
|
|
||||||
} else if (pt < 0x800) {
|
|
||||||
out += static_cast<char>((pt >> 6) | 0xC0);
|
|
||||||
out += static_cast<char>((pt & 0x3F) | 0x80);
|
|
||||||
} else if (pt < 0x10000) {
|
|
||||||
out += static_cast<char>((pt >> 12) | 0xE0);
|
|
||||||
out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
|
|
||||||
out += static_cast<char>((pt & 0x3F) | 0x80);
|
|
||||||
} else {
|
|
||||||
out += static_cast<char>((pt >> 18) | 0xF0);
|
|
||||||
out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
|
|
||||||
out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
|
|
||||||
out += static_cast<char>((pt & 0x3F) | 0x80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* parse_string()
|
|
||||||
*
|
|
||||||
* Parse a string, starting at the current position.
|
|
||||||
*/
|
|
||||||
string parse_string() {
|
|
||||||
string out;
|
|
||||||
long last_escaped_codepoint = -1;
|
|
||||||
while (true) {
|
|
||||||
if (i == str.size())
|
|
||||||
return fail("unexpected end of input in string", "");
|
|
||||||
|
|
||||||
char ch = str[i++];
|
|
||||||
|
|
||||||
if (ch == '"') {
|
|
||||||
encode_utf8(last_escaped_codepoint, out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_range(ch, 0, 0x1f))
|
|
||||||
return fail("unescaped " + esc(ch) + " in string", "");
|
|
||||||
|
|
||||||
// The usual case: non-escaped characters
|
|
||||||
if (ch != '\\') {
|
|
||||||
encode_utf8(last_escaped_codepoint, out);
|
|
||||||
last_escaped_codepoint = -1;
|
|
||||||
out += ch;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle escapes
|
|
||||||
if (i == str.size())
|
|
||||||
return fail("unexpected end of input in string", "");
|
|
||||||
|
|
||||||
ch = str[i++];
|
|
||||||
|
|
||||||
if (ch == 'u') {
|
|
||||||
// Extract 4-byte escape sequence
|
|
||||||
string esc = str.substr(i, 4);
|
|
||||||
// Explicitly check length of the substring. The following loop
|
|
||||||
// relies on std::string returning the terminating NUL when
|
|
||||||
// accessing str[length]. Checking here reduces brittleness.
|
|
||||||
if (esc.length() < 4) {
|
|
||||||
return fail("bad \\u escape: " + esc, "");
|
|
||||||
}
|
|
||||||
for (size_t j = 0; j < 4; j++) {
|
|
||||||
if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
|
|
||||||
&& !in_range(esc[j], '0', '9'))
|
|
||||||
return fail("bad \\u escape: " + esc, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
long codepoint = strtol(esc.data(), nullptr, 16);
|
|
||||||
|
|
||||||
// JSON specifies that characters outside the BMP shall be encoded as a pair
|
|
||||||
// of 4-hex-digit \u escapes encoding their surrogate pair components. Check
|
|
||||||
// whether we're in the middle of such a beast: the previous codepoint was an
|
|
||||||
// escaped lead (high) surrogate, and this is a trail (low) surrogate.
|
|
||||||
if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
|
|
||||||
&& in_range(codepoint, 0xDC00, 0xDFFF)) {
|
|
||||||
// Reassemble the two surrogate pairs into one astral-plane character, per
|
|
||||||
// the UTF-16 algorithm.
|
|
||||||
encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
|
|
||||||
| (codepoint - 0xDC00)) + 0x10000, out);
|
|
||||||
last_escaped_codepoint = -1;
|
|
||||||
} else {
|
|
||||||
encode_utf8(last_escaped_codepoint, out);
|
|
||||||
last_escaped_codepoint = codepoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 4;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
encode_utf8(last_escaped_codepoint, out);
|
|
||||||
last_escaped_codepoint = -1;
|
|
||||||
|
|
||||||
if (ch == 'b') {
|
|
||||||
out += '\b';
|
|
||||||
} else if (ch == 'f') {
|
|
||||||
out += '\f';
|
|
||||||
} else if (ch == 'n') {
|
|
||||||
out += '\n';
|
|
||||||
} else if (ch == 'r') {
|
|
||||||
out += '\r';
|
|
||||||
} else if (ch == 't') {
|
|
||||||
out += '\t';
|
|
||||||
} else if (ch == '"' || ch == '\\' || ch == '/') {
|
|
||||||
out += ch;
|
|
||||||
} else {
|
|
||||||
return fail("invalid escape character " + esc(ch), "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* parse_number()
|
|
||||||
*
|
|
||||||
* Parse a double.
|
|
||||||
*/
|
|
||||||
Json parse_number() {
|
|
||||||
size_t start_pos = i;
|
|
||||||
|
|
||||||
if (str[i] == '-')
|
|
||||||
i++;
|
|
||||||
|
|
||||||
// Integer part
|
|
||||||
if (str[i] == '0') {
|
|
||||||
i++;
|
|
||||||
if (in_range(str[i], '0', '9'))
|
|
||||||
return fail("leading 0s not permitted in numbers");
|
|
||||||
} else if (in_range(str[i], '1', '9')) {
|
|
||||||
i++;
|
|
||||||
while (in_range(str[i], '0', '9'))
|
|
||||||
i++;
|
|
||||||
} else {
|
|
||||||
return fail("invalid " + esc(str[i]) + " in number");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
|
|
||||||
&& (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
|
|
||||||
return std::atoi(str.c_str() + start_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decimal part
|
|
||||||
if (str[i] == '.') {
|
|
||||||
i++;
|
|
||||||
if (!in_range(str[i], '0', '9'))
|
|
||||||
return fail("at least one digit required in fractional part");
|
|
||||||
|
|
||||||
while (in_range(str[i], '0', '9'))
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exponent part
|
|
||||||
if (str[i] == 'e' || str[i] == 'E') {
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (str[i] == '+' || str[i] == '-')
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (!in_range(str[i], '0', '9'))
|
|
||||||
return fail("at least one digit required in exponent");
|
|
||||||
|
|
||||||
while (in_range(str[i], '0', '9'))
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::strtod(str.c_str() + start_pos, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expect(str, res)
|
|
||||||
*
|
|
||||||
* Expect that 'str' starts at the character that was just read. If it does, advance
|
|
||||||
* the input and return res. If not, flag an error.
|
|
||||||
*/
|
|
||||||
Json expect(const string &expected, Json res) {
|
|
||||||
assert(i != 0);
|
|
||||||
i--;
|
|
||||||
if (str.compare(i, expected.length(), expected) == 0) {
|
|
||||||
i += expected.length();
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* parse_json()
|
|
||||||
*
|
|
||||||
* Parse a JSON object.
|
|
||||||
*/
|
|
||||||
Json parse_json(int depth) {
|
|
||||||
if (depth > max_depth) {
|
|
||||||
return fail("exceeded maximum nesting depth");
|
|
||||||
}
|
|
||||||
|
|
||||||
char ch = get_next_token();
|
|
||||||
if (failed)
|
|
||||||
return Json();
|
|
||||||
|
|
||||||
if (ch == '-' || (ch >= '0' && ch <= '9')) {
|
|
||||||
i--;
|
|
||||||
return parse_number();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == 't')
|
|
||||||
return expect("true", true);
|
|
||||||
|
|
||||||
if (ch == 'f')
|
|
||||||
return expect("false", false);
|
|
||||||
|
|
||||||
if (ch == 'n')
|
|
||||||
return expect("null", Json());
|
|
||||||
|
|
||||||
if (ch == '"')
|
|
||||||
return parse_string();
|
|
||||||
|
|
||||||
if (ch == '{') {
|
|
||||||
map<string, Json> data;
|
|
||||||
ch = get_next_token();
|
|
||||||
if (ch == '}')
|
|
||||||
return data;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
if (ch != '"')
|
|
||||||
return fail("expected '\"' in object, got " + esc(ch));
|
|
||||||
|
|
||||||
string key = parse_string();
|
|
||||||
if (failed)
|
|
||||||
return Json();
|
|
||||||
|
|
||||||
ch = get_next_token();
|
|
||||||
if (ch != ':')
|
|
||||||
return fail("expected ':' in object, got " + esc(ch));
|
|
||||||
|
|
||||||
data[std::move(key)] = parse_json(depth + 1);
|
|
||||||
if (failed)
|
|
||||||
return Json();
|
|
||||||
|
|
||||||
ch = get_next_token();
|
|
||||||
if (ch == '}')
|
|
||||||
break;
|
|
||||||
if (ch != ',')
|
|
||||||
return fail("expected ',' in object, got " + esc(ch));
|
|
||||||
|
|
||||||
ch = get_next_token();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == '[') {
|
|
||||||
vector<Json> data;
|
|
||||||
ch = get_next_token();
|
|
||||||
if (ch == ']')
|
|
||||||
return data;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
i--;
|
|
||||||
data.push_back(parse_json(depth + 1));
|
|
||||||
if (failed)
|
|
||||||
return Json();
|
|
||||||
|
|
||||||
ch = get_next_token();
|
|
||||||
if (ch == ']')
|
|
||||||
break;
|
|
||||||
if (ch != ',')
|
|
||||||
return fail("expected ',' in list, got " + esc(ch));
|
|
||||||
|
|
||||||
ch = get_next_token();
|
|
||||||
(void)ch;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fail("expected value, got " + esc(ch));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}//namespace {
|
|
||||||
|
|
||||||
Json Json::parse(const string &in, string &err, JsonParse strategy) {
|
|
||||||
JsonParser parser { in, 0, err, false, strategy };
|
|
||||||
Json result = parser.parse_json(0);
|
|
||||||
|
|
||||||
// Check for any trailing garbage
|
|
||||||
parser.consume_garbage();
|
|
||||||
if (parser.failed)
|
|
||||||
return Json();
|
|
||||||
if (parser.i != in.size())
|
|
||||||
return parser.fail("unexpected trailing " + esc(in[parser.i]));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Documented in json11.hpp
|
|
||||||
vector<Json> Json::parse_multi(const string &in,
|
|
||||||
std::string::size_type &parser_stop_pos,
|
|
||||||
string &err,
|
|
||||||
JsonParse strategy) {
|
|
||||||
JsonParser parser { in, 0, err, false, strategy };
|
|
||||||
parser_stop_pos = 0;
|
|
||||||
vector<Json> json_vec;
|
|
||||||
while (parser.i != in.size() && !parser.failed) {
|
|
||||||
json_vec.push_back(parser.parse_json(0));
|
|
||||||
if (parser.failed)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Check for another object
|
|
||||||
parser.consume_garbage();
|
|
||||||
if (parser.failed)
|
|
||||||
break;
|
|
||||||
parser_stop_pos = parser.i;
|
|
||||||
}
|
|
||||||
return json_vec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Shape-checking
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool Json::has_shape(const shape & types, string & err) const {
|
|
||||||
if (!is_object()) {
|
|
||||||
err = "expected JSON object, got " + dump();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto & item : types) {
|
|
||||||
if ((*this)[item.first].type() != item.second) {
|
|
||||||
err = "bad type for " + item.first + " in " + dump();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace json11
|
|
232
json11.hpp
232
json11.hpp
@ -1,232 +0,0 @@
|
|||||||
/* json11
|
|
||||||
*
|
|
||||||
* json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
|
|
||||||
*
|
|
||||||
* The core object provided by the library is json11::Json. A Json object represents any JSON
|
|
||||||
* value: null, bool, number (int or double), string (std::string), array (std::vector), or
|
|
||||||
* object (std::map).
|
|
||||||
*
|
|
||||||
* Json objects act like values: they can be assigned, copied, moved, compared for equality or
|
|
||||||
* order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
|
|
||||||
* Json::parse (static) to parse a std::string as a Json object.
|
|
||||||
*
|
|
||||||
* Internally, the various types of Json object are represented by the JsonValue class
|
|
||||||
* hierarchy.
|
|
||||||
*
|
|
||||||
* A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
|
|
||||||
* so some JSON implementations distinguish between integers and floating-point numbers, while
|
|
||||||
* some don't. In json11, we choose the latter. Because some JSON implementations (namely
|
|
||||||
* Javascript itself) treat all numbers as the same type, distinguishing the two leads
|
|
||||||
* to JSON that will be *silently* changed by a round-trip through those implementations.
|
|
||||||
* Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
|
|
||||||
* provides integer helpers.
|
|
||||||
*
|
|
||||||
* Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
|
|
||||||
* range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
|
|
||||||
* or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
|
|
||||||
* will be exact for +/- 275 years.)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Copyright (c) 2013 Dropbox, Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <initializer_list>
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#if _MSC_VER <= 1800 // VS 2013
|
|
||||||
#ifndef noexcept
|
|
||||||
#define noexcept throw()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef snprintf
|
|
||||||
#define snprintf _snprintf_s
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace json11 {
|
|
||||||
|
|
||||||
enum JsonParse {
|
|
||||||
STANDARD, COMMENTS
|
|
||||||
};
|
|
||||||
|
|
||||||
class JsonValue;
|
|
||||||
|
|
||||||
class Json final {
|
|
||||||
public:
|
|
||||||
// Types
|
|
||||||
enum Type {
|
|
||||||
NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
|
|
||||||
};
|
|
||||||
|
|
||||||
// Array and object typedefs
|
|
||||||
typedef std::vector<Json> array;
|
|
||||||
typedef std::map<std::string, Json> object;
|
|
||||||
|
|
||||||
// Constructors for the various types of JSON value.
|
|
||||||
Json() noexcept; // NUL
|
|
||||||
Json(std::nullptr_t) noexcept; // NUL
|
|
||||||
Json(double value); // NUMBER
|
|
||||||
Json(int value); // NUMBER
|
|
||||||
Json(bool value); // BOOL
|
|
||||||
Json(const std::string &value); // STRING
|
|
||||||
Json(std::string &&value); // STRING
|
|
||||||
Json(const char * value); // STRING
|
|
||||||
Json(const array &values); // ARRAY
|
|
||||||
Json(array &&values); // ARRAY
|
|
||||||
Json(const object &values); // OBJECT
|
|
||||||
Json(object &&values); // OBJECT
|
|
||||||
|
|
||||||
// Implicit constructor: anything with a to_json() function.
|
|
||||||
template <class T, class = decltype(&T::to_json)>
|
|
||||||
Json(const T & t) : Json(t.to_json()) {}
|
|
||||||
|
|
||||||
// Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
|
|
||||||
template <class M, typename std::enable_if<
|
|
||||||
std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value
|
|
||||||
&& std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value,
|
|
||||||
int>::type = 0>
|
|
||||||
Json(const M & m) : Json(object(m.begin(), m.end())) {}
|
|
||||||
|
|
||||||
// Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
|
|
||||||
template <class V, typename std::enable_if<
|
|
||||||
std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value,
|
|
||||||
int>::type = 0>
|
|
||||||
Json(const V & v) : Json(array(v.begin(), v.end())) {}
|
|
||||||
|
|
||||||
// This prevents Json(some_pointer) from accidentally producing a bool. Use
|
|
||||||
// Json(bool(some_pointer)) if that behavior is desired.
|
|
||||||
Json(void *) = delete;
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
Type type() const;
|
|
||||||
|
|
||||||
bool is_null() const { return type() == NUL; }
|
|
||||||
bool is_number() const { return type() == NUMBER; }
|
|
||||||
bool is_bool() const { return type() == BOOL; }
|
|
||||||
bool is_string() const { return type() == STRING; }
|
|
||||||
bool is_array() const { return type() == ARRAY; }
|
|
||||||
bool is_object() const { return type() == OBJECT; }
|
|
||||||
|
|
||||||
// Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
|
|
||||||
// distinguish between integer and non-integer numbers - number_value() and int_value()
|
|
||||||
// can both be applied to a NUMBER-typed object.
|
|
||||||
double number_value() const;
|
|
||||||
int int_value() const;
|
|
||||||
|
|
||||||
// Return the enclosed value if this is a boolean, false otherwise.
|
|
||||||
bool bool_value() const;
|
|
||||||
// Return the enclosed string if this is a string, "" otherwise.
|
|
||||||
const std::string &string_value() const;
|
|
||||||
// Return the enclosed std::vector if this is an array, or an empty vector otherwise.
|
|
||||||
const array &array_items() const;
|
|
||||||
// Return the enclosed std::map if this is an object, or an empty map otherwise.
|
|
||||||
const object &object_items() const;
|
|
||||||
|
|
||||||
// Return a reference to arr[i] if this is an array, Json() otherwise.
|
|
||||||
const Json & operator[](size_t i) const;
|
|
||||||
// Return a reference to obj[key] if this is an object, Json() otherwise.
|
|
||||||
const Json & operator[](const std::string &key) const;
|
|
||||||
|
|
||||||
// Serialize.
|
|
||||||
void dump(std::string &out) const;
|
|
||||||
std::string dump() const {
|
|
||||||
std::string out;
|
|
||||||
dump(out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse. If parse fails, return Json() and assign an error message to err.
|
|
||||||
static Json parse(const std::string & in,
|
|
||||||
std::string & err,
|
|
||||||
JsonParse strategy = JsonParse::STANDARD);
|
|
||||||
static Json parse(const char * in,
|
|
||||||
std::string & err,
|
|
||||||
JsonParse strategy = JsonParse::STANDARD) {
|
|
||||||
if (in) {
|
|
||||||
return parse(std::string(in), err, strategy);
|
|
||||||
} else {
|
|
||||||
err = "null input";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Parse multiple objects, concatenated or separated by whitespace
|
|
||||||
static std::vector<Json> parse_multi(
|
|
||||||
const std::string & in,
|
|
||||||
std::string::size_type & parser_stop_pos,
|
|
||||||
std::string & err,
|
|
||||||
JsonParse strategy = JsonParse::STANDARD);
|
|
||||||
|
|
||||||
static inline std::vector<Json> parse_multi(
|
|
||||||
const std::string & in,
|
|
||||||
std::string & err,
|
|
||||||
JsonParse strategy = JsonParse::STANDARD) {
|
|
||||||
std::string::size_type parser_stop_pos;
|
|
||||||
return parse_multi(in, parser_stop_pos, err, strategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator== (const Json &rhs) const;
|
|
||||||
bool operator< (const Json &rhs) const;
|
|
||||||
bool operator!= (const Json &rhs) const { return !(*this == rhs); }
|
|
||||||
bool operator<= (const Json &rhs) const { return !(rhs < *this); }
|
|
||||||
bool operator> (const Json &rhs) const { return (rhs < *this); }
|
|
||||||
bool operator>= (const Json &rhs) const { return !(*this < rhs); }
|
|
||||||
|
|
||||||
/* has_shape(types, err)
|
|
||||||
*
|
|
||||||
* Return true if this is a JSON object and, for each item in types, has a field of
|
|
||||||
* the given type. If not, return false and set err to a descriptive message.
|
|
||||||
*/
|
|
||||||
typedef std::initializer_list<std::pair<std::string, Type>> shape;
|
|
||||||
bool has_shape(const shape & types, std::string & err) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<JsonValue> m_ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
|
|
||||||
class JsonValue {
|
|
||||||
protected:
|
|
||||||
friend class Json;
|
|
||||||
friend class JsonInt;
|
|
||||||
friend class JsonDouble;
|
|
||||||
virtual Json::Type type() const = 0;
|
|
||||||
virtual bool equals(const JsonValue * other) const = 0;
|
|
||||||
virtual bool less(const JsonValue * other) const = 0;
|
|
||||||
virtual void dump(std::string &out) const = 0;
|
|
||||||
virtual double number_value() const;
|
|
||||||
virtual int int_value() const;
|
|
||||||
virtual bool bool_value() const;
|
|
||||||
virtual const std::string &string_value() const;
|
|
||||||
virtual const Json::array &array_items() const;
|
|
||||||
virtual const Json &operator[](size_t i) const;
|
|
||||||
virtual const Json::object &object_items() const;
|
|
||||||
virtual const Json &operator[](const std::string &key) const;
|
|
||||||
virtual ~JsonValue() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace json11
|
|
@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
all: ../tiny_gltf.h
|
all: ../tiny_gltf.h
|
||||||
clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc
|
clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc
|
||||||
clang++ -DTINYGLTF_NOEXCEPTION -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc
|
# clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc
|
||||||
|
1042
tiny_gltf.h
1042
tiny_gltf.h
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user