danielaparker / jsoncons

A C++, header-only library for constructing JSON and JSON-like data formats, with JSON Pointer, JSON Patch, JSON Schema, JSONPath, JMESPath, CSV, MessagePack, CBOR, BSON, UBJSON
https://danielaparker.github.io/jsoncons
Other
697 stars 160 forks source link

Support `nlohmann-json` as JSON backend for schemas #516

Closed alexdewar closed 2 months ago

alexdewar commented 3 months ago

I'm currently working on a project which uses the nlohmann-json library for JSON parsing. It seems like it could be possible to support this as a backend for the JSON schema module but it currently doesn't work:

[1/6] Building CXX object src/HealthGPS.Data...es/HealthGPS.Datastore.dir/datamanager.cpp.o
FAILED: src/HealthGPS.Datastore/CMakeFiles/HealthGPS.Datastore.dir/datamanager.cpp.o 
/usr/lib/ccache/bin/c++ -DTBB_USE_ASSERT -I/home/alex/code/healthgps/src -isystem /home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include -g -std=c++20 -Wall -Wextra -Wpedantic -MD -MT src/HealthGPS.Datastore/CMakeFiles/HealthGPS.Datastore.dir/datamanager.cpp.o -MF src/HealthGPS.Datastore/CMakeFiles/HealthGPS.Datastore.dir/datamanager.cpp.o.d -o src/HealthGPS.Datastore/CMakeFiles/HealthGPS.Datastore.dir/datamanager.cpp.o -c /home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp
/home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp: In constructor ‘hgps::data::DataManager::DataManager(std::filesystem::__cxx11::path, hgps::core::VerboseMode)’:
/home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp:88:53: error: no matching function for call to ‘make_json_schema(nlohmann::json_abi_v3_11_3::basic_json<>, const hgps::data::DataManager::DataManager(std::filesystem::__cxx11::path, hgps::core::VerboseMode)::<lambda(const auto:47&)>&)’
   88 |     const auto schema = jsonschema::make_json_schema(nlohmann::json::parse(ifs), resolver);
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/jsonschema.hpp:10,
                 from /home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp:9:
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:211:5: note: candidate: ‘template<class Json, class URIResolver> typename std::enable_if<std::is_same<Json, typename jsoncons::extension_traits::detector<void, void, jsoncons::extension_traits::unary_function_object_t, URIResolver, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::type>::value, jsoncons::jsonschema::json_schema<Json> >::type jsoncons::jsonschema::make_json_schema(Json, const std::string&, const URIResolver&, evaluation_options)’
  211 |     make_json_schema(Json sch, const std::string& retrieval_uri, const URIResolver& resolver,
      |     ^~~~~~~~~~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:211:5: note:   candidate expects 3 arguments, 2 provided
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:227:23: note: candidate: ‘template<class Json> jsoncons::jsonschema::json_schema<Json> jsoncons::jsonschema::make_json_schema(Json, const std::string&, evaluation_options)’
  227 |     json_schema<Json> make_json_schema(Json sch, const std::string& retrieval_uri,
      |                       ^~~~~~~~~~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:227:23: note:   template argument deduction/substitution failed:
/home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp:88:82: note:   cannot convert ‘resolver’ (type ‘const hgps::data::DataManager::DataManager(std::filesystem::__cxx11::path, hgps::core::VerboseMode)::<lambda(const auto:47&)>’) to type ‘const std::string&’ {aka ‘const std::__cxx11::basic_string<char>&’}
   88 |     const auto schema = jsonschema::make_json_schema(nlohmann::json::parse(ifs), resolver);
      |                                                                                  ^~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:244:5: note: candidate: ‘template<class Json, class URIResolver> typename std::enable_if<std::is_same<Json, typename jsoncons::extension_traits::detector<void, void, jsoncons::extension_traits::unary_function_object_t, URIResolver, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::type>::value, jsoncons::jsonschema::json_schema<Json> >::type jsoncons::jsonschema::make_json_schema(Json, const URIResolver&, evaluation_options)’
  244 |     make_json_schema(Json sch, const URIResolver& resolver,
      |     ^~~~~~~~~~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:244:5: note:   template argument deduction/substitution failed:
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp: In substitution of ‘template<class Json, class URIResolver> typename std::enable_if<std::is_same<Json, typename jsoncons::extension_traits::detector<void, void, jsoncons::extension_traits::unary_function_object_t, URIResolver, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::type>::value, jsoncons::jsonschema::json_schema<Json> >::type jsoncons::jsonschema::make_json_schema(Json, const URIResolver&, evaluation_options) [with Json = nlohmann::json_abi_v3_11_3::basic_json<>; URIResolver = hgps::data::DataManager::DataManager(std::filesystem::__cxx11::path, hgps::core::VerboseMode)::<lambda(const auto:47&)>]’:
/home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp:88:53:   required from here
   88 |     const auto schema = jsonschema::make_json_schema(nlohmann::json::parse(ifs), resolver);
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:244:5: error: no type named ‘type’ in ‘struct std::enable_if<false, jsoncons::jsonschema::json_schema<nlohmann::json_abi_v3_11_3::basic_json<> > >’
  244 |     make_json_schema(Json sch, const URIResolver& resolver,
      |     ^~~~~~~~~~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:260:23: note: candidate: ‘template<class Json> jsoncons::jsonschema::json_schema<Json> jsoncons::jsonschema::make_json_schema(Json, evaluation_options)’
  260 |     json_schema<Json> make_json_schema(Json sch,
      |                       ^~~~~~~~~~~~~~~~
/home/alex/code/healthgps/out/build/linux-debug/vcpkg_installed/x64-linux/include/jsoncons_ext/jsonschema/json_schema_factory.hpp:260:23: note:   template argument deduction/substitution failed:
/home/alex/code/healthgps/src/HealthGPS.Datastore/datamanager.cpp:88:82: note:   cannot convert ‘resolver’ (type ‘const hgps::data::DataManager::DataManager(std::filesystem::__cxx11::path, hgps::core::VerboseMode)::<lambda(const auto:47&)>’) to type ‘jsoncons::jsonschema::evaluation_options’
   88 |     const auto schema = jsonschema::make_json_schema(nlohmann::json::parse(ifs), resolver);
      |                                                                                  ^~~~~~~~
ninja: build stopped: subcommand failed.
danielaparker commented 3 months ago

You're brave :-)

While the jsonschema library depends largely on the type requirements of the template parameter Json, and not specifically on the implementation details of jsoncons::basic_json, jsoncons::basic_json and nlohmann::basic_json have different type properties. They have some similarities where they're both following the standard library, e.g. contains(), size(), at(), find(), but there is nothing in the standard library that's like a container that supports both a vector-like and map-like interface, and here jsoncons and nlohmann take different approaches.

To make this work, you'd have to write an adaptor that adapts a nlohmann::basic_json to the type requirements of the jsonschema library, but that would be hard, as we haven't formally defined what those type requirements are. At some point I want to be able to use the library with a different, read-only, representation of JSON (which allows for very fast parsing), so I am thinking about this. But for now I don't think it's realistic. I think if you do want to use this library for schema validation only, you need to separate the functionality, and you're input will have to be text, not a nlohmann::basic_json.

alexdewar commented 3 months ago

Thanks for the speedy response!

I guess I'll just read the input file separately with jsoncons for the validation step for now then. Maybe in future I'll migrate to using jsoncons everywhere else too.

danielaparker commented 3 months ago

I guess I'll just read the input file separately with jsoncons for the validation step

That would definitely be my recommendation, hide the validation code in a cpp file, provide a function interface that accepts text or a stream, and don't let jsoncons leak into the rest of your application.