diff --git a/.vscode/settings.json b/.vscode/settings.json index ee537ea15..9e2dffc5f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -154,5 +154,6 @@ "modernize-*", "-modernize-use-nodiscard", "-modernize-use-trailing-return-type" - ] + ], + "python.pythonPath": "/usr/local/bin/python3" } diff --git a/docs/index.md b/docs/index.md index 0d222ceab..f8ea1030a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,7 +40,9 @@ See each header file for documentation. scattering, multiple importance sampling - [Yocto/ModelIO](yocto/yocto_modelio.md): parsing and writing for Ply/Obj/Pbrt formats -- [Yocto/CommonIO](yocto/yocto_commonio.md): printing utilities, file io utilities, +- [Yocto/CommonIO](yocto/yocto_commonio.md): JSON data type, json io utilities, + command line parsing +- [Yocto/JSON](yocto/yocto_json.md): printing utilities, file io utilities, command line parsing - [Yocto/Common](yocto/yocto_common.md): container and iterator utilities - [Yocto/Parallel](yocto/yocto_parallel.md): concurrency utilities diff --git a/docs/yocto/yocto_json.md b/docs/yocto/yocto_json.md new file mode 100644 index 000000000..46598b99b --- /dev/null +++ b/docs/yocto/yocto_json.md @@ -0,0 +1,12 @@ +# Yocto/JSON: Utilities for manipulating JSON data + +Yocto/JSON is an implementation of a utilities for hanlding JSON data, +including a Json variant data type, loading, saving and formatting JSON, +and a parser for command line arguments to JSON. +Compared to other libraries, it is more lightweight and provides a common +implementation for the rest of Yocto/GL. +Yocto/JSON is implemented in `yocto_json.h` and `yocto_json.cpp`, and +depends on `json.hpp`. + +**This library is experimental** and will be documented appropriately when +the code reaches stability. diff --git a/libs/yocto/CMakeLists.txt b/libs/yocto/CMakeLists.txt index 5a10dc71f..e43b61b2d 100644 --- a/libs/yocto/CMakeLists.txt +++ b/libs/yocto/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(yocto yocto_trace.h yocto_trace.cpp yocto_sceneio.h yocto_sceneio.cpp yocto_commonio.h yocto_commonio.cpp + yocto_json.h yocto_json.cpp ext/stb_image.h ext/stb_image_resize.h ext/stb_image_write.h ext/stb_image.cpp ext/cgltf.h ext/cgltf_write.h ext/cgltf.cpp ext/json.hpp diff --git a/libs/yocto/yocto_commonio.cpp b/libs/yocto/yocto_commonio.cpp index 5bbfc46bf..71f14e1d8 100644 --- a/libs/yocto/yocto_commonio.cpp +++ b/libs/yocto/yocto_commonio.cpp @@ -41,7 +41,7 @@ #include #include -#include "ext/json.hpp" +#include "yocto_json.h" // ----------------------------------------------------------------------------- // USING DIRECTIVES @@ -392,1004 +392,11 @@ string path_current() { return std::filesystem::current_path().u8string(); } } // namespace yocto -// ----------------------------------------------------------------------------- -// IMPLEMENTATION OF JSON SUPPORT -// ----------------------------------------------------------------------------- -namespace yocto { - -#define YOCTO_JSON_SAX 1 - -using njson = nlohmann::ordered_json; - -// load/save json -bool load_json(const string& filename, njson& js, string& error) { - // error helpers - auto parse_error = [filename, &error]() { - error = filename + ": parse error in json"; - return false; - }; - auto text = ""s; - if (!load_text(filename, text, error)) return false; - try { - js = njson::parse(text); - return true; - } catch (std::exception&) { - return parse_error(); - } -} - -bool save_json(const string& filename, const njson& js, string& error) { - return save_text(filename, js.dump(2), error); -} - -// convert json -void to_json(json_value& js, const njson& njs) { - switch (njs.type()) { - case njson::value_t::null: js = json_value{}; break; - case njson::value_t::number_integer: js = (int64_t)njs; break; - case njson::value_t::number_unsigned: js = (uint64_t)njs; break; - case njson::value_t::number_float: js = (double)njs; break; - case njson::value_t::boolean: js = (bool)njs; break; - case njson::value_t::string: js = (string)njs; break; - case njson::value_t::array: - js = json_array(); - for (auto& ejs : njs) to_json(js.emplace_back(), ejs); - break; - case njson::value_t::object: - js = json_object(); - for (auto& [key, ejs] : njs.items()) to_json(js[key], ejs); - break; - case njson::value_t::binary: - js = json_binary(); - js.get_ref() = njs.get_binary(); - break; - case njson::value_t::discarded: js = json_value{}; break; - } -} - -// convert json -void from_json(const json_value& js, njson& njs) { - switch (js.type()) { - case json_type::null: njs = {}; break; - case json_type::integer: njs = js.get_ref(); break; - case json_type::unsigned_: njs = js.get_ref(); break; - case json_type::real: njs = js.get_ref(); break; - case json_type::boolean: njs = js.get_ref(); break; - case json_type::string_: njs = js.get_ref(); break; - case json_type::array: - njs = njson::array(); - for (auto& ejs : js) from_json(ejs, njs.emplace_back()); - break; - case json_type::object: - njs = njson::object(); - for (auto& [key, ejs] : js.items()) from_json(ejs, njs[key]); - break; - case json_type::binary: - njs = njson::binary({}); - njs.get_binary() = js.get_ref(); - break; - } -} - -#if YOCTO_JSON_SAX == 1 - -// load json -bool load_json(const string& filename, json_value& js, string& error) { - // error helpers - auto parse_error = [filename, &error]() { - error = filename + ": parse error in json"; - return false; - }; - - // sax handler - struct sax_handler { - // stack - yocto::json_value* root = nullptr; - std::vector stack = {}; - std::string current_key; - explicit sax_handler(yocto::json_value* root_) { - *root_ = yocto::json_value{}; - root = root_; - stack.push_back(root); - } - - // get current value - yocto::json_value& next_value() { - if (stack.size() == 1) return *root; - if (stack.back()->is_array()) return (*stack.back()).emplace_back(); - if (stack.back()->is_object()) return (*stack.back())[current_key]; - throw yocto::json_error{"bad json type"}; - } - - // values - bool null() { - next_value() = yocto::json_value{}; - return true; - } - bool boolean(bool value) { - next_value() = value; - return true; - } - bool number_integer(int64_t value) { - next_value() = value; - return true; - } - bool number_unsigned(uint64_t value) { - next_value() = value; - return true; - } - bool number_float(double value, const std::string&) { - next_value() = value; - return true; - } - bool string(std::string& value) { - next_value() = value; - return true; - } - bool binary(std::vector& value) { - next_value() = value; - return true; - } - - // objects - bool start_object(size_t elements) { - next_value() = yocto::json_object{}; - stack.push_back(&next_value()); - return true; - } - bool end_object() { - stack.pop_back(); - return true; - } - bool key(std::string& value) { - current_key = value; - return true; - } - - // arrays - bool start_array(size_t elements) { - next_value() = yocto::json_array{}; - stack.push_back(&next_value()); - return true; - } - bool end_array() { - stack.pop_back(); - return true; - } - - bool parse_error(size_t position, const std::string& last_token, - const nlohmann::detail::exception&) { - return false; - } - }; - - // set up parsing - js = json_value{}; - auto handler = sax_handler{&js}; - - // load text - auto text = ""s; - if (!load_text(filename, text, error)) return false; - - // parse json - if (!njson::sax_parse(text, &handler)) return parse_error(); - return true; -} - -#else - -// load json -bool load_json(const string& filename, json_value& js, string& error) { - // parse json - auto njs = njson{}; - if (!load_json(filename, njs, error)) return false; - - // convert - to_json(js, njs); - return true; -} - -#endif - -// save json -bool save_json(const string& filename, const json_value& js, string& error) { - // convert - auto njs = njson{}; - from_json(js, njs); - - // save - return save_json(filename, njs, error); -} - -// Formats a Json to string -bool format_json(string& text, const json_value& js, string& error) { - // convert - auto njs = njson{}; - from_json(js, njs); - - // save - text = njs.dump(2); - return true; -} -string format_json(const json_value& js) { - auto text = string{}; - auto error = string{}; - if (!format_json(text, js, error)) return ""; - return text; -} - -// Validate a value against a schema -static bool validate_json(const json_value& value, const string& path, - const json_value& schema, vector& errors, size_t max_error) { - // error handling - auto emit_error = [&errors, max_error, &path](const string& message) { - errors.push_back(message + (path.empty() ? ""s : ("at " + path))); - return errors.size() >= max_error; - }; - - // early exit - if (schema.is_boolean() && schema.get()) return true; - if (schema.is_object() && schema.empty()) return true; - - // validate type - if (schema.contains("type") && schema.at("type").is_string()) { - auto& type = schema.at("type").get_ref(); - auto type_ok = (type == "null" && value.is_null()) || - (type == "integer" && value.is_integral()) || - (type == "number" && value.is_number()) || - (type == "boolean" && value.is_boolean()) || - (type == "string" && value.is_string()) || - (type == "array" && value.is_array()) || - (type == "object" && value.is_object()); - if (!type_ok) { - if (!emit_error(type + " expected")) return false; - } - } - if (schema.contains("type") && schema.at("type").is_array()) { - auto type_ok = false; - for (auto& tschema : schema) { - if (type_ok) break; - auto& type = tschema.get_ref(); - type_ok = (type == "null" && value.is_null()) || - (type == "integer" && value.is_integral()) || - (type == "number" && value.is_number()) || - (type == "boolean" && value.is_boolean()) || - (type == "string" && value.is_string()) || - (type == "array" && value.is_array()) || - (type == "object" && value.is_object()); - } - if (!type_ok) { - auto types = ""s; - for (auto& tschema : schema) - types += (types.empty() ? "" : " or") + tschema.get_ref(); - if (!emit_error(types + " expected")) return false; - } - } - - // check range - // TODO(fabio): fix number precision - if (schema.contains("minimum") && value.is_number()) { - if (schema.at("minimum").get() > value.get()) { - if (!emit_error("value out of range")) return false; - } - } - if (schema.contains("maximum") && value.is_number()) { - if (schema.at("maximum").get() > value.get()) { - if (!emit_error("value out of range")) return false; - } - } - if (schema.contains("exclusiveMinimum") && value.is_number()) { - if (schema.at("exclusiveMinimum").get() >= value.get()) { - if (!emit_error("value out of range")) return false; - } - } - if (schema.contains("exclusiveMaximum") && value.is_number()) { - if (schema.at("exclusiveMaximum").get() <= value.get()) { - if (!emit_error("value out of range")) return false; - } - } - - // enum checks - if (schema.contains("enum") && schema.at("enum").is_array()) { - auto found = false; - for (auto& item : schema.at("enum")) { - if (found) break; - if (item.is_string() && value.is_string() && - item.get_ref() == value.get_ref()) - found = true; - if (item.is_integral() && value.is_integral() && - item.get() == value.get()) - found = true; - if (item.is_number() && value.is_number() && - item.get() == value.get()) - found = true; - } - if (!found) { - if (!emit_error("invalid enum")) return false; - } - } - - // size checks - if (schema.contains("minLength") && value.is_string()) { - if (schema.at("minLength").get() > value.get_ref().size()) { - if (!emit_error("size out of range")) return false; - } - } - if (schema.contains("maxLength") && value.is_string()) { - if (schema.at("maxLength").get() < value.get_ref().size()) { - if (!emit_error("size out of range")) return false; - } - } - if (schema.contains("minItems") && value.is_array()) { - if (schema.at("minItems").get() > - value.get_ref().size()) { - if (!emit_error("size out of range")) return false; - } - } - if (schema.contains("maxItems") && value.is_array()) { - if (schema.at("maxItems").get() < - value.get_ref().size()) { - if (!emit_error("size out of range")) return false; - } - } - if (schema.contains("minProperties") && value.is_object()) { - if (schema.at("minProperties").get() > - value.get_ref().size()) { - if (!emit_error("size out of range")) return false; - } - } - if (schema.contains("maxProperties") && value.is_object()) { - if (schema.at("maxProperties").get() < - value.get_ref().size()) { - if (!emit_error("size out of range")) return false; - } - } - - // check array items - if (schema.contains("items") && value.is_object() && - schema.at("items").is_object()) { - auto& items = schema.at("items"); - for (auto idx = (size_t)0; idx < value.size(); idx++) { - if (!validate_json(value.at(idx), path + "/" + std::to_string(idx), items, - errors, max_error)) { - if (errors.size() > max_error) break; - } - } - } - if (schema.contains("items") && value.is_array() && - schema.at("items").is_array()) { - auto& items = schema.at("items").get_ref(); - for (auto idx = (size_t)0; idx < std::min(items.size(), value.size()); - idx++) { - if (!validate_json(value.at(idx), path + "/" + std::to_string(idx), - items.at(idx), errors, max_error)) { - if (errors.size() > max_error) break; - } - } - } - - // check object properties - if (schema.contains("properties") && value.is_object() && - schema.at("properties").is_object()) { - auto& properties = schema.at("properties").get_ref(); - for (auto& [name, property] : properties) { - if (!value.contains(name)) continue; - if (!validate_json( - value.at(name), path + "/" + name, property, errors, max_error)) { - if (errors.size() > max_error) break; - } - } - } - if (schema.contains("additionalProperties") && value.is_object() && - schema.contains("properties") && - schema.at("additionalProperties").is_boolean() && - schema.at("additionalProperties").get() == false) { - auto& properties = schema.at("properties"); - for (auto& [name, item] : value.get_ref()) { - if (properties.contains(name)) { - if (!emit_error("unknown property " + name)) return false; - } - } - } - if (schema.contains("additionalProperties") && value.is_object() && - schema.contains("properties") && - schema.at("additionalProperties").is_object()) { - auto& properties = schema.at("properties"); - for (auto& [name, item] : value.get_ref()) { - if (properties.contains(name)) continue; - if (!validate_json( - item, path + "/" + name, properties, errors, max_error)) { - if (errors.size() > max_error) break; - } - } - } - if (schema.contains("required") && value.is_object() && - schema.at("required").is_array()) { - auto& required = schema.at("required").get_ref(); - for (auto& name_ : required) { - auto& name = name_.get_ref(); - if (!value.contains(name)) { - if (emit_error("missing value for " + name)) return false; - } - } - } - - // done - return false; -} -bool validate_json( - const json_value& value, const json_value& schema, string& error) { - auto errors = vector{}; - if (validate_json(value, "", schema, errors, 1)) return true; - error = errors.at(0); - return false; -} -bool validate_json(const json_value& value, const json_value& schema, - vector& errors, size_t max_errors) { - return validate_json(value, "", schema, errors, max_errors); -} - -// convert json -void to_json(njson& njs, json_cview js) { - switch (get_type(js)) { - case json_type::null: njs = nullptr; break; - case json_type::integer: njs = get_integer(js); break; - case json_type::unsigned_: njs = get_unsigned(js); break; - case json_type::real: njs = get_real(js); break; - case json_type::boolean: njs = get_boolean(js); break; - case json_type::string_: njs = get_string(js); break; - case json_type::array: - njs = njson::array(); - for (auto ejs : iterate_array(js)) to_json(njs.emplace_back(), ejs); - break; - case json_type::object: - njs = njson::object(); - for (auto [key, ejs] : iterate_object(js)) to_json(njs[string{key}], ejs); - break; - case json_type::binary: - njs = njson::binary({}); - get_binary(js, njs.get_binary()); - break; - } -} - -// convert json -void from_json(const njson& njs, json_view js) { - switch (njs.type()) { - case njson::value_t::null: set_null(js); break; - case njson::value_t::number_integer: set_integer(js, (int64_t)njs); break; - case njson::value_t::number_unsigned: set_unsigned(js, njs); break; - case njson::value_t::number_float: set_real(js, njs); break; - case njson::value_t::boolean: set_boolean(js, (bool)njs); break; - case njson::value_t::string: set_string(js, (string)njs); break; - case njson::value_t::array: - set_array(js); - for (auto& ejs : njs) from_json(ejs, append_element(js)); - break; - case njson::value_t::object: - set_object(js); - for (auto& [key, ejs] : njs.items()) - from_json(ejs, insert_element(js, key)); - break; - case njson::value_t::binary: set_binary(js, njs.get_binary()); break; - case njson::value_t::discarded: set_null(js); break; - } -} - -#if YOCTO_JSON_SAX == 1 - -// load json -bool load_json(const string& filename, json_tree& js, string& error) { - // error helpers - auto parse_error = [filename, &error]() { - error = filename + ": parse error in json"; - return false; - }; - - // sax handler - struct sax_handler { - // stack - json_view root; - std::vector stack = {}; - std::string current_key; - explicit sax_handler(json_view root_) : root{root_}, stack{root_} {} - - // get current value - json_view next_value() { - if (stack.size() == 1) return root; - auto& jst = _get_type(stack.back()); - if (jst == json_type::array) return append_element(stack.back()); - if (jst == json_type::object) - return insert_element(stack.back(), current_key); - throw yocto::json_error{"bad json type"}; - } - - // values - bool null() { - set_null(next_value()); - return true; - } - bool boolean(bool value) { - set_boolean(next_value(), value); - return true; - } - bool number_integer(int64_t value) { - set_integer(next_value(), value); - return true; - } - bool number_unsigned(uint64_t value) { - set_unsigned(next_value(), value); - return true; - } - bool number_float(double value, const std::string&) { - set_real(next_value(), value); - return true; - } - bool string(std::string& value) { - set_string(next_value(), value); - return true; - } - bool binary(std::vector& value) { - set_binary(next_value(), value); - return true; - } - - // objects - bool start_object(size_t elements) { - set_object(next_value()); - stack.push_back(next_value()); - return true; - } - bool end_object() { - stack.pop_back(); - return true; - } - bool key(std::string& value) { - current_key = value; - return true; - } - - // arrays - bool start_array(size_t elements) { - set_array(next_value()); - stack.push_back(next_value()); - return true; - } - bool end_array() { - stack.pop_back(); - return true; - } - - bool parse_error(size_t position, const std::string& last_token, - const nlohmann::detail::exception&) { - return false; - } - }; - - // set up parsing - js = json_tree{}; - auto handler = sax_handler{get_root(js)}; - - // load text - auto text = ""s; - if (!load_text(filename, text, error)) return false; - - // parse json - if (!njson::sax_parse(text, &handler)) return parse_error(); - return true; -} - -#else - -// load json -bool load_json(const string& filename, json_tree& js, string& error) { - // parse json - auto njs = njson{}; - if (!load_json(filename, njs, error)) return false; - - // convert - from_json(njs, get_root(js)); - return true; -} - -#endif - -// save json -bool save_json(const string& filename, const json_tree& js, string& error) { - // convert - auto njs = njson{}; - to_json(njs, get_root((json_tree&)js)); - - // save - return save_json(filename, njs, error); -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // IMPLEMENTATION OF COMMAND-LINE PARSING // ----------------------------------------------------------------------------- namespace yocto { -static json_value fix_cli_schema(const json_value& schema) { return schema; } - -static string get_cliusage( - const json_value& schema, const string& app_name, const string& command) { - // helper - auto is_positional = [](const json_value& schema, - const string& name) -> bool { - if (!schema.contains("cli_positional")) return false; - if (!schema.at("cli_positional").is_array()) return false; - for (auto& pname : schema.at("cli_positional")) { - if (pname.is_string() && pname.get_ref() == name) return true; - } - return false; - }; - auto is_required = [](const json_value& schema, const string& name) -> bool { - if (!schema.contains("required")) return false; - if (!schema.at("required").is_array()) return false; - for (auto& pname : schema.at("required")) { - if (pname.is_string() && pname.get_ref() == name) return true; - } - return false; - }; - auto has_commands = [](const json_value& schema) -> bool { - for (auto& [name, property] : schema.at("properties").items()) { - if (property.value("type", "") == "object") return true; - } - return false; - }; - - auto message = string{}; - auto usage_optional = string{}, usage_positional = string{}, - usage_command = string{}; - for (auto& [name, property] : schema.at("properties").items()) { - if (property.value("type", "") == "object") continue; - auto decorated_name = name; - auto positional = is_positional(schema, name); - if (!positional) { - decorated_name = "--" + name; - if (property.value("type", "") == "boolean") - decorated_name += "/--no-" + name; - if (property.contains("cli_alt")) - decorated_name += ", -" + property.value("cli_alt", ""); - } - auto line = " " + decorated_name; - if (property.value("type", "") != "boolean") { - line += " " + property.value("type", ""); - } - while (line.size() < 32) line += " "; - line += property.value("description", ""); - if (is_required(schema, name)) { - line += " [req]\n"; - } else if (property.contains("default")) { - line += " [" + format_json(property.at("default")) + "]\n"; - } else { - line += "\n"; - } - if (property.contains("enum")) { - line += " with choices: "; - auto len = 16; - for (auto& choice_ : property.at("enum")) { - auto choice = format_json(choice_); - if (len + choice.size() + 2 > 78) { - line += "\n "; - len = 16; - } - line += choice + ", "; - len += choice.size() + 2; - } - line = line.substr(0, line.size() - 2); - line += "\n"; - } - if (positional) { - usage_positional += line; - } else { - usage_optional += line; - } - } - if (has_commands(schema)) { - for (auto& [name, property] : schema.at("properties").items()) { - if (property.value("type", "") != "object") continue; - auto line = " " + name; - while (line.size() < 32) line += " "; - line += property.value("description", "") + "\n"; - usage_command += line; - } - } - - { - auto line = string{}; - line += " --help"; - while (line.size() < 32) line += " "; - line += "Prints an help message\n"; - usage_optional += line; - } - - message += "usage: " + path_basename(app_name); - if (!command.empty()) message += " " + command; - if (!usage_command.empty()) message += " command"; - if (!usage_optional.empty()) message += " [options]"; - if (!usage_positional.empty()) message += " "; - message += "\n"; - message += schema.value("description", "") + "\n\n"; - if (!usage_command.empty()) { - message += "commands:\n" + usage_command + "\n"; - } - if (!usage_optional.empty()) { - message += "options:\n" + usage_optional + "\n"; - } - if (!usage_positional.empty()) { - message += "arguments:\n" + usage_positional + "\n"; - } - return message; -} - -string get_command(const cli_state& cli) { return cli.command; } -bool get_help(const cli_state& cli) { return cli.help; } -string get_usage(const cli_state& cli) { return cli.usage; } - -static bool parse_clivalue( - json_value& value, const string& arg, const json_value& schema) { - // if (!choices.empty()) { - // if (std::find(choices.begin(), choices.end(), arg) == choices.end()) - // return false; - // } - auto type = schema.value("type", "string"); - if (type == "string") { - value = arg; - return true; - } else if (type == "integer") { - auto end = (char*)nullptr; - if (arg.find('-') == 0) { - value = (int64_t)strtol(arg.c_str(), &end, 10); - } else { - value = (uint64_t)strtoul(arg.c_str(), &end, 10); - } - return end != nullptr; - } else if (type == "number") { - auto end = (char*)nullptr; - value = strtod(arg.c_str(), &end); - return end != nullptr; - return true; - } else if (type == "boolean") { - if (arg == "true" || arg == "1") { - value = true; - return true; - } else if (arg == "false" || arg == "0") { - value = false; - return true; - } else { - return false; - } - } - return false; -} - -static bool parse_clivalue( - json_value& value, const vector& args, const json_value& schema) { - auto type = schema.value("type", "string"); - if (type == "array") { - value = json_array{}; - for (auto& arg : args) { - if (!parse_clivalue(value.emplace_back(), arg, schema.at("items"))) - return false; - } - return true; - } - return false; -} - -static bool set_clivalues( - const json_value& js, cli_setter& value, string& error) { - auto cli_error = [&error](const string& message) { - error = message; - return false; - }; - - if (!value.is_object()) return cli_error("bad value"); - for (auto& [name, item] : value) { - if (!js.contains(name)) continue; - if (item.is_object()) { - if (!set_clivalues(js.at(name), item, error)) return false; - } else { - if (!item.set(js.at(name))) return cli_error("bad value for " + name); - } - } - return true; -} - -static const char* cli_help_message = "Help invoked"; - -static bool parse_cli(json_value& value, const json_value& schema_, - const vector& args, string& error, string& usage, string& command) { - auto cli_error = [&error](const string& message) { - error = message; - return false; - }; - - // helpers - auto advance_positional = [](const json_value& schema, - size_t& last_positional) -> string { - if (!schema.contains("cli_positional")) return ""; - auto& positionals = schema.at("cli_positional"); - if (!positionals.is_array()) return ""; - if (positionals.size() == last_positional) return ""; - if (!positionals.at(last_positional).is_string()) return ""; - return positionals.at(last_positional++).get(); - }; - auto is_positional = [](const json_value& schema, - const string& name) -> bool { - if (!schema.contains("cli_positional")) return false; - if (!schema.at("cli_positional").is_array()) return false; - for (auto& pname : schema.at("cli_positional")) { - if (pname.is_string() && pname.get_ref() == name) return true; - } - return false; - }; - auto is_required = [](const json_value& schema, const string& name) -> bool { - if (!schema.contains("required")) return false; - if (!schema.at("required").is_array()) return false; - for (auto& pname : schema.at("required")) { - if (pname.is_string() && pname.get_ref() == name) return true; - } - return false; - }; - auto get_alternate = [](const json_value& schema, - const string& alt) -> string { - if (!schema.contains("cli_alternate")) return ""; - if (!schema.at("cli_alternate").is_object()) return ""; - if (!schema.at("cli_alternate").contains(alt)) return ""; - if (!schema.at("cli_alternate").at(alt).is_string()) return ""; - return schema.at("cli_alternate").at(alt).get(); - }; - auto get_command = [](const json_value& schema) -> string { - if (!schema.contains("cli_command")) return "$command"; - if (!schema.at("cli_command").is_string()) return "$command"; - return schema.at("cli_command").get(); - }; - auto has_commands = [](const json_value& schema) -> bool { - for (auto& [name, property] : schema.at("properties").items()) { - if (property.value("type", "") == "object") return true; - } - return false; - }; - - // parsing stack - struct stack_elem { - string name = ""; - json_value& schema; - json_value& value; - size_t positional = 0; - }; - - // initialize parsing - auto schema = fix_cli_schema(schema_); - value = json_object{}; - auto stack = vector{{"", schema, value, 0}}; - command = ""; - usage = get_cliusage(schema, args[0], command); - - // parse the command line - for (auto idx = (size_t)1; idx < args.size(); idx++) { - auto& [_, schema, value, cpositional] = stack.back(); - auto arg = args.at(idx); - auto positional = arg.find('-') != 0; - if (positional && has_commands(schema)) { - auto name = string{}; - for (auto& [pname, property] : schema.at("properties").items()) { - if (property.value("type", "string") != "object") continue; - if (pname != arg) continue; - name = arg; - } - if (name.empty()) return cli_error("missing value for command"); - value[get_command(schema)] = name; - value[name] = json_object{}; - stack.push_back( - {name, schema.at("properties").at(name), value.at(name), 0}); - command += (command.empty() ? "" : " ") + name; - usage = get_cliusage(stack.back().schema, args[0], command); - continue; - } else if (positional) { - auto name = string{}; - auto next_positional = advance_positional(schema, cpositional); - for (auto& [pname, property] : schema.at("properties").items()) { - if (property.value("type", "string") == "object") continue; - if (pname != next_positional) continue; - name = pname; - } - if (name.empty()) return cli_error("too many positional arguments"); - auto& property = schema.at("properties").at(name); - if (property.value("type", "string") == "array") { - auto array_args = vector(args.begin() + idx, args.end()); - if (!parse_clivalue(value[name], array_args, property)) - return cli_error("bad value for " + name); - idx = args.size(); - } else if (property.value("type", "string") != "object") { - if (!parse_clivalue(value[name], args[idx], property)) - return cli_error("bad value for " + name); - } - } else { - if (arg == "--help" || arg == "-?") { - return cli_error(cli_help_message); - } - arg = arg.substr(1); - if (arg.find('-') == 0) arg = arg.substr(1); - auto name = string{}; - for (auto& [pname, property] : schema.at("properties").items()) { - if (property.value("type", "string") == "object") continue; - if (property.value("type", "string") == "array") continue; - if (is_positional(schema, pname)) continue; - if (pname != arg && get_alternate(schema, pname) != arg && - pname != "no-" + arg) // TODO(fabio): fix boolean - continue; - name = pname; - break; - } - if (name.empty()) return cli_error("unknown option " + args[idx]); - if (value.contains(name)) return cli_error("option already set " + name); - auto& property = schema.at("properties").at(name); - if (property.value("type", "string") == "boolean") { - if (!parse_clivalue( - value[name], arg.find("no-") != 0 ? "true" : "false", property)) - return cli_error("bad value for " + name); - } else { - if (idx + 1 >= args.size()) - return cli_error("missing value for " + name); - if (!parse_clivalue(value[name], args[idx + 1], property)) - return cli_error("bad value for " + name); - idx += 1; - } - } - } - - // check for required and apply defaults - for (auto& [_, schema, value, __] : stack) { - if (has_commands(schema) && !value.contains(get_command(schema))) - return cli_error("missing value for " + get_command(schema)); - for (auto& [name, property] : schema.at("properties").items()) { - if (property.value("type", "string") == "object") continue; - if (is_required(schema, name) && !value.contains(name)) - return cli_error("missing value for " + name); - if (property.contains("default") && !value.contains(name)) - value[name] = property.at("default"); - } - } - - // done - return true; -} - -bool parse_cli(json_value& value, const json_value& schema, - const vector& args, string& error, string& usage) { - auto command = string{}; - return parse_cli(value, schema, args, error, usage, command); -} - -bool parse_cli(json_value& value, const json_value& schema, int argc, - const char** argv, string& error, string& usage) { - return parse_cli(value, schema, {argv, argv + argc}, error, usage); -} - -void parse_cli( - json_value& value, const json_value& schema, const vector& args) { - auto error = string{}; - auto usage = string{}; - if (!parse_cli(value, schema, args, error, usage)) { - if (error != cli_help_message) { - print_info("error: " + error); - print_info(""); - } - print_info(usage); - exit(error != cli_help_message ? 1 : 0); - } -} - -void parse_cli( - json_value& value, const json_value& schema, int argc, const char** argv) { - return parse_cli(value, schema, {argv, argv + argc}); -} - // initialize a command line parser cli_state make_cli(const string& name, const string& usage) { auto cli = cli_state{}; @@ -1421,6 +428,27 @@ cli_command add_command( return add_command({cli, {}}, name, usage); } +static bool set_clivalues( + const json_value& js, cli_setter& value, string& error) { + auto cli_error = [&error](const string& message) { + error = message; + return false; + }; + + if (!value.is_object()) return cli_error("bad value"); + for (auto& [name, item] : value) { + if (!js.contains(name)) continue; + if (item.is_object()) { + if (!set_clivalues(js.at(name), item, error)) return false; + } else { + if (!item.set(js.at(name))) return cli_error("bad value for " + name); + } + } + return true; +} + +static const char* cli_help_message = "Help invoked"; + bool parse_cli(cli_state& cli, const vector& args, string& error) { auto usage = string{}; auto command = string{}; diff --git a/libs/yocto/yocto_commonio.h b/libs/yocto/yocto_commonio.h index cf6ac8ce8..bbd9153c5 100644 --- a/libs/yocto/yocto_commonio.h +++ b/libs/yocto/yocto_commonio.h @@ -51,6 +51,8 @@ #include #include +#include "yocto_json.h" + // ----------------------------------------------------------------------------- // USING DIRECTIVES // ----------------------------------------------------------------------------- @@ -123,19 +125,6 @@ string elapsed_formatted(simple_timer& timer); // ----------------------------------------------------------------------------- namespace yocto { -// Parse the command line described by a JSON schema. -// Checks for errors, and exits on error or help. -struct json_value; -void parse_cli( - json_value& value, const json_value& schema, int argc, const char** argv); -void parse_cli( - json_value& value, const json_value& schema, const vector& args); -// Parse the command line described by a schema. -bool parse_cli(json_value& value, const json_value& schema, - const vector& args, string& error, string& usage); -bool parse_cli(json_value& value, const json_value& schema, int argc, - const char** argv, string& error, string& usage); - // Initialize a command line parser. struct cli_state; cli_state make_cli(const string& cmd, const string& usage); @@ -313,464 +302,6 @@ bool save_binary( } // namespace yocto -// ----------------------------------------------------------------------------- -// JSON SUPPORT -// ----------------------------------------------------------------------------- -namespace yocto { - -// Json type -enum struct json_type { - // clang-format off - null, integer, unsigned_, real, boolean, string_, array, object, binary - // clang-format on -}; - -// Json forward declarations -struct json_value; -using json_array = vector; -using json_object = vector>; -using json_binary = vector; -using json_iterator = json_value*; -using json_citerator = const json_value*; -using json_oiterator = pair*; -using json_ociterator = const pair*; - -// Json type error -struct json_error : std::runtime_error { - using std::runtime_error::runtime_error; -}; - -// Json value -struct json_value { - // constructors - json_value(); - json_value(const json_value& other); - json_value(json_value&& other); - explicit json_value(std::nullptr_t); - explicit json_value(int32_t); - explicit json_value(int64_t); - explicit json_value(uint32_t); - explicit json_value(uint64_t); - explicit json_value(float); - explicit json_value(double); - explicit json_value(bool); - explicit json_value(const string&); - explicit json_value(string_view); - explicit json_value(const char* value); - explicit json_value(const json_array&); - explicit json_value(const json_object&); - explicit json_value(const json_binary&); - template - explicit json_value(const T& value); - - // assignments - json_value& operator=(const json_value& other); - json_value& operator=(json_value&& other); - template - json_value& operator=(const T& value); - - // type - json_type type() const; - bool is_null() const; - bool is_integer() const; - bool is_unsigned() const; - bool is_real() const; - bool is_integral() const; - bool is_number() const; - bool is_boolean() const; - bool is_string() const; - bool is_array() const; - bool is_object() const; - bool is_binary() const; - - // conversions (see get) - explicit operator int32_t() const; - explicit operator int64_t() const; - explicit operator uint32_t() const; - explicit operator uint64_t() const; - explicit operator float() const; - explicit operator double() const; - explicit operator bool() const; - explicit operator string() const; - explicit operator string_view() const; - template - explicit operator T() const; - - // get values via conversion - template - T get() const; - template - void get_to(T& value) const; - - // access references - template - T& get_ref(); - template - const T& get_ref() const; - - // structure support - bool empty() const; - size_t size() const; - void clear(); - void resize(size_t size); - void reserve(size_t size); - void update(const json_value& other); - - // elemnt acceess - bool contains(const string& key) const; - json_value& operator[](size_t idx); - json_value& operator[](const string& key); - json_value& at(size_t idx); - const json_value& at(size_t idx) const; - json_value& at(const string& key); - const json_value& at(const string& key) const; - json_value& front(); - const json_value& front() const; - json_value& back(); - const json_value& back() const; - void push_back(const json_value& value); - void push_back(json_value&& value); - template - void push_back(const T& value); - template - json_value& emplace_back(Args&&... args); - template - json_value& emplace(Args&&... args); - - // iteration - json_iterator begin(); - json_citerator begin() const; - json_iterator end(); - json_citerator end() const; - struct json_object_it; - struct json_object_cit; - json_object_it items(); - json_object_cit items() const; - json_iterator find(const string& key); - json_citerator find(const string& key) const; - - // get value at an object key - template - T value(const string& key, const T& default_) const; - string value(const string& key, const char* default_) const; - - // array/object/binary creation - static json_value array(); - static json_value object(); - static json_value binary(); - - // swap - void swap(json_value& other); - -#ifdef __APPLE__ - explicit json_value(size_t); - explicit operator size_t() const; -#endif - - // destructor - ~json_value(); - - json_type _type = json_type::null; - union { - int64_t _integer; - uint64_t _unsigned; - double _real; - bool _boolean; - string* _string; - json_array* _array; - json_object* _object; - json_binary* _binary; - }; -}; - -// Load/save a json file -bool load_json(const string& filename, json_value& js, string& error); -bool save_json(const string& filename, const json_value& js, string& error); - -// Formats/parse a Json to/from string -bool parse_json(const string& text, json_value& js, string& error); -bool format_json(string& text, const json_value& js, string& error); -string format_json(const json_value& js); - -// Conversion shortcuts -template -inline T from_json(const json_value& js); -template -inline json_value to_json(const T& value); -template -inline bool from_json(const json_value& js, T& value, json_error& error); -template -inline bool to_json(const json_value& js, T& value, json_error& error); - -// Conversion from json to values -inline void from_json(const json_value& js, int64_t& value); -inline void from_json(const json_value& js, int32_t& value); -inline void from_json(const json_value& js, uint64_t& value); -inline void from_json(const json_value& js, uint32_t& value); -inline void from_json(const json_value& js, double& value); -inline void from_json(const json_value& js, float& value); -inline void from_json(const json_value& js, bool& value); -inline void from_json(const json_value& js, string& value); -template -inline void from_json(const json_value& js, vector& value); -template -inline void from_json(const json_value& js, array& value); - -// Conversion to json from values -inline void to_json(json_value& js, int64_t value); -inline void to_json(json_value& js, int32_t value); -inline void to_json(json_value& js, uint64_t value); -inline void to_json(json_value& js, uint32_t value); -inline void to_json(json_value& js, double value); -inline void to_json(json_value& js, float value); -inline void to_json(json_value& js, bool value); -inline void to_json(json_value& js, const string& value); -template -inline void to_json(json_value& js, const vector& value); -template -inline void to_json(json_value& js, const array& value); - -// Validate a value against a schema -bool validate_json( - const json_value& value, const json_value& schema, string& error); -bool validate_json(const json_value& value, const json_value& schema, - vector& errors, size_t max_errors = 100); - -// Declarations -struct json_view; -struct json_cview; - -// Json tree -struct json_tree { - union json_value { - int64_t _integer = 0; - uint64_t _unsigned; - double _real; - bool _boolean; - struct { - uint32_t start; - uint32_t length; - } _string; - struct { - uint32_t length; - uint32_t skip; - } _array; - struct { - uint32_t length; - uint32_t skip; - } _object; - struct { - uint32_t start; - uint32_t length; - } _binary; - }; - vector types = {json_type::null}; - vector values = {json_value{}}; - vector strings = {}; - vector keys = {}; - vector key_list = {}; - vector binaries = {}; - vector build_skip_stack = {}; - bool valid = true; - string error = ""; -}; - -// Load/save a json file -bool load_json(const string& filename, json_tree& js, string& error); -bool save_json(const string& filename, const json_tree& js, string& error); - -// Get view from value -inline json_view get_root(json_tree& js); -inline json_cview get_croot(json_tree& js); - -// Error handling -inline void set_error(json_tree& js, string_view error); -inline void clear_error(json_tree& js); - -// Json view -struct json_view { - json_tree* root = nullptr; - uint32_t index = 0; - json_view(json_tree* root_) : root{root_}, index{(uint32_t)-1} {} - json_view(json_tree* root_, uint32_t index_) : root{root_}, index{index_} {} -}; -struct json_cview { - json_tree* root = nullptr; - uint32_t index = 0; - json_cview(json_tree* root_) : root{root_}, index{(uint32_t)-1} {} - json_cview(json_tree* root_, uint32_t index_) : root{root_}, index{index_} {} - json_cview(json_view other) : root{other.root}, index{other.index} {} -}; - -// Error check -inline bool is_valid(json_cview js); -inline bool is_valid(json_view js); -inline string get_error(json_cview js); -inline string get_error(json_view js); -inline string compute_path(json_cview js); -inline bool set_error(json_cview js, string_view error); - -// Type -inline json_type get_type(json_cview js); -// Type -inline bool is_null(json_cview js); -inline bool is_integer(json_cview js); -inline bool is_unsigned(json_cview js); -inline bool is_real(json_cview js); -inline bool is_integral(json_cview js); -inline bool is_number(json_cview js); -inline bool is_boolean(json_cview js); -inline bool is_string(json_cview js); -inline bool is_array(json_cview js); -inline bool is_object(json_cview js); -inline bool is_binary(json_cview js); - -// Initialization to basic types -inline bool set_null(json_view js); -inline bool set_integer(json_view js, int64_t value); -inline bool set_unsigned(json_view js, uint64_t value); -inline bool set_real(json_view js, double value); -inline bool set_boolean(json_view js, bool value); -inline bool set_string(json_view js, const string& value); -inline bool set_integral(json_view js, int64_t value); -inline bool set_integral(json_view js, int32_t value); -inline bool set_integral(json_view js, uint64_t value); -inline bool set_integral(json_view js, uint32_t value); -inline bool set_number(json_view js, double value); -inline bool set_number(json_view js, float value); - -// Get basic values -inline bool get_integer(json_cview js, int64_t& value); -inline bool get_unsigned(json_cview js, uint64_t& value); -inline bool get_real(json_cview js, double& value); -inline bool get_boolean(json_cview js, bool& value); -inline bool get_string(json_cview js, string& value); -inline bool get_integral(json_cview js, int64_t& value); -inline bool get_integral(json_cview js, uint64_t& value); -inline bool get_number(json_cview js, double& value); -inline bool get_integral(json_cview js, int32_t& value); -inline bool get_integral(json_cview js, uint32_t& value); -inline bool get_number(json_cview js, float& value); - -// Get basic values - ignore errors if present -inline int64_t get_integer(json_cview js); -inline uint64_t get_unsigned(json_cview js); -inline double get_real(json_cview js); -inline bool get_boolean(json_cview js); -inline string get_string(json_cview js); -inline int64_t get_integral(json_cview js); -inline uint64_t get_uintegral(json_cview js); -inline double get_number(json_cview js); - -// Compound type -inline bool is_empty(json_cview js); -inline size_t get_size(json_cview js); - -// Array -inline bool set_array(json_view js); -inline bool set_array(json_view js, size_t size); -inline bool array_size(json_cview js, size_t& size); -inline bool has_element(json_view js, size_t idx); -inline bool has_element(json_cview js, size_t idx); -inline json_view get_element(json_view js, size_t idx); -inline json_cview get_element(json_cview js, size_t idx); -inline json_view append_element(json_view js); -inline auto iterate_array(json_view js); -inline auto iterate_array(json_cview js); - -// Object -inline bool set_object(json_view js); -inline bool object_size(json_cview js, size_t& size); -inline bool has_element(json_view js, string_view key); -inline bool has_element(json_cview js, string_view key); -inline json_view get_element(json_view js, string_view key); -inline json_cview get_element(json_cview js, string_view key); -inline json_view insert_element(json_view js, string_view key); -inline auto iterate_object(json_view js); -inline auto iterate_object(json_cview js); - -// Binary -inline bool set_binary(json_view js, const json_binary& value); -inline bool get_binary(json_cview js, json_binary& value); - -// Get the path of a json view -inline string compute_path(json_cview js); - -// Conversion from json to values -template -inline bool get_value(json_cview js, T& value); - -// Conversion from json to values -inline bool get_value(json_cview js, int64_t& value); -inline bool get_value(json_cview js, int32_t& value); -inline bool get_value(json_cview js, uint64_t& value); -inline bool get_value(json_cview js, uint32_t& value); -inline bool get_value(json_cview js, double& value); -inline bool get_value(json_cview js, float& value); -inline bool get_value(json_cview js, bool& value); -inline bool get_value(json_cview js, string& value); -template -inline bool get_value(json_cview js, vector& value); -template -inline bool get_value(json_cview js, array& value); - -// Get value at a key or index -template -inline bool get_value_at(json_cview js, string_view key, T& value); -template -inline bool get_value_at(json_cview js, size_t idx, T& value); - -// Get value at a key or nothing is key is not preesent -template -inline bool get_value_if(json_cview js, string_view key, T& value); - -// Conversion to json from values -template -inline bool set_value(json_view js, const T& value); - -// Conversion to json from values -inline bool set_value(json_view js, int64_t value); -inline bool set_value(json_view js, int32_t value); -inline bool set_value(json_view js, uint64_t value); -inline bool set_value(json_view js, uint32_t value); -inline bool set_value(json_view js, double value); -inline bool set_value(json_view js, float value); -inline bool set_value(json_view js, bool value); -inline bool set_value(json_view js, const string& value); -inline bool set_value(json_view js, const char* value); -template -inline bool set_value(json_view js, const vector& value); -template -inline bool set_value(json_view js, const array& value); - -// Helpers for user-defined types -inline bool check_array(json_cview js); -inline bool check_array(json_cview js, size_t size_); -inline bool check_object(json_cview js); - -// Helpers for user-defined types -inline bool set_array(json_view js); -template -inline bool set_value_at(json_view js, size_t idx, const T& value); -template -inline bool append_value(json_view js, const T& value); -inline json_view append_array(json_view js); -inline json_view append_object(json_view js); - -// Helpers for user-defined types -inline bool set_object(json_view js); -template -inline bool set_value_at(json_view js, string_view key, const T& value); -template -inline bool insert_value(json_view js, string_view key, const T& value); -template -inline bool insert_value_if( - json_view js, string_view key, const T& value, const T& default_); -inline json_view insert_array(json_view js, string_view key); -inline json_view insert_object(json_view js, string_view key); - -} // namespace yocto - // ----------------------------------------------------------------------------- // // @@ -791,1547 +322,6 @@ inline string format(const string& fmt, Args&&... args); } // namespace yocto -// ----------------------------------------------------------------------------- -// JSON SUPPORT -// ----------------------------------------------------------------------------- -namespace yocto { - -// constructors -inline json_value::json_value() : _type{json_type::null}, _unsigned{0} {} -inline json_value::json_value(const json_value& other) - : _type{json_type::null}, _unsigned{0} { - switch (other._type) { - case json_type::null: - _type = json_type::null; - _integer = other._integer; - break; - case json_type::integer: - _type = json_type::integer; - _integer = other._integer; - break; - case json_type::unsigned_: - _type = json_type::unsigned_; - _unsigned = other._unsigned; - break; - case json_type::real: - _type = json_type::real; - _real = other._real; - break; - case json_type::boolean: - _type = json_type::boolean; - _boolean = other._boolean; - break; - case json_type::string_: - _type = json_type::string_; - _string = new string{*other._string}; - break; - case json_type::array: - _type = json_type::array; - _array = new json_array{*other._array}; - break; - case json_type::object: - _type = json_type::object; - _object = new json_object{*other._object}; - break; - case json_type::binary: - _type = json_type::binary; - _binary = new json_binary{*other._binary}; - break; - } -} -inline json_value::json_value(json_value&& other) - : _type{json_type::null}, _unsigned{0} { - swap(other); -} -inline json_value::json_value(std::nullptr_t) - : _type{json_type::null}, _unsigned{0} {} -inline json_value::json_value(int64_t value) - : _type{json_type::integer}, _integer{value} {} -inline json_value::json_value(int32_t value) - : _type{json_type::integer}, _integer{value} {} -inline json_value::json_value(uint64_t value) - : _type{json_type::unsigned_}, _unsigned{value} {} -inline json_value::json_value(uint32_t value) - : _type{json_type::unsigned_}, _unsigned{value} {} -inline json_value::json_value(double value) - : _type{json_type::real}, _real{value} {} -inline json_value::json_value(float value) - : _type{json_type::real}, _real{value} {} -inline json_value::json_value(bool value) - : _type{json_type::boolean}, _boolean{value} {} -inline json_value::json_value(const string& value) - : _type{json_type::string_}, _string{new string{value}} {} -inline json_value::json_value(string_view value) - : _type{json_type::string_}, _string{new string{value}} {} -inline json_value::json_value(const char* value) - : _type{json_type::string_}, _string{new string{value}} {} -inline json_value::json_value(const json_array& value) - : _type{json_type::array}, _array{new json_array{value}} {} -inline json_value::json_value(const json_object& value) - : _type{json_type::object}, _object{new json_object{value}} {} -inline json_value::json_value(const json_binary& value) - : _type{json_type::binary}, _binary{new json_binary{value}} {} -template -inline json_value::json_value(const T& value) { - to_json(*this, value); -} -#ifdef __APPLE__ -inline json_value::json_value(size_t value) - : _type{json_type::unsigned_}, _unsigned{(uint64_t)value} {} -#endif - -// assignments -inline json_value& json_value::operator=(const json_value& value) { - auto js = json_value{value}; - this->swap(js); - return *this; -} -inline json_value& json_value::operator=(json_value&& value) { - this->swap(value); - return *this; -} -template -inline json_value& json_value::operator=(const T& value) { - auto js = json_value{value}; - this->swap(js); - return *this; -} - -// type -inline json_type json_value::type() const { return _type; } -inline bool json_value::is_null() const { return _type == json_type::null; } -inline bool json_value::is_integer() const { - return _type == json_type::integer; -} -inline bool json_value::is_unsigned() const { - return _type == json_type::unsigned_; -} -inline bool json_value::is_real() const { return _type == json_type::real; } -inline bool json_value::is_integral() const { - return _type == json_type::integer || _type == json_type::unsigned_; -} -inline bool json_value::is_number() const { - return _type == json_type::real || _type == json_type::integer || - _type == json_type::unsigned_; -} -inline bool json_value::is_boolean() const { - return _type == json_type::boolean; -} -inline bool json_value::is_string() const { - return _type == json_type::string_; -} -inline bool json_value::is_array() const { return _type == json_type::array; } -inline bool json_value::is_object() const { return _type == json_type::object; } -inline bool json_value::is_binary() const { return _type == json_type::binary; } - -// conversions -inline json_value::operator int64_t() const { - if (_type != json_type::integer && _type != json_type::unsigned_) - throw json_error{"integer expected"}; - return _type == json_type::integer ? (int64_t)_integer : (int64_t)_unsigned; -} -inline json_value::operator int32_t() const { - if (_type != json_type::integer && _type != json_type::unsigned_) - throw json_error{"integer expected"}; - return _type == json_type::integer ? (int32_t)_integer : (int32_t)_unsigned; -} -inline json_value::operator uint64_t() const { - if (_type != json_type::integer && _type != json_type::unsigned_) - throw json_error{"integer expected"}; - return _type == json_type::integer ? (uint64_t)_integer : (uint64_t)_unsigned; -} -inline json_value::operator uint32_t() const { - if (_type != json_type::integer && _type != json_type::unsigned_) - throw json_error{"integer expected"}; - return _type == json_type::integer ? (uint32_t)_integer : (uint32_t)_unsigned; -} -inline json_value::operator double() const { - if (_type != json_type::real && _type != json_type::integer && - _type != json_type::unsigned_) - throw json_error{"number expected"}; - return _type == json_type::real - ? (double)_real - : _type == json_type::integer ? (double)_integer - : (double)_unsigned; -} -inline json_value::operator float() const { - if (_type != json_type::real && _type != json_type::integer && - _type != json_type::unsigned_) - throw json_error{"number expected"}; - return _type == json_type::real - ? (float)_real - : _type == json_type::integer ? (float)_integer : (float)_unsigned; -} -inline json_value::operator bool() const { - if (_type != json_type::boolean) throw json_error{"boolean expected"}; - return _boolean; -} -inline json_value::operator string() const { - if (_type != json_type::string_) throw json_error{"string expected"}; - return *_string; -} -inline json_value::operator string_view() const { - if (_type != json_type::string_) throw json_error{"string expected"}; - return *_string; -} -template -inline json_value::operator T() const { - auto value = T{}; - from_json(*this, value); - return value; -} -#ifdef __APPLE__ -inline json_value::operator size_t() const { - return (size_t) operator uint64_t(); -} -#endif - -// conversions -template -inline T json_value::get() const { - return operator T(); -} -template -inline void json_value::get_to(T& value) const { - value = operator T(); -} - -// access -template -inline T& json_value::get_ref() { - static_assert( - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v, - "type not in the json variant"); - if constexpr (std::is_same_v) { - if (_type != json_type::integer) throw json_error{"integer expected"}; - return _integer; - } else if constexpr (std::is_same_v) { - if (_type != json_type::unsigned_) throw json_error{"unsigned expected"}; - return _unsigned; - } else if constexpr (std::is_same_v) { - if (_type != json_type::real) throw json_error{"real expected"}; - return _real; - } else if constexpr (std::is_same_v) { - if (_type != json_type::boolean) throw json_error{"boolean expected"}; - return _boolean; - } else if constexpr (std::is_same_v) { - if (_type != json_type::string_) throw json_error{"string expected"}; - return *_string; - } else if constexpr (std::is_same_v) { - if (_type != json_type::array) throw json_error{"array expected"}; - return *_array; - } else if constexpr (std::is_same_v) { - if (_type != json_type::object) throw json_error{"object expected"}; - return *_object; - } else if constexpr (std::is_same_v) { - if (_type != json_type::binary) throw json_error{"binary expected"}; - return *_binary; - } else { - // will never get here - } -} -// access -template -inline const T& json_value::get_ref() const { - return ((json_value*)this)->get_ref(); // const cast -} - -// structure support -inline bool json_value::empty() const { - switch (_type) { - case json_type::null: return true; - case json_type::array: return _array->empty(); - case json_type::object: return _object->empty(); - default: return false; - } -} -inline size_t json_value::size() const { - switch (_type) { - case json_type::null: return 0; - case json_type::array: return _array->size(); - case json_type::object: return _object->size(); - default: return 1; - } -} -inline void json_value::clear() { - switch (_type) { - case json_type::null: _unsigned = 0; break; - case json_type::integer: _integer = 0; break; - case json_type::unsigned_: _unsigned = 0; break; - case json_type::real: _real = 0; break; - case json_type::boolean: _boolean = false; break; - case json_type::string_: _string->clear(); break; - case json_type::array: _array->clear(); break; - case json_type::object: _object->clear(); break; - case json_type::binary: _binary->clear(); break; - default: break; - } -} -inline void json_value::resize(size_t size) { - switch (_type) { - case json_type::array: _array->resize(size); break; - default: throw json_error{"array expected"}; - } -} -inline void json_value::reserve(size_t size) { - switch (_type) { - case json_type::array: _array->reserve(size); break; - case json_type::object: _object->reserve(size); break; - default: throw json_error{"structure expected"}; - } -} - -// array support -inline json_value& json_value::operator[](size_t idx) { - if (_type == json_type::null) *this = json_array{}; - if (_type != json_type::array) throw json_error{"array expected"}; - if (idx >= _array->size()) throw json_error{"index out of range"}; - return _array->operator[](idx); -} -inline json_value& json_value::operator[](const string& key) { - if (_type == json_type::null) *this = json_object{}; - if (_type != json_type::object) throw json_error{"object expected"}; - if (auto elem = find(key); elem != nullptr) return *elem; - return _object->emplace_back(key, json_value{}).second; -} -inline json_value& json_value::at(size_t idx) { - if (_type != json_type::array) throw json_error{"array expected"}; - if (idx >= _array->size()) throw json_error{"index out of range"}; - return _array->operator[](idx); -} -inline const json_value& json_value::at(size_t idx) const { - if (_type != json_type::array) throw json_error{"array expected"}; - if (idx >= _array->size()) throw json_error{"index out of range"}; - return _array->operator[](idx); -} -inline json_value& json_value::at(const string& key) { - if (_type != json_type::object) throw json_error{"object expected"}; - if (auto elem = find(key); elem != nullptr) return *elem; - throw json_error{"missing key " + key}; -} -inline const json_value& json_value::at(const string& key) const { - if (_type != json_type::object) throw json_error{"object expected"}; - if (auto elem = find(key); elem != nullptr) return *elem; - throw json_error{"missing key " + key}; -} -inline json_value& json_value::front() { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->front(); -} -inline const json_value& json_value::front() const { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->front(); -} -inline json_value& json_value::back() { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->back(); -} -inline const json_value& json_value::back() const { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->back(); -} -inline void json_value::push_back(const json_value& value) { - if (_type == json_type::null) *this = json_array{}; - if (_type != json_type::array) throw json_error{"array expected"}; - _array->push_back(value); -} -inline void json_value::push_back(json_value&& value) { - if (_type == json_type::null) *this = json_array{}; - if (_type != json_type::array) throw json_error{"array expected"}; - _array->push_back(std::move(value)); -} -template -inline void json_value::push_back(const T& value) { - return push_back(json_value{value}); -} -template -inline json_value& json_value::emplace_back(Args&&... args) { - if (_type == json_type::null) *this = json_array{}; - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->emplace_back(std::forward(args)...); -} -template -inline json_value& json_value::emplace(Args&&... args) { - if (_type == json_type::null) *this = json_object{}; - if (_type != json_type::object) throw json_error{"object expected"}; - return _array->emplace_back(std::forward(args)...); -} -inline void json_value::update(const json_value& other) { - if (_type == json_type::null) *this = json_object{}; - if (_type != json_type::object) throw json_error{"object expected"}; - if (other._type != json_type::object) throw json_error{"object expected"}; - for (auto& [key, value] : *other._object) this->operator[](key) = value; -} - -// Iteration -inline json_value* json_value::begin() { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->data(); -} -inline const json_value* json_value::begin() const { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->data(); -} -inline json_value* json_value::end() { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->data() + _array->size(); -} -inline const json_value* json_value::end() const { - if (_type != json_type::array) throw json_error{"array expected"}; - return _array->data() + _array->size(); -} -struct json_value::json_object_it { - pair* _begin; - pair* _end; - pair* begin() { return _begin; } - pair* end() { return _end; } -}; -struct json_value::json_object_cit { - const pair* _begin; - const pair* _end; - const pair* begin() { return _begin; } - const pair* end() { return _end; } -}; -inline json_value::json_object_it json_value::items() { - if (_type != json_type::object) throw json_error{"object expected"}; - return {_object->data(), _object->data() + _object->size()}; -} -inline json_value::json_object_cit json_value::items() const { - return {_object->data(), _object->data() + _object->size()}; -} -inline json_value* json_value::find(const string& key) { - if (_type != json_type::object) throw json_error{"object expected"}; - for (auto& [key_, value] : *_object) { - if (key_ == key) return &value; - } - return nullptr; -} -inline const json_value* json_value::find(const string& key) const { - if (_type != json_type::object) throw json_error{"object expected"}; - for (auto& [key_, value] : *_object) { - if (key_ == key) return &value; - } - return nullptr; -} -inline bool json_value::contains(const string& key) const { - return find(key) != nullptr; -} - -// get value at an object key -template -inline T json_value::value(const string& key, const T& default_) const { - if (_type != json_type::object) throw json_error{"object expected"}; - auto element = find(key); - return element ? element->get() : default_; -} -inline string json_value::value(const string& key, const char* default_) const { - return value(key, default_); -} - -// array/object/binary creation -inline json_value json_value::array() { return json_value{json_array{}}; } -inline json_value json_value::object() { return json_value{json_object{}}; } -inline json_value json_value::binary() { return json_value{json_binary{}}; } - -// swap -inline void json_value::swap(json_value& other) { - std::swap(_type, other._type); - std::swap(_unsigned, other._unsigned); // hask to swap bits -} - -// destructor -inline json_value::~json_value() { - switch (_type) { - case json_type::string_: delete _string; break; - case json_type::array: delete _array; break; - case json_type::object: delete _object; break; - case json_type::binary: delete _binary; break; - default: break; - } - _type = json_type::null; - _unsigned = 0; -} - -// Conversion shortcuts -template -inline T from_json(const json_value& js) { - auto value = T{}; - from_json(js, value); - return value; -} -template -inline json_value to_json(const T& value) { - auto js = json_value{}; - to_json(js, value); - return js; -} -template -inline bool from_json(const json_value& js, T& value, json_error& error) { - try { - from_json(js, value); - return true; - } catch (json_error& err) { - error = err; - return false; - } -} -template -inline bool to_json(const json_value& js, T& value, json_error& error) { - try { - to_json(js, value); - return true; - } catch (json_error& err) { - error = err; - return false; - } -} - -// Conversion from json to values -inline void from_json(const json_value& js, int64_t& value) { - value = (int64_t)js; -} -inline void from_json(const json_value& js, int32_t& value) { - value = (int32_t)js; -} -inline void from_json(const json_value& js, uint64_t& value) { - value = (uint64_t)js; -} -inline void from_json(const json_value& js, uint32_t& value) { - value = (uint32_t)js; -} -inline void from_json(const json_value& js, double& value) { - value = (double)js; -} -inline void from_json(const json_value& js, float& value) { value = (float)js; } -inline void from_json(const json_value& js, bool& value) { value = (bool)js; } -inline void from_json(const json_value& js, string& value) { - value = (string)js; -} -template -inline void from_json(const json_value& js, vector& value) { - value.clear(); - value.reserve(js.size()); - for (auto& ejs : js) from_json(ejs, value.emplace_back()); -} -template -inline void from_json(const json_value& js, array& value) { - if (js.size() != value.size()) throw json_error{"wrong array size"}; - for (auto idx = (size_t)0; idx < value.size(); idx++) - from_json(js.at(idx), value.at(idx)); -} - -// Conversion to json from values -inline void to_json(json_value& js, int64_t value) { js = value; } -inline void to_json(json_value& js, int32_t value) { js = value; } -inline void to_json(json_value& js, uint64_t value) { js = value; } -inline void to_json(json_value& js, uint32_t value) { js = value; } -inline void to_json(json_value& js, double value) { js = value; } -inline void to_json(json_value& js, float value) { js = value; } -inline void to_json(json_value& js, bool value) { js = value; } -inline void to_json(json_value& js, const string& value) { js = value; } -template -inline void to_json(json_value& js, const vector& value) { - js = json_array{}; - for (auto& v : value) to_json(js.emplace_back(), v); -} -template -inline void to_json(json_value& js, const array& value) { - js = json_array{}; - js.resize(value.size()); - for (auto idx = (size_t)0; idx < value.size(); idx++) - to_json(js.at(idx), value.at(idx)); -} - -// Get view from value -inline json_view get_root(json_tree& js) { return {&js, 0}; } -inline json_cview get_croot(json_tree& js) { return {&js, 0}; } -inline bool set_error(json_cview js, string_view error) { - if (!is_valid(js)) return false; - set_error(*js.root, string{error} + " at " + compute_path(js)); - return false; -} -inline json_view set_error_view(json_cview js, string_view error) { - if (!is_valid(js)) return {js.root}; - set_error(*js.root, string{error} + " at " + compute_path(js)); - return {js.root}; -} - -// Error handling -inline void set_error(json_tree& js, string_view error) { - if (!js.valid) return; - js.valid = false; - js.error = string{error}; -} -inline void clear_error(json_tree& js) { - js.valid = true; - js.error = ""; -} - -// Helpers -inline json_type& _get_type(json_view js) { - if (!is_valid(js)) throw std::invalid_argument{"bad json"}; - return js.root->types[js.index]; -} -inline const json_type& _get_type(json_cview js) { - if (!is_valid(js)) throw std::invalid_argument{"bad json"}; - return js.root->types[js.index]; -} -inline json_tree::json_value& _get_value(json_view js) { - if (!is_valid(js)) throw std::invalid_argument{"bad json"}; - return js.root->values[js.index]; -} -inline const json_tree::json_value& _get_value(json_cview js) { - if (!is_valid(js)) throw std::invalid_argument{"bad json"}; - return js.root->values[js.index]; -} -inline uint32_t _get_capacity(uint32_t length) { - if (length == 0) return 0; - if (length <= 4) return 4; - // TODO(fabio): faster pow2 - auto capacity = (uint32_t)4; - while (capacity < length) capacity *= 2; - return capacity; -} -inline string_view _get_key(json_cview js) { - if (!is_valid(js)) throw std::invalid_argument{"bad tree"}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::string_) throw std::invalid_argument{"bad key"}; - return {js.root->keys.data() + jsv._string.start, jsv._string.length}; -} -inline void _find_path(json_view js, vector& path); - -// Error check -inline bool is_valid(json_cview js) { - return js.root != nullptr && js.root->valid && js.index != (uint32_t)-1; -} -inline bool is_valid(json_view js) { - return js.root != nullptr && js.root->valid && js.index != (uint32_t)-1; -} -inline string get_error(json_cview js) { - if (js.root == nullptr) return "bad root"; - if (js.root->valid) return ""; - return js.root->error; -} -inline string get_error(json_view js) { - if (js.root == nullptr) return "bad root"; - if (js.root->valid) return ""; - return js.root->error; -} - -// Type -inline json_type get_type(json_cview js) { - if (!is_valid(js)) return json_type::null; - return _get_type(js); -} -inline bool is_null(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::null; -} -inline bool is_integer(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::integer; -} -inline bool is_unsigned(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::unsigned_; -} -inline bool is_real(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::real; -} -inline bool is_integral(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::integer || jst == json_type::unsigned_; -} -inline bool is_number(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::integer || jst == json_type::unsigned_ || - jst == json_type::real; -} -inline bool is_boolean(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::boolean; -} -inline bool is_string(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::string_; -} -inline bool is_array(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::array; -} -inline bool is_object(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::object; -} -inline bool is_binary(json_cview js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - return jst == json_type::binary; -} - -// Initialization to basic types -inline bool set_null(json_view js) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - jst = json_type::null; - return true; -} -inline bool set_integer(json_view js, int64_t value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::integer; - jsv._integer = value; - return true; -} -inline bool set_unsigned(json_view js, uint64_t value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::unsigned_; - jsv._unsigned = value; - return true; -} -inline bool set_real(json_view js, double value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::real; - jsv._real = value; - return true; -} -inline bool set_boolean(json_view js, bool value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::boolean; - jsv._boolean = value; - return true; -} -inline bool set_string(json_view js, const string& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::string_; - jsv._string.start = (uint32_t)js.root->strings.size(); - jsv._string.length = (uint32_t)value.size(); - js.root->strings.insert(js.root->strings.end(), value.begin(), value.end()); - js.root->strings.push_back(0); - return true; -} -inline bool set_integral(json_view js, int64_t value) { - return set_integer(js, value); -} -inline bool set_integral(json_view js, int32_t value) { - return set_integer(js, value); -} -inline bool set_integral(json_view js, uint64_t value) { - return set_unsigned(js, value); -} -inline bool set_integral(json_view js, uint32_t value) { - return set_unsigned(js, value); -} -inline bool set_number(json_view js, double value) { - return set_real(js, value); -} -inline bool set_number(json_view js, float value) { - return set_real(js, value); -} - -// Get basic values -inline bool get_integer(json_cview js, int64_t& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::integer) return set_error(js, "integer expected"); - value = jsv._integer; - return true; -} -inline bool get_unsigned(json_cview js, uint64_t& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::unsigned_) return set_error(js, "unsigned expected"); - value = jsv._unsigned; - return true; -} -inline bool get_real(json_cview js, double& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::real) return set_error(js, "real expected"); - value = jsv._real; - return true; -} -inline bool get_boolean(json_cview js, bool& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::boolean) return set_error(js, "boolean expected"); - value = jsv._boolean; - return true; -} -inline bool get_string(json_cview js, string& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::string_) return set_error(js, "string expected"); - value = string{js.root->strings.data() + jsv._string.start, - js.root->strings.data() + jsv._string.start + jsv._string.length}; - return true; -} -inline bool get_integral(json_cview js, int64_t& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::integer && jst != json_type::unsigned_) - return set_error(js, "integer expected"); - value = (jst == json_type::integer) ? (int64_t)jsv._integer - : (int64_t)jsv._unsigned; - return true; -} -inline bool get_integral(json_cview js, uint64_t& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::integer && jst != json_type::unsigned_) - return set_error(js, "integer expected"); - value = (jst == json_type::integer) ? (uint64_t)jsv._integer - : (uint64_t)jsv._unsigned; - return true; -} -inline bool get_number(json_cview js, double& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::real && jst != json_type::integer && - jst != json_type::unsigned_) - return set_error(js, "number expected"); - value = (jst == json_type::real) - ? (double)jsv._real - : (jst == json_type::integer) ? (double)jsv._integer - : (double)jsv._unsigned; - return true; -} -inline bool get_integral(json_cview js, int32_t& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::integer && jst != json_type::unsigned_) - return set_error(js, "integer expected"); - value = (jst == json_type::integer) ? (int32_t)jsv._integer - : (int32_t)jsv._unsigned; - return true; -} -inline bool get_integral(json_cview js, uint32_t& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::integer && jst != json_type::unsigned_) - return set_error(js, "integer expected"); - value = (jst == json_type::integer) ? (uint32_t)jsv._integer - : (uint32_t)jsv._unsigned; - return true; -} -inline bool get_number(json_cview js, float& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::real && jst != json_type::integer && - jst != json_type::unsigned_) - return set_error(js, "number expected"); - value = (jst == json_type::real) - ? (float)jsv._real - : (jst == json_type::integer) ? (float)jsv._integer - : (float)jsv._unsigned; - return true; -} - -// Get basic values -inline int64_t get_integer(json_cview js) { - auto value = (int64_t)0; - return get_integer(js, value) ? value : 0; -} -inline uint64_t get_unsigned(json_cview js) { - auto value = (uint64_t)0; - return get_unsigned(js, value) ? value : 0; -} -inline double get_real(json_cview js) { - auto value = (double)0; - return get_real(js, value) ? value : 0; -} -inline bool get_boolean(json_cview js) { - auto value = false; - return get_boolean(js, value) ? value : false; -} -inline string get_string(json_cview js) { - auto value = string{}; - return get_string(js, value) ? value : string{}; -} -inline int64_t get_integral(json_cview js) { - auto value = (int64_t)0; - return get_integral(js, value) ? value : 0; -} -inline double get_number(json_cview js) { - auto value = (double)0; - return get_number(js, value) ? value : 0; -} - -// Compound type -inline bool is_empty(json_cview js); -inline size_t get_size(json_cview js); - -// Array iteeration -inline auto iterate_array(json_view js) { - struct iterator { - json_view js; - bool operator!=(const iterator& other) { - return is_valid(js) && js.index != other.js.index; - } - iterator& operator++() { - if (!is_valid(js)) return *this; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - js.index += 1; - if (jst == json_type::array) js.index += jsv._array.skip; - if (jst == json_type::object) js.index += jsv._object.skip; - return *this; - } - json_view operator*() const { return js; } - }; - struct iterator_wrapper { - json_view begin_; - json_view end_; - iterator begin() { return {begin_}; } - iterator end() { return {end_}; } - }; - if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::array) { - set_error_view(js, "array expected"); - return iterator_wrapper{{js.root}, {js.root}}; - } - return iterator_wrapper{ - {js.root, js.index + 1}, {js.root, js.index + 1 + jsv._array.skip}}; -} -inline auto iterate_array(json_cview js) { - struct iterator { - json_cview js; - bool operator!=(const iterator& other) { - return is_valid(js) && js.index != other.js.index; - } - iterator& operator++() { - if (!is_valid(js)) return *this; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - js.index += 1; - if (jst == json_type::array) js.index += jsv._array.skip; - if (jst == json_type::object) js.index += jsv._object.skip; - return *this; - } - json_cview operator*() const { return js; } - }; - struct iterator_wrapper { - json_cview begin_; - json_cview end_; - iterator begin() { return {begin_}; } - iterator end() { return {end_}; } - }; - if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::array) { - set_error_view(js, "array expected"); - return iterator_wrapper{{js.root}, {js.root}}; - } - return iterator_wrapper{ - {js.root, js.index + 1}, - {js.root, js.index + 1 + jsv._array.skip}, - }; -} - -// Array -inline bool set_array(json_view js) { - if (!is_valid(js)) return false; - if (js.index != js.root->values.size() - 1) - throw std::out_of_range{"can only add at the end"}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::array; - jsv._array = {0, 0}; - return true; -} -inline bool array_size(json_cview js, size_t& size) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::array) return set_error(js, "array expected"); - size = (size_t)jsv._array.length; - return true; -} -inline json_view get_element(json_view js, size_t idx) { - if (!is_valid(js)) return {js.root}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::array) return set_error_view(js, "array expected"); - if (idx >= jsv._array.length) - return set_error_view(js, "index out of bounds"); - if (jsv._array.length == jsv._array.skip) { - return {js.root, js.index + 1 + (uint32_t)idx}; - } else { - auto count = 0; - for (auto ejs : iterate_array(js)) { - if (count++ == idx) return ejs; - } - return set_error_view(js, "index out of bounds"); - } -} -inline json_cview get_element(json_cview js, size_t idx) { - if (!is_valid(js)) return {js.root}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::array) return set_error_view(js, "array expected"); - if (idx >= jsv._array.length) - return set_error_view(js, "index out of bounds"); - if (jsv._array.length == jsv._array.skip) { - return {js.root, js.index + 1 + (uint32_t)idx}; - } else { - auto count = 0; - for (auto ejs : iterate_array(js)) { - if (count++ == idx) return ejs; - } - return set_error_view(js, "index out of bounds"); - } -} -inline json_view append_element(json_view js) { - if (!is_valid(js)) return {js.root}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::array) return set_error_view(js, "array expected"); - if (js.index + 1 + jsv._array.skip != js.root->values.size()) - throw std::out_of_range{"can only add at the end"}; - jsv._array.length += 1; - auto index = (uint32_t)js.root->values.size(); - js.root->types.emplace_back(json_type::null); - js.root->values.emplace_back(); - auto stack = vector{}; - _find_path(js, stack); - for (auto jss : stack) { - auto& jsst = _get_type(jss); - auto& jssv = _get_value(jss); - if (jsst == json_type::array) { - jssv._array.skip += 1; - } else if (jsst == json_type::object) { - jssv._object.skip += 1; - } else { - throw std::runtime_error{"bad stack"}; - } - } - return {js.root, index}; -} - -// Object iteration -inline auto iterate_object(json_view js) { - struct iterator { - json_view js; - bool operator!=(const iterator& other) { - return is_valid(js) && js.index != other.js.index; - } - iterator& operator++() { - if (!is_valid(js)) return *this; - auto& jst = _get_type(json_view{js.root, js.index + 1}); - auto& jsv = _get_value(json_view{js.root, js.index + 1}); - js.index += 2; - if (jst == json_type::array) js.index += jsv._array.skip; - if (jst == json_type::object) js.index += jsv._object.skip; - return *this; - } - pair operator*() const { - return {_get_key(js), json_view{js.root, js.index + 1}}; - } - }; - struct iterator_wrapper { - json_view begin_; - json_view end_; - iterator begin() { return {begin_}; } - iterator end() { return {end_}; } - }; - if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::object) { - set_error_view(js, "object expected"); - return iterator_wrapper{{js.root}, {js.root}}; - } - return iterator_wrapper{ - {js.root, js.index + 1}, {js.root, js.index + 1 + jsv._object.skip}}; -} -inline auto iterate_object(json_cview js) { - struct iterator { - json_cview js; - bool operator!=(const iterator& other) { - return is_valid(js) && js.index != other.js.index; - } - iterator& operator++() { - if (!is_valid(js)) return *this; - auto& jst = _get_type(json_cview{js.root, js.index + 1}); - auto& jsv = _get_value(json_cview{js.root, js.index + 1}); - js.index += 2; - if (jst == json_type::array) js.index += jsv._array.skip; - if (jst == json_type::object) js.index += jsv._object.skip; - return *this; - } - pair operator*() const { - return {_get_key(js), json_cview{js.root, js.index + 1}}; - } - }; - struct iterator_wrapper { - json_cview begin_; - json_cview end_; - iterator begin() { return {begin_}; } - iterator end() { return {end_}; } - }; - if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::object) { - set_error_view(js, "object expected"); - return iterator_wrapper{{js.root}, {js.root}}; - } - return iterator_wrapper{ - {js.root, js.index + 1}, {js.root, js.index + 1 + jsv._object.skip}}; -} - -// Object -inline bool set_object(json_view js) { - if (!is_valid(js)) return false; - if (js.index != js.root->values.size() - 1) - throw std::out_of_range{"can only add at the end"}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - jst = json_type::object; - jsv._array = {0, 0}; - return true; -} -inline bool object_size(json_cview js, size_t& size) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::object) return set_error(js, "object expected"); - size = (size_t)jsv._object.length; - return true; -} -inline json_view get_element(json_view js, string_view key) { - if (!is_valid(js)) return {js.root}; - auto& jst = _get_type(js); - if (jst != json_type::object) return set_error_view(js, "object expected"); - for (auto [okey, ejs] : iterate_object(js)) { - if (okey == key) return ejs; - } - return set_error_view(js, "missing key " + string{key}); -} -inline json_cview get_element(json_cview js, string_view key) { - if (!is_valid(js)) return {js.root}; - auto& jst = _get_type(js); - if (jst != json_type::object) return set_error_view(js, "object expected"); - for (auto [okey, value] : iterate_object(js)) { - if (okey == key) return value; - } - return set_error_view(js, "missing key " + string{key}); -} -inline bool has_element(json_view js, string_view key) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - if (jst != json_type::object) return set_error(js, "object expected"); - for (auto [okey, value] : iterate_object(js)) { - if (okey == key) return true; - } - return false; -} -inline bool has_element(json_cview js, string_view key) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - if (jst != json_type::object) return set_error(js, "object expected"); - for (auto [okey, value] : iterate_object(js)) { - if (okey == key) return true; - } - return false; -} -inline json_view insert_element(json_view js, string_view key) { - if (!is_valid(js)) return {js.root}; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::object) return set_error_view(js, "object expected"); - if (js.index + 1 + jsv._object.skip != js.root->values.size()) - throw std::out_of_range{"can only add at the end"}; - for (auto [okey, ejs] : iterate_object(js)) { - if (okey == key) return ejs; - } - jsv._object.length += 1; - auto& jkt = js.root->types.emplace_back(); - auto& jkv = js.root->values.emplace_back(); - jkt = json_type::string_; - for (auto kv : js.root->key_list) { - auto okey = string_view{ - js.root->keys.data() + kv._string.start, kv._string.length}; - if (okey == key) jkv = kv; - } - if (jkv._string.length == 0) { - jkv._string.start = (uint32_t)js.root->keys.size(); - jkv._string.length = (uint32_t)key.size(); - js.root->keys.insert(js.root->keys.end(), key.begin(), key.end()); - js.root->keys.push_back(0); - } - auto index = (uint32_t)js.root->values.size(); - js.root->types.emplace_back(json_type::null); - js.root->values.emplace_back(); - auto stack = vector{}; - _find_path(js, stack); - for (auto jss : stack) { - auto& jsst = _get_type(jss); - auto& jssv = _get_value(jss); - if (jsst == json_type::array) { - jssv._array.skip += 2; - } else if (jsst == json_type::object) { - jssv._object.skip += 2; - } else { - throw std::runtime_error{"bad stack"}; - } - } - return {js.root, index}; -} - -// Binary -inline bool set_binary(json_view js, const json_binary& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - // TODO(fabio): implement reuse - jst = json_type::binary; - jsv._binary.start = (uint32_t)js.root->binaries.size(); - jsv._binary.length = (uint32_t)value.size(); - js.root->binaries.insert(js.root->binaries.end(), value.begin(), value.end()); - return true; -} -inline bool get_binary(json_cview js, json_binary& value) { - if (!is_valid(js)) return false; - auto& jst = _get_type(js); - auto& jsv = _get_value(js); - if (jst != json_type::binary) return set_error(js, "binary expected"); - value = json_binary{js.root->binaries.data() + jsv._binary.start, - js.root->binaries.data() + jsv._binary.start + jsv._binary.length}; - return true; -} - -// Get the path of a json view -inline bool _compute_path(json_cview js, json_cview jsv, string& path) { - if (!is_valid(js) || !is_valid(jsv)) { - return false; - } else if (js.index == jsv.index) { - path = "/"; - return true; - } else if (is_array(js)) { - auto idx = 0; - for (auto ejs : iterate_array(js)) { - if (!_compute_path(ejs, jsv, path)) continue; - if (path.back() == '/') path.pop_back(); - path = "/"s + std::to_string(idx) + path; - return true; - } - return false; - } else if (is_object(js)) { - for (auto [key, ejs] : iterate_object(js)) { - if (!_compute_path(ejs, jsv, path)) continue; - if (path.back() == '/') path.pop_back(); - path = "/" + string{key} + path; - return true; - } - return false; - } else { - return false; - } -} -inline string compute_path(json_cview js) { - auto path = string{}; - if (_compute_path({js.root, 0}, js, path)) { - return path; - } else { - return ""; - } -} - -// Conversion from json to values -inline bool get_value(json_cview js, int64_t& value) { - return get_integral(js, value); -} -inline bool get_value(json_cview js, int32_t& value) { - return get_integral(js, value); -} -inline bool get_value(json_cview js, uint64_t& value) { - return get_integral(js, value); -} -inline bool get_value(json_cview js, uint32_t& value) { - return get_integral(js, value); -} -inline bool get_value(json_cview js, double& value) { - return get_number(js, value); -} -inline bool get_value(json_cview js, float& value) { - return get_number(js, value); -} -inline bool get_value(json_cview js, bool& value) { - return get_boolean(js, value); -} -inline bool get_value(json_cview js, string& value) { - return get_string(js, value); -} -template -inline bool get_value(json_cview js, vector& value) { - if (!is_valid(js)) return false; - if (!is_array(js)) return set_error(js, "array expected"); - value.clear(); - auto size = (size_t)0; - array_size(js, size); - value.reserve(size); - for (auto ejs : iterate_array(js)) { - if (!get_value(ejs, value.emplace_back())) return false; - } - return true; -} -template -inline bool get_value(json_cview js, array& value) { - if (!is_valid(js)) return false; - if (!is_array(js)) return set_error(js, "array expected"); - auto size = (size_t)0; - array_size(js, size); - if (size != N) return set_error(js, "size mismatched"); - auto idx = 0; - for (auto ejs : iterate_array(js)) { - if (!get_value(ejs, value.at(idx++))) return false; - } - return true; -} - -// Get value at a key or index -template -inline bool get_value_at(json_cview js, string_view key, T& value) { - if (!is_valid(js)) return false; - auto element = get_element(js, key); - return get_value(element, value); -} -template -inline bool get_value_at(json_cview js, size_t idx, T& value) { - if (!is_valid(js)) return false; - auto element = get_element(js, idx); - if (!is_valid(element)) return false; - return get_value(element, value); -} - -// Get value at a key or nothing is key is not preesent -template -inline bool get_value_if(json_cview js, string_view key, T& value) { - if (!is_valid(js)) return false; - if (!has_element(js, key)) return true; - auto element = get_element(js, key); - if (!is_valid(element)) return false; - return get_value(element, value); -} - -// Conversion to json from values -inline bool set_value(json_view js, int64_t value) { - return set_integral(js, value); -} -inline bool set_value(json_view js, int32_t value) { - return set_integral(js, value); -} -inline bool set_value(json_view js, uint64_t value) { - return set_integral(js, value); -} -inline bool set_value(json_view js, uint32_t value) { - return set_integral(js, value); -} -inline bool set_value(json_view js, double value) { - return set_number(js, value); -} -inline bool set_value(json_view js, float value) { - return set_number(js, value); -} -inline bool set_value(json_view js, bool value) { - return set_boolean(js, value); -} -inline bool set_value(json_view js, const string& value) { - return set_string(js, value); -} -inline bool set_value(json_view js, const char* value) { - return set_string(js, value); -} -template -inline bool set_value(json_view js, const vector& value) { - if (!set_array(js)) return false; - for (auto& v : value) { - if (!set_value(append_element(js), v)) return false; - } - return true; -} -template -inline bool set_value(json_view js, const array& value) { - if (!set_array(js)) return false; - for (auto& v : value) { - if (!set_value(append_element(js), v)) return false; - } - return true; -} - -// Helpers for user-defined types -inline bool check_array(json_cview js) { - if (!is_valid(js)) return false; - if (!is_array(js)) return set_error(js, "array expected"); - return true; -} -inline bool check_array(json_cview js, size_t size_) { - if (!is_valid(js)) return false; - if (!is_array(js)) return set_error(js, "array expected"); - auto size = (size_t)0; - if (!array_size(js, size) || size != size_) - return set_error(js, "mismatched size"); - return true; -} -inline bool check_object(json_cview js) { - if (!is_valid(js)) return false; - if (!is_object(js)) return set_error(js, "array expected"); - return true; -} - -// Helpers for user-defined types -template -inline bool set_value_at(json_view js, size_t idx, const T& value) { - if (!is_valid(js)) return false; - auto ejs = get_element(js, idx); - if (!is_valid(ejs)) return false; - return set_value(ejs, value); -} -template -inline bool append_value(json_view js, const T& value) { - if (!is_valid(js)) return false; - auto ejs = append_element(js); - if (!is_valid(ejs)) return false; - return set_value(ejs, value); -} -inline json_view append_array(json_view js) { - if (!is_valid(js)) return {js.root}; - auto ejs = append_element(js); - if (!is_valid(ejs)) return {js.root}; - if (!set_array(ejs)) return {js.root}; - return ejs; -} -inline json_view append_object(json_view js) { - if (!is_valid(js)) return {js.root}; - auto ejs = append_element(js); - if (!is_valid(ejs)) return {js.root}; - if (!set_object(ejs)) return {js.root}; - return ejs; -} - -// Helpers for user-defined types -template -inline bool set_value_at(json_view js, string_view key, const T& value) { - if (!is_valid(js)) return false; - auto ejs = get_element(js, key); - if (!is_valid(ejs)) return false; - return set_value(ejs, value); -} -template -inline bool insert_value(json_view js, string_view key, const T& value) { - if (!is_valid(js)) return false; - auto ejs = insert_element(js, key); - if (!is_valid(ejs)) return false; - return set_value(ejs, value); -} -template -inline bool insert_value_if( - json_view js, string_view key, const T& value, const T& default_) { - if (!is_valid(js)) return false; - if (value == default_) return true; - auto ejs = insert_element(js, key); - if (!is_valid(ejs)) return false; - return set_value(ejs, value); -} -inline json_view insert_array(json_view js, string_view key) { - if (!is_valid(js)) return {js.root}; - auto ejs = insert_element(js, key); - if (!is_valid(ejs)) return {js.root}; - if (!set_array(ejs)) return {js.root}; - return ejs; -} -inline json_view insert_object(json_view js, string_view key) { - if (!is_valid(js)) return {js.root}; - auto ejs = insert_element(js, key); - if (!is_valid(ejs)) return {js.root}; - if (!set_object(ejs)) return {js.root}; - return ejs; -} - -// Helpers that need to be declared here -inline bool _find_anchestors( - json_view js, uint32_t index, vector& path) { - auto& jst = _get_type(js); - if (jst == json_type::array) { - if (js.index == index) { - path.push_back(js); - return true; - } - // if (index <= js.index || index >= js.index + 1 + jsv._array.skip) - // return false; - // path.push_back(js.index); - for (auto ejs : iterate_array(js)) { - if (_find_anchestors(ejs, index, path)) { - path.push_back(ejs); - return true; - } - } - return false; - } else if (jst == json_type::object) { - if (js.index == index) { - path.push_back(js); - return true; - } - // if (index <= js.index || index >= js.index + 1 + jsv._object.skip) - // return false; - // path.push_back(js.index); - for (auto [okey, ejs] : iterate_object(js)) { - if (_find_anchestors(ejs, index, path)) { - path.push_back(js); - return true; - } - } - return false; - } else { - return false; - } -} -inline void _find_path(json_view js, vector& path) { - path.clear(); - _find_anchestors({js.root, 0}, js.index, path); -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // FILE IO // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_json.cpp b/libs/yocto/yocto_json.cpp new file mode 100644 index 000000000..2a49239b7 --- /dev/null +++ b/libs/yocto/yocto_json.cpp @@ -0,0 +1,1033 @@ +// +// Implementation for Yocto/JSON +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2020 Fabio Pellacini +// +// 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 "yocto_json.h" + +#include +#include +#include +#include + +#include "ext/json.hpp" +#include "yocto_commonio.h" + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF JSON IO +// ----------------------------------------------------------------------------- +namespace yocto { + +#define YOCTO_JSON_SAX 1 + +using njson = nlohmann::ordered_json; + +// load/save json +bool load_json(const string& filename, njson& js, string& error) { + // error helpers + auto parse_error = [filename, &error]() { + error = filename + ": parse error in json"; + return false; + }; + auto text = ""s; + if (!load_text(filename, text, error)) return false; + try { + js = njson::parse(text); + return true; + } catch (std::exception&) { + return parse_error(); + } +} + +bool save_json(const string& filename, const njson& js, string& error) { + return save_text(filename, js.dump(2), error); +} + +// convert json +void to_json(json_value& js, const njson& njs) { + switch (njs.type()) { + case njson::value_t::null: js = json_value{}; break; + case njson::value_t::number_integer: js = (int64_t)njs; break; + case njson::value_t::number_unsigned: js = (uint64_t)njs; break; + case njson::value_t::number_float: js = (double)njs; break; + case njson::value_t::boolean: js = (bool)njs; break; + case njson::value_t::string: js = (string)njs; break; + case njson::value_t::array: + js = json_array(); + for (auto& ejs : njs) to_json(js.emplace_back(), ejs); + break; + case njson::value_t::object: + js = json_object(); + for (auto& [key, ejs] : njs.items()) to_json(js[key], ejs); + break; + case njson::value_t::binary: + js = json_binary(); + js.get_ref() = njs.get_binary(); + break; + case njson::value_t::discarded: js = json_value{}; break; + } +} + +// convert json +void from_json(const json_value& js, njson& njs) { + switch (js.type()) { + case json_type::null: njs = {}; break; + case json_type::integer: njs = js.get_ref(); break; + case json_type::unsigned_: njs = js.get_ref(); break; + case json_type::real: njs = js.get_ref(); break; + case json_type::boolean: njs = js.get_ref(); break; + case json_type::string_: njs = js.get_ref(); break; + case json_type::array: + njs = njson::array(); + for (auto& ejs : js) from_json(ejs, njs.emplace_back()); + break; + case json_type::object: + njs = njson::object(); + for (auto& [key, ejs] : js.items()) from_json(ejs, njs[key]); + break; + case json_type::binary: + njs = njson::binary({}); + njs.get_binary() = js.get_ref(); + break; + } +} + +#if YOCTO_JSON_SAX == 1 + +// load json +bool load_json(const string& filename, json_value& js, string& error) { + // error helpers + auto parse_error = [filename, &error]() { + error = filename + ": parse error in json"; + return false; + }; + + // sax handler + struct sax_handler { + // stack + yocto::json_value* root = nullptr; + std::vector stack = {}; + std::string current_key; + explicit sax_handler(yocto::json_value* root_) { + *root_ = yocto::json_value{}; + root = root_; + stack.push_back(root); + } + + // get current value + yocto::json_value& next_value() { + if (stack.size() == 1) return *root; + if (stack.back()->is_array()) return (*stack.back()).emplace_back(); + if (stack.back()->is_object()) return (*stack.back())[current_key]; + throw yocto::json_error{"bad json type"}; + } + + // values + bool null() { + next_value() = yocto::json_value{}; + return true; + } + bool boolean(bool value) { + next_value() = value; + return true; + } + bool number_integer(int64_t value) { + next_value() = value; + return true; + } + bool number_unsigned(uint64_t value) { + next_value() = value; + return true; + } + bool number_float(double value, const std::string&) { + next_value() = value; + return true; + } + bool string(std::string& value) { + next_value() = value; + return true; + } + bool binary(std::vector& value) { + next_value() = value; + return true; + } + + // objects + bool start_object(size_t elements) { + next_value() = yocto::json_object{}; + stack.push_back(&next_value()); + return true; + } + bool end_object() { + stack.pop_back(); + return true; + } + bool key(std::string& value) { + current_key = value; + return true; + } + + // arrays + bool start_array(size_t elements) { + next_value() = yocto::json_array{}; + stack.push_back(&next_value()); + return true; + } + bool end_array() { + stack.pop_back(); + return true; + } + + bool parse_error(size_t position, const std::string& last_token, + const nlohmann::detail::exception&) { + return false; + } + }; + + // set up parsing + js = json_value{}; + auto handler = sax_handler{&js}; + + // load text + auto text = ""s; + if (!load_text(filename, text, error)) return false; + + // parse json + if (!njson::sax_parse(text, &handler)) return parse_error(); + return true; +} + +#else + +// load json +bool load_json(const string& filename, json_value& js, string& error) { + // parse json + auto njs = njson{}; + if (!load_json(filename, njs, error)) return false; + + // convert + to_json(js, njs); + return true; +} + +#endif + +// save json +bool save_json(const string& filename, const json_value& js, string& error) { + // convert + auto njs = njson{}; + from_json(js, njs); + + // save + return save_json(filename, njs, error); +} + +// Formats a Json to string +bool format_json(string& text, const json_value& js, string& error) { + // convert + auto njs = njson{}; + from_json(js, njs); + + // save + text = njs.dump(2); + return true; +} +string format_json(const json_value& js) { + auto text = string{}; + auto error = string{}; + if (!format_json(text, js, error)) return ""; + return text; +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF JSON VALIDATION +// ----------------------------------------------------------------------------- +namespace yocto { + +// Validate a value against a schema +static bool validate_json(const json_value& value, const string& path, + const json_value& schema, vector& errors, size_t max_error) { + // error handling + auto emit_error = [&errors, max_error, &path](const string& message) { + errors.push_back(message + (path.empty() ? ""s : ("at " + path))); + return errors.size() >= max_error; + }; + + // early exit + if (schema.is_boolean() && schema.get()) return true; + if (schema.is_object() && schema.empty()) return true; + + // validate type + if (schema.contains("type") && schema.at("type").is_string()) { + auto& type = schema.at("type").get_ref(); + auto type_ok = (type == "null" && value.is_null()) || + (type == "integer" && value.is_integral()) || + (type == "number" && value.is_number()) || + (type == "boolean" && value.is_boolean()) || + (type == "string" && value.is_string()) || + (type == "array" && value.is_array()) || + (type == "object" && value.is_object()); + if (!type_ok) { + if (!emit_error(type + " expected")) return false; + } + } + if (schema.contains("type") && schema.at("type").is_array()) { + auto type_ok = false; + for (auto& tschema : schema) { + if (type_ok) break; + auto& type = tschema.get_ref(); + type_ok = (type == "null" && value.is_null()) || + (type == "integer" && value.is_integral()) || + (type == "number" && value.is_number()) || + (type == "boolean" && value.is_boolean()) || + (type == "string" && value.is_string()) || + (type == "array" && value.is_array()) || + (type == "object" && value.is_object()); + } + if (!type_ok) { + auto types = ""s; + for (auto& tschema : schema) + types += (types.empty() ? "" : " or") + tschema.get_ref(); + if (!emit_error(types + " expected")) return false; + } + } + + // check range + // TODO(fabio): fix number precision + if (schema.contains("minimum") && value.is_number()) { + if (schema.at("minimum").get() > value.get()) { + if (!emit_error("value out of range")) return false; + } + } + if (schema.contains("maximum") && value.is_number()) { + if (schema.at("maximum").get() > value.get()) { + if (!emit_error("value out of range")) return false; + } + } + if (schema.contains("exclusiveMinimum") && value.is_number()) { + if (schema.at("exclusiveMinimum").get() >= value.get()) { + if (!emit_error("value out of range")) return false; + } + } + if (schema.contains("exclusiveMaximum") && value.is_number()) { + if (schema.at("exclusiveMaximum").get() <= value.get()) { + if (!emit_error("value out of range")) return false; + } + } + + // enum checks + if (schema.contains("enum") && schema.at("enum").is_array()) { + auto found = false; + for (auto& item : schema.at("enum")) { + if (found) break; + if (item.is_string() && value.is_string() && + item.get_ref() == value.get_ref()) + found = true; + if (item.is_integral() && value.is_integral() && + item.get() == value.get()) + found = true; + if (item.is_number() && value.is_number() && + item.get() == value.get()) + found = true; + } + if (!found) { + if (!emit_error("invalid enum")) return false; + } + } + + // size checks + if (schema.contains("minLength") && value.is_string()) { + if (schema.at("minLength").get() > value.get_ref().size()) { + if (!emit_error("size out of range")) return false; + } + } + if (schema.contains("maxLength") && value.is_string()) { + if (schema.at("maxLength").get() < value.get_ref().size()) { + if (!emit_error("size out of range")) return false; + } + } + if (schema.contains("minItems") && value.is_array()) { + if (schema.at("minItems").get() > + value.get_ref().size()) { + if (!emit_error("size out of range")) return false; + } + } + if (schema.contains("maxItems") && value.is_array()) { + if (schema.at("maxItems").get() < + value.get_ref().size()) { + if (!emit_error("size out of range")) return false; + } + } + if (schema.contains("minProperties") && value.is_object()) { + if (schema.at("minProperties").get() > + value.get_ref().size()) { + if (!emit_error("size out of range")) return false; + } + } + if (schema.contains("maxProperties") && value.is_object()) { + if (schema.at("maxProperties").get() < + value.get_ref().size()) { + if (!emit_error("size out of range")) return false; + } + } + + // check array items + if (schema.contains("items") && value.is_object() && + schema.at("items").is_object()) { + auto& items = schema.at("items"); + for (auto idx = (size_t)0; idx < value.size(); idx++) { + if (!validate_json(value.at(idx), path + "/" + std::to_string(idx), items, + errors, max_error)) { + if (errors.size() > max_error) break; + } + } + } + if (schema.contains("items") && value.is_array() && + schema.at("items").is_array()) { + auto& items = schema.at("items").get_ref(); + for (auto idx = (size_t)0; idx < std::min(items.size(), value.size()); + idx++) { + if (!validate_json(value.at(idx), path + "/" + std::to_string(idx), + items.at(idx), errors, max_error)) { + if (errors.size() > max_error) break; + } + } + } + + // check object properties + if (schema.contains("properties") && value.is_object() && + schema.at("properties").is_object()) { + auto& properties = schema.at("properties").get_ref(); + for (auto& [name, property] : properties) { + if (!value.contains(name)) continue; + if (!validate_json( + value.at(name), path + "/" + name, property, errors, max_error)) { + if (errors.size() > max_error) break; + } + } + } + if (schema.contains("additionalProperties") && value.is_object() && + schema.contains("properties") && + schema.at("additionalProperties").is_boolean() && + schema.at("additionalProperties").get() == false) { + auto& properties = schema.at("properties"); + for (auto& [name, item] : value.get_ref()) { + if (properties.contains(name)) { + if (!emit_error("unknown property " + name)) return false; + } + } + } + if (schema.contains("additionalProperties") && value.is_object() && + schema.contains("properties") && + schema.at("additionalProperties").is_object()) { + auto& properties = schema.at("properties"); + for (auto& [name, item] : value.get_ref()) { + if (properties.contains(name)) continue; + if (!validate_json( + item, path + "/" + name, properties, errors, max_error)) { + if (errors.size() > max_error) break; + } + } + } + if (schema.contains("required") && value.is_object() && + schema.at("required").is_array()) { + auto& required = schema.at("required").get_ref(); + for (auto& name_ : required) { + auto& name = name_.get_ref(); + if (!value.contains(name)) { + if (emit_error("missing value for " + name)) return false; + } + } + } + + // done + return false; +} +bool validate_json( + const json_value& value, const json_value& schema, string& error) { + auto errors = vector{}; + if (validate_json(value, "", schema, errors, 1)) return true; + error = errors.at(0); + return false; +} +bool validate_json(const json_value& value, const json_value& schema, + vector& errors, size_t max_errors) { + return validate_json(value, "", schema, errors, max_errors); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF COMMAND-LINE PARSING +// ----------------------------------------------------------------------------- +namespace yocto { + +static json_value fix_cli_schema(const json_value& schema) { return schema; } + +static string get_cliusage( + const json_value& schema, const string& app_name, const string& command) { + // helper + auto is_positional = [](const json_value& schema, + const string& name) -> bool { + if (!schema.contains("cli_positional")) return false; + if (!schema.at("cli_positional").is_array()) return false; + for (auto& pname : schema.at("cli_positional")) { + if (pname.is_string() && pname.get_ref() == name) return true; + } + return false; + }; + auto is_required = [](const json_value& schema, const string& name) -> bool { + if (!schema.contains("required")) return false; + if (!schema.at("required").is_array()) return false; + for (auto& pname : schema.at("required")) { + if (pname.is_string() && pname.get_ref() == name) return true; + } + return false; + }; + auto has_commands = [](const json_value& schema) -> bool { + for (auto& [name, property] : schema.at("properties").items()) { + if (property.value("type", "") == "object") return true; + } + return false; + }; + + auto message = string{}; + auto usage_optional = string{}, usage_positional = string{}, + usage_command = string{}; + for (auto& [name, property] : schema.at("properties").items()) { + if (property.value("type", "") == "object") continue; + auto decorated_name = name; + auto positional = is_positional(schema, name); + if (!positional) { + decorated_name = "--" + name; + if (property.value("type", "") == "boolean") + decorated_name += "/--no-" + name; + if (property.contains("cli_alt")) + decorated_name += ", -" + property.value("cli_alt", ""); + } + auto line = " " + decorated_name; + if (property.value("type", "") != "boolean") { + line += " " + property.value("type", ""); + } + while (line.size() < 32) line += " "; + line += property.value("description", ""); + if (is_required(schema, name)) { + line += " [req]\n"; + } else if (property.contains("default")) { + line += " [" + format_json(property.at("default")) + "]\n"; + } else { + line += "\n"; + } + if (property.contains("enum")) { + line += " with choices: "; + auto len = 16; + for (auto& choice_ : property.at("enum")) { + auto choice = format_json(choice_); + if (len + choice.size() + 2 > 78) { + line += "\n "; + len = 16; + } + line += choice + ", "; + len += choice.size() + 2; + } + line = line.substr(0, line.size() - 2); + line += "\n"; + } + if (positional) { + usage_positional += line; + } else { + usage_optional += line; + } + } + if (has_commands(schema)) { + for (auto& [name, property] : schema.at("properties").items()) { + if (property.value("type", "") != "object") continue; + auto line = " " + name; + while (line.size() < 32) line += " "; + line += property.value("description", "") + "\n"; + usage_command += line; + } + } + + { + auto line = string{}; + line += " --help"; + while (line.size() < 32) line += " "; + line += "Prints an help message\n"; + usage_optional += line; + } + + message += "usage: " + path_basename(app_name); + if (!command.empty()) message += " " + command; + if (!usage_command.empty()) message += " command"; + if (!usage_optional.empty()) message += " [options]"; + if (!usage_positional.empty()) message += " "; + message += "\n"; + message += schema.value("description", "") + "\n\n"; + if (!usage_command.empty()) { + message += "commands:\n" + usage_command + "\n"; + } + if (!usage_optional.empty()) { + message += "options:\n" + usage_optional + "\n"; + } + if (!usage_positional.empty()) { + message += "arguments:\n" + usage_positional + "\n"; + } + return message; +} + +string get_command(const cli_state& cli) { return cli.command; } +bool get_help(const cli_state& cli) { return cli.help; } +string get_usage(const cli_state& cli) { return cli.usage; } + +static bool parse_clivalue( + json_value& value, const string& arg, const json_value& schema) { + // if (!choices.empty()) { + // if (std::find(choices.begin(), choices.end(), arg) == choices.end()) + // return false; + // } + auto type = schema.value("type", "string"); + if (type == "string") { + value = arg; + return true; + } else if (type == "integer") { + auto end = (char*)nullptr; + if (arg.find('-') == 0) { + value = (int64_t)strtol(arg.c_str(), &end, 10); + } else { + value = (uint64_t)strtoul(arg.c_str(), &end, 10); + } + return end != nullptr; + } else if (type == "number") { + auto end = (char*)nullptr; + value = strtod(arg.c_str(), &end); + return end != nullptr; + return true; + } else if (type == "boolean") { + if (arg == "true" || arg == "1") { + value = true; + return true; + } else if (arg == "false" || arg == "0") { + value = false; + return true; + } else { + return false; + } + } + return false; +} + +static bool parse_clivalue( + json_value& value, const vector& args, const json_value& schema) { + auto type = schema.value("type", "string"); + if (type == "array") { + value = json_array{}; + for (auto& arg : args) { + if (!parse_clivalue(value.emplace_back(), arg, schema.at("items"))) + return false; + } + return true; + } + return false; +} + +static const char* cli_help_message = "Help invoked"; + +bool parse_cli(json_value& value, const json_value& schema_, + const vector& args, string& error, string& usage, string& command) { + auto cli_error = [&error](const string& message) { + error = message; + return false; + }; + + // helpers + auto advance_positional = [](const json_value& schema, + size_t& last_positional) -> string { + if (!schema.contains("cli_positional")) return ""; + auto& positionals = schema.at("cli_positional"); + if (!positionals.is_array()) return ""; + if (positionals.size() == last_positional) return ""; + if (!positionals.at(last_positional).is_string()) return ""; + return positionals.at(last_positional++).get(); + }; + auto is_positional = [](const json_value& schema, + const string& name) -> bool { + if (!schema.contains("cli_positional")) return false; + if (!schema.at("cli_positional").is_array()) return false; + for (auto& pname : schema.at("cli_positional")) { + if (pname.is_string() && pname.get_ref() == name) return true; + } + return false; + }; + auto is_required = [](const json_value& schema, const string& name) -> bool { + if (!schema.contains("required")) return false; + if (!schema.at("required").is_array()) return false; + for (auto& pname : schema.at("required")) { + if (pname.is_string() && pname.get_ref() == name) return true; + } + return false; + }; + auto get_alternate = [](const json_value& schema, + const string& alt) -> string { + if (!schema.contains("cli_alternate")) return ""; + if (!schema.at("cli_alternate").is_object()) return ""; + if (!schema.at("cli_alternate").contains(alt)) return ""; + if (!schema.at("cli_alternate").at(alt).is_string()) return ""; + return schema.at("cli_alternate").at(alt).get(); + }; + auto get_command = [](const json_value& schema) -> string { + if (!schema.contains("cli_command")) return "$command"; + if (!schema.at("cli_command").is_string()) return "$command"; + return schema.at("cli_command").get(); + }; + auto has_commands = [](const json_value& schema) -> bool { + for (auto& [name, property] : schema.at("properties").items()) { + if (property.value("type", "") == "object") return true; + } + return false; + }; + + // parsing stack + struct stack_elem { + string name = ""; + json_value& schema; + json_value& value; + size_t positional = 0; + }; + + // initialize parsing + auto schema = fix_cli_schema(schema_); + value = json_object{}; + auto stack = vector{{"", schema, value, 0}}; + command = ""; + usage = get_cliusage(schema, args[0], command); + + // parse the command line + for (auto idx = (size_t)1; idx < args.size(); idx++) { + auto& [_, schema, value, cpositional] = stack.back(); + auto arg = args.at(idx); + auto positional = arg.find('-') != 0; + if (positional && has_commands(schema)) { + auto name = string{}; + for (auto& [pname, property] : schema.at("properties").items()) { + if (property.value("type", "string") != "object") continue; + if (pname != arg) continue; + name = arg; + } + if (name.empty()) return cli_error("missing value for command"); + value[get_command(schema)] = name; + value[name] = json_object{}; + stack.push_back( + {name, schema.at("properties").at(name), value.at(name), 0}); + command += (command.empty() ? "" : " ") + name; + usage = get_cliusage(stack.back().schema, args[0], command); + continue; + } else if (positional) { + auto name = string{}; + auto next_positional = advance_positional(schema, cpositional); + for (auto& [pname, property] : schema.at("properties").items()) { + if (property.value("type", "string") == "object") continue; + if (pname != next_positional) continue; + name = pname; + } + if (name.empty()) return cli_error("too many positional arguments"); + auto& property = schema.at("properties").at(name); + if (property.value("type", "string") == "array") { + auto array_args = vector(args.begin() + idx, args.end()); + if (!parse_clivalue(value[name], array_args, property)) + return cli_error("bad value for " + name); + idx = args.size(); + } else if (property.value("type", "string") != "object") { + if (!parse_clivalue(value[name], args[idx], property)) + return cli_error("bad value for " + name); + } + } else { + if (arg == "--help" || arg == "-?") { + return cli_error(cli_help_message); + } + arg = arg.substr(1); + if (arg.find('-') == 0) arg = arg.substr(1); + auto name = string{}; + for (auto& [pname, property] : schema.at("properties").items()) { + if (property.value("type", "string") == "object") continue; + if (property.value("type", "string") == "array") continue; + if (is_positional(schema, pname)) continue; + if (pname != arg && get_alternate(schema, pname) != arg && + pname != "no-" + arg) // TODO(fabio): fix boolean + continue; + name = pname; + break; + } + if (name.empty()) return cli_error("unknown option " + args[idx]); + if (value.contains(name)) return cli_error("option already set " + name); + auto& property = schema.at("properties").at(name); + if (property.value("type", "string") == "boolean") { + if (!parse_clivalue( + value[name], arg.find("no-") != 0 ? "true" : "false", property)) + return cli_error("bad value for " + name); + } else { + if (idx + 1 >= args.size()) + return cli_error("missing value for " + name); + if (!parse_clivalue(value[name], args[idx + 1], property)) + return cli_error("bad value for " + name); + idx += 1; + } + } + } + + // check for required and apply defaults + for (auto& [_, schema, value, __] : stack) { + if (has_commands(schema) && !value.contains(get_command(schema))) + return cli_error("missing value for " + get_command(schema)); + for (auto& [name, property] : schema.at("properties").items()) { + if (property.value("type", "string") == "object") continue; + if (is_required(schema, name) && !value.contains(name)) + return cli_error("missing value for " + name); + if (property.contains("default") && !value.contains(name)) + value[name] = property.at("default"); + } + } + + // done + return true; +} + +bool parse_cli(json_value& value, const json_value& schema, + const vector& args, string& error, string& usage) { + auto command = string{}; + return parse_cli(value, schema, args, error, usage, command); +} + +bool parse_cli(json_value& value, const json_value& schema, int argc, + const char** argv, string& error, string& usage) { + return parse_cli(value, schema, {argv, argv + argc}, error, usage); +} + +void parse_cli( + json_value& value, const json_value& schema, const vector& args) { + auto error = string{}; + auto usage = string{}; + if (!parse_cli(value, schema, args, error, usage)) { + if (error != cli_help_message) { + print_info("error: " + error); + print_info(""); + } + print_info(usage); + exit(error != cli_help_message ? 1 : 0); + } +} + +void parse_cli( + json_value& value, const json_value& schema, int argc, const char** argv) { + return parse_cli(value, schema, {argv, argv + argc}); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF JSON TREE DATA STRUCTURE +// ----------------------------------------------------------------------------- +namespace yocto { + +// convert json +void to_json(njson& njs, json_cview js) { + switch (get_type(js)) { + case json_type::null: njs = nullptr; break; + case json_type::integer: njs = get_integer(js); break; + case json_type::unsigned_: njs = get_unsigned(js); break; + case json_type::real: njs = get_real(js); break; + case json_type::boolean: njs = get_boolean(js); break; + case json_type::string_: njs = get_string(js); break; + case json_type::array: + njs = njson::array(); + for (auto ejs : iterate_array(js)) to_json(njs.emplace_back(), ejs); + break; + case json_type::object: + njs = njson::object(); + for (auto [key, ejs] : iterate_object(js)) to_json(njs[string{key}], ejs); + break; + case json_type::binary: + njs = njson::binary({}); + get_binary(js, njs.get_binary()); + break; + } +} + +// convert json +void from_json(const njson& njs, json_view js) { + switch (njs.type()) { + case njson::value_t::null: set_null(js); break; + case njson::value_t::number_integer: set_integer(js, (int64_t)njs); break; + case njson::value_t::number_unsigned: set_unsigned(js, njs); break; + case njson::value_t::number_float: set_real(js, njs); break; + case njson::value_t::boolean: set_boolean(js, (bool)njs); break; + case njson::value_t::string: set_string(js, (string)njs); break; + case njson::value_t::array: + set_array(js); + for (auto& ejs : njs) from_json(ejs, append_element(js)); + break; + case njson::value_t::object: + set_object(js); + for (auto& [key, ejs] : njs.items()) + from_json(ejs, insert_element(js, key)); + break; + case njson::value_t::binary: set_binary(js, njs.get_binary()); break; + case njson::value_t::discarded: set_null(js); break; + } +} + +#if YOCTO_JSON_SAX == 1 + +// load json +bool load_json(const string& filename, json_tree& js, string& error) { + // error helpers + auto parse_error = [filename, &error]() { + error = filename + ": parse error in json"; + return false; + }; + + // sax handler + struct sax_handler { + // stack + json_view root; + std::vector stack = {}; + std::string current_key; + explicit sax_handler(json_view root_) : root{root_}, stack{root_} {} + + // get current value + json_view next_value() { + if (stack.size() == 1) return root; + auto& jst = _get_type(stack.back()); + if (jst == json_type::array) return append_element(stack.back()); + if (jst == json_type::object) + return insert_element(stack.back(), current_key); + throw yocto::json_error{"bad json type"}; + } + + // values + bool null() { + set_null(next_value()); + return true; + } + bool boolean(bool value) { + set_boolean(next_value(), value); + return true; + } + bool number_integer(int64_t value) { + set_integer(next_value(), value); + return true; + } + bool number_unsigned(uint64_t value) { + set_unsigned(next_value(), value); + return true; + } + bool number_float(double value, const std::string&) { + set_real(next_value(), value); + return true; + } + bool string(std::string& value) { + set_string(next_value(), value); + return true; + } + bool binary(std::vector& value) { + set_binary(next_value(), value); + return true; + } + + // objects + bool start_object(size_t elements) { + set_object(next_value()); + stack.push_back(next_value()); + return true; + } + bool end_object() { + stack.pop_back(); + return true; + } + bool key(std::string& value) { + current_key = value; + return true; + } + + // arrays + bool start_array(size_t elements) { + set_array(next_value()); + stack.push_back(next_value()); + return true; + } + bool end_array() { + stack.pop_back(); + return true; + } + + bool parse_error(size_t position, const std::string& last_token, + const nlohmann::detail::exception&) { + return false; + } + }; + + // set up parsing + js = json_tree{}; + auto handler = sax_handler{get_root(js)}; + + // load text + auto text = ""s; + if (!load_text(filename, text, error)) return false; + + // parse json + if (!njson::sax_parse(text, &handler)) return parse_error(); + return true; +} + +#else + +// load json +bool load_json(const string& filename, json_tree& js, string& error) { + // parse json + auto njs = njson{}; + if (!load_json(filename, njs, error)) return false; + + // convert + from_json(njs, get_root(js)); + return true; +} + +#endif + +// save json +bool save_json(const string& filename, const json_tree& js, string& error) { + // convert + auto njs = njson{}; + to_json(njs, get_root((json_tree&)js)); + + // save + return save_json(filename, njs, error); +} + +} // namespace yocto diff --git a/libs/yocto/yocto_json.h b/libs/yocto/yocto_json.h new file mode 100644 index 000000000..4a253ccef --- /dev/null +++ b/libs/yocto/yocto_json.h @@ -0,0 +1,2146 @@ +// +// # Yocto/JSON: Utilities for manipulating JSON data +// +// Yocto/JSON is an implementation of a utilities for hanlding JSON data, +// including a Json variant data type, loading, saving and formatting JSON, +// and a parser for command line arguments to JSON. +// Compared to other libraries, it is more lightweight and provides a common +// implementation for the rest of Yocto/GL. +// Yocto/JSON is implemented in `yocto_json.h` and `yocto_json.cpp`, and +// depends on `json.hpp`. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2020 Fabio Pellacini +// +// 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 _YOCTO_JSON_H_ +#define _YOCTO_JSON_H_ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::array; +using std::function; +using std::pair; +using std::string; +using std::string_view; +using std::unordered_map; +using std::vector; +using namespace std::string_literals; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON DATA TYPE +// ----------------------------------------------------------------------------- +namespace yocto { + +// Json type +enum struct json_type { + // clang-format off + null, integer, unsigned_, real, boolean, string_, array, object, binary + // clang-format on +}; + +// Json forward declarations +struct json_value; +using json_array = vector; +using json_object = vector>; +using json_binary = vector; +using json_iterator = json_value*; +using json_citerator = const json_value*; +using json_oiterator = pair*; +using json_ociterator = const pair*; + +// Json type error +struct json_error : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +// Json value +struct json_value { + // constructors + json_value(); + json_value(const json_value& other); + json_value(json_value&& other); + explicit json_value(std::nullptr_t); + explicit json_value(int32_t); + explicit json_value(int64_t); + explicit json_value(uint32_t); + explicit json_value(uint64_t); + explicit json_value(float); + explicit json_value(double); + explicit json_value(bool); + explicit json_value(const string&); + explicit json_value(string_view); + explicit json_value(const char* value); + explicit json_value(const json_array&); + explicit json_value(const json_object&); + explicit json_value(const json_binary&); + template + explicit json_value(const T& value); + + // assignments + json_value& operator=(const json_value& other); + json_value& operator=(json_value&& other); + template + json_value& operator=(const T& value); + + // type + json_type type() const; + bool is_null() const; + bool is_integer() const; + bool is_unsigned() const; + bool is_real() const; + bool is_integral() const; + bool is_number() const; + bool is_boolean() const; + bool is_string() const; + bool is_array() const; + bool is_object() const; + bool is_binary() const; + + // conversions (see get) + explicit operator int32_t() const; + explicit operator int64_t() const; + explicit operator uint32_t() const; + explicit operator uint64_t() const; + explicit operator float() const; + explicit operator double() const; + explicit operator bool() const; + explicit operator string() const; + explicit operator string_view() const; + template + explicit operator T() const; + + // get values via conversion + template + T get() const; + template + void get_to(T& value) const; + + // access references + template + T& get_ref(); + template + const T& get_ref() const; + + // structure support + bool empty() const; + size_t size() const; + void clear(); + void resize(size_t size); + void reserve(size_t size); + void update(const json_value& other); + + // elemnt acceess + bool contains(const string& key) const; + json_value& operator[](size_t idx); + json_value& operator[](const string& key); + json_value& at(size_t idx); + const json_value& at(size_t idx) const; + json_value& at(const string& key); + const json_value& at(const string& key) const; + json_value& front(); + const json_value& front() const; + json_value& back(); + const json_value& back() const; + void push_back(const json_value& value); + void push_back(json_value&& value); + template + void push_back(const T& value); + template + json_value& emplace_back(Args&&... args); + template + json_value& emplace(Args&&... args); + + // iteration + json_iterator begin(); + json_citerator begin() const; + json_iterator end(); + json_citerator end() const; + struct json_object_it; + struct json_object_cit; + json_object_it items(); + json_object_cit items() const; + json_iterator find(const string& key); + json_citerator find(const string& key) const; + + // get value at an object key + template + T value(const string& key, const T& default_) const; + string value(const string& key, const char* default_) const; + + // array/object/binary creation + static json_value array(); + static json_value object(); + static json_value binary(); + + // swap + void swap(json_value& other); + +#ifdef __APPLE__ + explicit json_value(size_t); + explicit operator size_t() const; +#endif + + // destructor + ~json_value(); + + json_type _type = json_type::null; + union { + int64_t _integer; + uint64_t _unsigned; + double _real; + bool _boolean; + string* _string; + json_array* _array; + json_object* _object; + json_binary* _binary; + }; +}; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON IO +// ----------------------------------------------------------------------------- +namespace yocto { + +// Load/save a json file +bool load_json(const string& filename, json_value& js, string& error); +bool save_json(const string& filename, const json_value& js, string& error); + +// Formats/parse a Json to/from string +bool parse_json(const string& text, json_value& js, string& error); +bool format_json(string& text, const json_value& js, string& error); +string format_json(const json_value& js); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON CONVERSION +// ----------------------------------------------------------------------------- +namespace yocto { + +// Conversion shortcuts +template +inline T from_json(const json_value& js); +template +inline json_value to_json(const T& value); +template +inline bool from_json(const json_value& js, T& value, json_error& error); +template +inline bool to_json(const json_value& js, T& value, json_error& error); + +// Conversion from json to values +inline void from_json(const json_value& js, int64_t& value); +inline void from_json(const json_value& js, int32_t& value); +inline void from_json(const json_value& js, uint64_t& value); +inline void from_json(const json_value& js, uint32_t& value); +inline void from_json(const json_value& js, double& value); +inline void from_json(const json_value& js, float& value); +inline void from_json(const json_value& js, bool& value); +inline void from_json(const json_value& js, string& value); +template +inline void from_json(const json_value& js, vector& value); +template +inline void from_json(const json_value& js, array& value); + +// Conversion to json from values +inline void to_json(json_value& js, int64_t value); +inline void to_json(json_value& js, int32_t value); +inline void to_json(json_value& js, uint64_t value); +inline void to_json(json_value& js, uint32_t value); +inline void to_json(json_value& js, double value); +inline void to_json(json_value& js, float value); +inline void to_json(json_value& js, bool value); +inline void to_json(json_value& js, const string& value); +template +inline void to_json(json_value& js, const vector& value); +template +inline void to_json(json_value& js, const array& value); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON VALIDATION +// ----------------------------------------------------------------------------- +namespace yocto { + +// Validate a value against a schema +bool validate_json( + const json_value& value, const json_value& schema, string& error); +bool validate_json(const json_value& value, const json_value& schema, + vector& errors, size_t max_errors = 100); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// COMMAND LINE PARSING +// ----------------------------------------------------------------------------- +namespace yocto { + +// Parse the command line described by a JSON schema. +// Checks for errors, and exits on error or help. +struct json_value; +void parse_cli( + json_value& value, const json_value& schema, int argc, const char** argv); +void parse_cli( + json_value& value, const json_value& schema, const vector& args); +// Parse the command line described by a schema. +bool parse_cli(json_value& value, const json_value& schema, + const vector& args, string& error, string& usage); +bool parse_cli(json_value& value, const json_value& schema, int argc, + const char** argv, string& error, string& usage); + +// Low-level parsing routine +bool parse_cli(json_value& value, const json_value& schema, + const vector& args, string& error, string& usage, string& command); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON TREE DATA TYPE +// ----------------------------------------------------------------------------- +namespace yocto { + +// Declarations +struct json_view; +struct json_cview; + +// Json tree +struct json_tree { + union json_value { + int64_t _integer = 0; + uint64_t _unsigned; + double _real; + bool _boolean; + struct { + uint32_t start; + uint32_t length; + } _string; + struct { + uint32_t length; + uint32_t skip; + } _array; + struct { + uint32_t length; + uint32_t skip; + } _object; + struct { + uint32_t start; + uint32_t length; + } _binary; + }; + vector types = {json_type::null}; + vector values = {json_value{}}; + vector strings = {}; + vector keys = {}; + vector key_list = {}; + vector binaries = {}; + vector build_skip_stack = {}; + bool valid = true; + string error = ""; +}; + +// Load/save a json file +bool load_json(const string& filename, json_tree& js, string& error); +bool save_json(const string& filename, const json_tree& js, string& error); + +// Get view from value +inline json_view get_root(json_tree& js); +inline json_cview get_croot(json_tree& js); + +// Error handling +inline void set_error(json_tree& js, string_view error); +inline void clear_error(json_tree& js); + +// Json view +struct json_view { + json_tree* root = nullptr; + uint32_t index = 0; + json_view(json_tree* root_) : root{root_}, index{(uint32_t)-1} {} + json_view(json_tree* root_, uint32_t index_) : root{root_}, index{index_} {} +}; +struct json_cview { + json_tree* root = nullptr; + uint32_t index = 0; + json_cview(json_tree* root_) : root{root_}, index{(uint32_t)-1} {} + json_cview(json_tree* root_, uint32_t index_) : root{root_}, index{index_} {} + json_cview(json_view other) : root{other.root}, index{other.index} {} +}; + +// Error check +inline bool is_valid(json_cview js); +inline bool is_valid(json_view js); +inline string get_error(json_cview js); +inline string get_error(json_view js); +inline string compute_path(json_cview js); +inline bool set_error(json_cview js, string_view error); + +// Type +inline json_type get_type(json_cview js); +// Type +inline bool is_null(json_cview js); +inline bool is_integer(json_cview js); +inline bool is_unsigned(json_cview js); +inline bool is_real(json_cview js); +inline bool is_integral(json_cview js); +inline bool is_number(json_cview js); +inline bool is_boolean(json_cview js); +inline bool is_string(json_cview js); +inline bool is_array(json_cview js); +inline bool is_object(json_cview js); +inline bool is_binary(json_cview js); + +// Initialization to basic types +inline bool set_null(json_view js); +inline bool set_integer(json_view js, int64_t value); +inline bool set_unsigned(json_view js, uint64_t value); +inline bool set_real(json_view js, double value); +inline bool set_boolean(json_view js, bool value); +inline bool set_string(json_view js, const string& value); +inline bool set_integral(json_view js, int64_t value); +inline bool set_integral(json_view js, int32_t value); +inline bool set_integral(json_view js, uint64_t value); +inline bool set_integral(json_view js, uint32_t value); +inline bool set_number(json_view js, double value); +inline bool set_number(json_view js, float value); + +// Get basic values +inline bool get_integer(json_cview js, int64_t& value); +inline bool get_unsigned(json_cview js, uint64_t& value); +inline bool get_real(json_cview js, double& value); +inline bool get_boolean(json_cview js, bool& value); +inline bool get_string(json_cview js, string& value); +inline bool get_integral(json_cview js, int64_t& value); +inline bool get_integral(json_cview js, uint64_t& value); +inline bool get_number(json_cview js, double& value); +inline bool get_integral(json_cview js, int32_t& value); +inline bool get_integral(json_cview js, uint32_t& value); +inline bool get_number(json_cview js, float& value); + +// Get basic values - ignore errors if present +inline int64_t get_integer(json_cview js); +inline uint64_t get_unsigned(json_cview js); +inline double get_real(json_cview js); +inline bool get_boolean(json_cview js); +inline string get_string(json_cview js); +inline int64_t get_integral(json_cview js); +inline uint64_t get_uintegral(json_cview js); +inline double get_number(json_cview js); + +// Compound type +inline bool is_empty(json_cview js); +inline size_t get_size(json_cview js); + +// Array +inline bool set_array(json_view js); +inline bool set_array(json_view js, size_t size); +inline bool array_size(json_cview js, size_t& size); +inline bool has_element(json_view js, size_t idx); +inline bool has_element(json_cview js, size_t idx); +inline json_view get_element(json_view js, size_t idx); +inline json_cview get_element(json_cview js, size_t idx); +inline json_view append_element(json_view js); +inline auto iterate_array(json_view js); +inline auto iterate_array(json_cview js); + +// Object +inline bool set_object(json_view js); +inline bool object_size(json_cview js, size_t& size); +inline bool has_element(json_view js, string_view key); +inline bool has_element(json_cview js, string_view key); +inline json_view get_element(json_view js, string_view key); +inline json_cview get_element(json_cview js, string_view key); +inline json_view insert_element(json_view js, string_view key); +inline auto iterate_object(json_view js); +inline auto iterate_object(json_cview js); + +// Binary +inline bool set_binary(json_view js, const json_binary& value); +inline bool get_binary(json_cview js, json_binary& value); + +// Get the path of a json view +inline string compute_path(json_cview js); + +// Conversion from json to values +template +inline bool get_value(json_cview js, T& value); + +// Conversion from json to values +inline bool get_value(json_cview js, int64_t& value); +inline bool get_value(json_cview js, int32_t& value); +inline bool get_value(json_cview js, uint64_t& value); +inline bool get_value(json_cview js, uint32_t& value); +inline bool get_value(json_cview js, double& value); +inline bool get_value(json_cview js, float& value); +inline bool get_value(json_cview js, bool& value); +inline bool get_value(json_cview js, string& value); +template +inline bool get_value(json_cview js, vector& value); +template +inline bool get_value(json_cview js, array& value); + +// Get value at a key or index +template +inline bool get_value_at(json_cview js, string_view key, T& value); +template +inline bool get_value_at(json_cview js, size_t idx, T& value); + +// Get value at a key or nothing is key is not preesent +template +inline bool get_value_if(json_cview js, string_view key, T& value); + +// Conversion to json from values +template +inline bool set_value(json_view js, const T& value); + +// Conversion to json from values +inline bool set_value(json_view js, int64_t value); +inline bool set_value(json_view js, int32_t value); +inline bool set_value(json_view js, uint64_t value); +inline bool set_value(json_view js, uint32_t value); +inline bool set_value(json_view js, double value); +inline bool set_value(json_view js, float value); +inline bool set_value(json_view js, bool value); +inline bool set_value(json_view js, const string& value); +inline bool set_value(json_view js, const char* value); +template +inline bool set_value(json_view js, const vector& value); +template +inline bool set_value(json_view js, const array& value); + +// Helpers for user-defined types +inline bool check_array(json_cview js); +inline bool check_array(json_cview js, size_t size_); +inline bool check_object(json_cview js); + +// Helpers for user-defined types +inline bool set_array(json_view js); +template +inline bool set_value_at(json_view js, size_t idx, const T& value); +template +inline bool append_value(json_view js, const T& value); +inline json_view append_array(json_view js); +inline json_view append_object(json_view js); + +// Helpers for user-defined types +inline bool set_object(json_view js); +template +inline bool set_value_at(json_view js, string_view key, const T& value); +template +inline bool insert_value(json_view js, string_view key, const T& value); +template +inline bool insert_value_if( + json_view js, string_view key, const T& value, const T& default_); +inline json_view insert_array(json_view js, string_view key); +inline json_view insert_object(json_view js, string_view key); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// +// +// IMPLEMENTATION +// +// +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// JSON DATA TYPE +// ----------------------------------------------------------------------------- +namespace yocto { + +// constructors +inline json_value::json_value() : _type{json_type::null}, _unsigned{0} {} +inline json_value::json_value(const json_value& other) + : _type{json_type::null}, _unsigned{0} { + switch (other._type) { + case json_type::null: + _type = json_type::null; + _integer = other._integer; + break; + case json_type::integer: + _type = json_type::integer; + _integer = other._integer; + break; + case json_type::unsigned_: + _type = json_type::unsigned_; + _unsigned = other._unsigned; + break; + case json_type::real: + _type = json_type::real; + _real = other._real; + break; + case json_type::boolean: + _type = json_type::boolean; + _boolean = other._boolean; + break; + case json_type::string_: + _type = json_type::string_; + _string = new string{*other._string}; + break; + case json_type::array: + _type = json_type::array; + _array = new json_array{*other._array}; + break; + case json_type::object: + _type = json_type::object; + _object = new json_object{*other._object}; + break; + case json_type::binary: + _type = json_type::binary; + _binary = new json_binary{*other._binary}; + break; + } +} +inline json_value::json_value(json_value&& other) + : _type{json_type::null}, _unsigned{0} { + swap(other); +} +inline json_value::json_value(std::nullptr_t) + : _type{json_type::null}, _unsigned{0} {} +inline json_value::json_value(int64_t value) + : _type{json_type::integer}, _integer{value} {} +inline json_value::json_value(int32_t value) + : _type{json_type::integer}, _integer{value} {} +inline json_value::json_value(uint64_t value) + : _type{json_type::unsigned_}, _unsigned{value} {} +inline json_value::json_value(uint32_t value) + : _type{json_type::unsigned_}, _unsigned{value} {} +inline json_value::json_value(double value) + : _type{json_type::real}, _real{value} {} +inline json_value::json_value(float value) + : _type{json_type::real}, _real{value} {} +inline json_value::json_value(bool value) + : _type{json_type::boolean}, _boolean{value} {} +inline json_value::json_value(const string& value) + : _type{json_type::string_}, _string{new string{value}} {} +inline json_value::json_value(string_view value) + : _type{json_type::string_}, _string{new string{value}} {} +inline json_value::json_value(const char* value) + : _type{json_type::string_}, _string{new string{value}} {} +inline json_value::json_value(const json_array& value) + : _type{json_type::array}, _array{new json_array{value}} {} +inline json_value::json_value(const json_object& value) + : _type{json_type::object}, _object{new json_object{value}} {} +inline json_value::json_value(const json_binary& value) + : _type{json_type::binary}, _binary{new json_binary{value}} {} +template +inline json_value::json_value(const T& value) { + to_json(*this, value); +} +#ifdef __APPLE__ +inline json_value::json_value(size_t value) + : _type{json_type::unsigned_}, _unsigned{(uint64_t)value} {} +#endif + +// assignments +inline json_value& json_value::operator=(const json_value& value) { + auto js = json_value{value}; + this->swap(js); + return *this; +} +inline json_value& json_value::operator=(json_value&& value) { + this->swap(value); + return *this; +} +template +inline json_value& json_value::operator=(const T& value) { + auto js = json_value{value}; + this->swap(js); + return *this; +} + +// type +inline json_type json_value::type() const { return _type; } +inline bool json_value::is_null() const { return _type == json_type::null; } +inline bool json_value::is_integer() const { + return _type == json_type::integer; +} +inline bool json_value::is_unsigned() const { + return _type == json_type::unsigned_; +} +inline bool json_value::is_real() const { return _type == json_type::real; } +inline bool json_value::is_integral() const { + return _type == json_type::integer || _type == json_type::unsigned_; +} +inline bool json_value::is_number() const { + return _type == json_type::real || _type == json_type::integer || + _type == json_type::unsigned_; +} +inline bool json_value::is_boolean() const { + return _type == json_type::boolean; +} +inline bool json_value::is_string() const { + return _type == json_type::string_; +} +inline bool json_value::is_array() const { return _type == json_type::array; } +inline bool json_value::is_object() const { return _type == json_type::object; } +inline bool json_value::is_binary() const { return _type == json_type::binary; } + +// conversions +inline json_value::operator int64_t() const { + if (_type != json_type::integer && _type != json_type::unsigned_) + throw json_error{"integer expected"}; + return _type == json_type::integer ? (int64_t)_integer : (int64_t)_unsigned; +} +inline json_value::operator int32_t() const { + if (_type != json_type::integer && _type != json_type::unsigned_) + throw json_error{"integer expected"}; + return _type == json_type::integer ? (int32_t)_integer : (int32_t)_unsigned; +} +inline json_value::operator uint64_t() const { + if (_type != json_type::integer && _type != json_type::unsigned_) + throw json_error{"integer expected"}; + return _type == json_type::integer ? (uint64_t)_integer : (uint64_t)_unsigned; +} +inline json_value::operator uint32_t() const { + if (_type != json_type::integer && _type != json_type::unsigned_) + throw json_error{"integer expected"}; + return _type == json_type::integer ? (uint32_t)_integer : (uint32_t)_unsigned; +} +inline json_value::operator double() const { + if (_type != json_type::real && _type != json_type::integer && + _type != json_type::unsigned_) + throw json_error{"number expected"}; + return _type == json_type::real + ? (double)_real + : _type == json_type::integer ? (double)_integer + : (double)_unsigned; +} +inline json_value::operator float() const { + if (_type != json_type::real && _type != json_type::integer && + _type != json_type::unsigned_) + throw json_error{"number expected"}; + return _type == json_type::real + ? (float)_real + : _type == json_type::integer ? (float)_integer : (float)_unsigned; +} +inline json_value::operator bool() const { + if (_type != json_type::boolean) throw json_error{"boolean expected"}; + return _boolean; +} +inline json_value::operator string() const { + if (_type != json_type::string_) throw json_error{"string expected"}; + return *_string; +} +inline json_value::operator string_view() const { + if (_type != json_type::string_) throw json_error{"string expected"}; + return *_string; +} +template +inline json_value::operator T() const { + auto value = T{}; + from_json(*this, value); + return value; +} +#ifdef __APPLE__ +inline json_value::operator size_t() const { + return (size_t) operator uint64_t(); +} +#endif + +// conversions +template +inline T json_value::get() const { + return operator T(); +} +template +inline void json_value::get_to(T& value) const { + value = operator T(); +} + +// access +template +inline T& json_value::get_ref() { + static_assert( + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v, + "type not in the json variant"); + if constexpr (std::is_same_v) { + if (_type != json_type::integer) throw json_error{"integer expected"}; + return _integer; + } else if constexpr (std::is_same_v) { + if (_type != json_type::unsigned_) throw json_error{"unsigned expected"}; + return _unsigned; + } else if constexpr (std::is_same_v) { + if (_type != json_type::real) throw json_error{"real expected"}; + return _real; + } else if constexpr (std::is_same_v) { + if (_type != json_type::boolean) throw json_error{"boolean expected"}; + return _boolean; + } else if constexpr (std::is_same_v) { + if (_type != json_type::string_) throw json_error{"string expected"}; + return *_string; + } else if constexpr (std::is_same_v) { + if (_type != json_type::array) throw json_error{"array expected"}; + return *_array; + } else if constexpr (std::is_same_v) { + if (_type != json_type::object) throw json_error{"object expected"}; + return *_object; + } else if constexpr (std::is_same_v) { + if (_type != json_type::binary) throw json_error{"binary expected"}; + return *_binary; + } else { + // will never get here + } +} +// access +template +inline const T& json_value::get_ref() const { + return ((json_value*)this)->get_ref(); // const cast +} + +// structure support +inline bool json_value::empty() const { + switch (_type) { + case json_type::null: return true; + case json_type::array: return _array->empty(); + case json_type::object: return _object->empty(); + default: return false; + } +} +inline size_t json_value::size() const { + switch (_type) { + case json_type::null: return 0; + case json_type::array: return _array->size(); + case json_type::object: return _object->size(); + default: return 1; + } +} +inline void json_value::clear() { + switch (_type) { + case json_type::null: _unsigned = 0; break; + case json_type::integer: _integer = 0; break; + case json_type::unsigned_: _unsigned = 0; break; + case json_type::real: _real = 0; break; + case json_type::boolean: _boolean = false; break; + case json_type::string_: _string->clear(); break; + case json_type::array: _array->clear(); break; + case json_type::object: _object->clear(); break; + case json_type::binary: _binary->clear(); break; + default: break; + } +} +inline void json_value::resize(size_t size) { + switch (_type) { + case json_type::array: _array->resize(size); break; + default: throw json_error{"array expected"}; + } +} +inline void json_value::reserve(size_t size) { + switch (_type) { + case json_type::array: _array->reserve(size); break; + case json_type::object: _object->reserve(size); break; + default: throw json_error{"structure expected"}; + } +} + +// array support +inline json_value& json_value::operator[](size_t idx) { + if (_type == json_type::null) *this = json_array{}; + if (_type != json_type::array) throw json_error{"array expected"}; + if (idx >= _array->size()) throw json_error{"index out of range"}; + return _array->operator[](idx); +} +inline json_value& json_value::operator[](const string& key) { + if (_type == json_type::null) *this = json_object{}; + if (_type != json_type::object) throw json_error{"object expected"}; + if (auto elem = find(key); elem != nullptr) return *elem; + return _object->emplace_back(key, json_value{}).second; +} +inline json_value& json_value::at(size_t idx) { + if (_type != json_type::array) throw json_error{"array expected"}; + if (idx >= _array->size()) throw json_error{"index out of range"}; + return _array->operator[](idx); +} +inline const json_value& json_value::at(size_t idx) const { + if (_type != json_type::array) throw json_error{"array expected"}; + if (idx >= _array->size()) throw json_error{"index out of range"}; + return _array->operator[](idx); +} +inline json_value& json_value::at(const string& key) { + if (_type != json_type::object) throw json_error{"object expected"}; + if (auto elem = find(key); elem != nullptr) return *elem; + throw json_error{"missing key " + key}; +} +inline const json_value& json_value::at(const string& key) const { + if (_type != json_type::object) throw json_error{"object expected"}; + if (auto elem = find(key); elem != nullptr) return *elem; + throw json_error{"missing key " + key}; +} +inline json_value& json_value::front() { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->front(); +} +inline const json_value& json_value::front() const { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->front(); +} +inline json_value& json_value::back() { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->back(); +} +inline const json_value& json_value::back() const { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->back(); +} +inline void json_value::push_back(const json_value& value) { + if (_type == json_type::null) *this = json_array{}; + if (_type != json_type::array) throw json_error{"array expected"}; + _array->push_back(value); +} +inline void json_value::push_back(json_value&& value) { + if (_type == json_type::null) *this = json_array{}; + if (_type != json_type::array) throw json_error{"array expected"}; + _array->push_back(std::move(value)); +} +template +inline void json_value::push_back(const T& value) { + return push_back(json_value{value}); +} +template +inline json_value& json_value::emplace_back(Args&&... args) { + if (_type == json_type::null) *this = json_array{}; + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->emplace_back(std::forward(args)...); +} +template +inline json_value& json_value::emplace(Args&&... args) { + if (_type == json_type::null) *this = json_object{}; + if (_type != json_type::object) throw json_error{"object expected"}; + return _array->emplace_back(std::forward(args)...); +} +inline void json_value::update(const json_value& other) { + if (_type == json_type::null) *this = json_object{}; + if (_type != json_type::object) throw json_error{"object expected"}; + if (other._type != json_type::object) throw json_error{"object expected"}; + for (auto& [key, value] : *other._object) this->operator[](key) = value; +} + +// Iteration +inline json_value* json_value::begin() { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->data(); +} +inline const json_value* json_value::begin() const { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->data(); +} +inline json_value* json_value::end() { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->data() + _array->size(); +} +inline const json_value* json_value::end() const { + if (_type != json_type::array) throw json_error{"array expected"}; + return _array->data() + _array->size(); +} +struct json_value::json_object_it { + pair* _begin; + pair* _end; + pair* begin() { return _begin; } + pair* end() { return _end; } +}; +struct json_value::json_object_cit { + const pair* _begin; + const pair* _end; + const pair* begin() { return _begin; } + const pair* end() { return _end; } +}; +inline json_value::json_object_it json_value::items() { + if (_type != json_type::object) throw json_error{"object expected"}; + return {_object->data(), _object->data() + _object->size()}; +} +inline json_value::json_object_cit json_value::items() const { + return {_object->data(), _object->data() + _object->size()}; +} +inline json_value* json_value::find(const string& key) { + if (_type != json_type::object) throw json_error{"object expected"}; + for (auto& [key_, value] : *_object) { + if (key_ == key) return &value; + } + return nullptr; +} +inline const json_value* json_value::find(const string& key) const { + if (_type != json_type::object) throw json_error{"object expected"}; + for (auto& [key_, value] : *_object) { + if (key_ == key) return &value; + } + return nullptr; +} +inline bool json_value::contains(const string& key) const { + return find(key) != nullptr; +} + +// get value at an object key +template +inline T json_value::value(const string& key, const T& default_) const { + if (_type != json_type::object) throw json_error{"object expected"}; + auto element = find(key); + return element ? element->get() : default_; +} +inline string json_value::value(const string& key, const char* default_) const { + return value(key, default_); +} + +// array/object/binary creation +inline json_value json_value::array() { return json_value{json_array{}}; } +inline json_value json_value::object() { return json_value{json_object{}}; } +inline json_value json_value::binary() { return json_value{json_binary{}}; } + +// swap +inline void json_value::swap(json_value& other) { + std::swap(_type, other._type); + std::swap(_unsigned, other._unsigned); // hask to swap bits +} + +// destructor +inline json_value::~json_value() { + switch (_type) { + case json_type::string_: delete _string; break; + case json_type::array: delete _array; break; + case json_type::object: delete _object; break; + case json_type::binary: delete _binary; break; + default: break; + } + _type = json_type::null; + _unsigned = 0; +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON CONVERSION +// ----------------------------------------------------------------------------- +namespace yocto { + +// Conversion shortcuts +template +inline T from_json(const json_value& js) { + auto value = T{}; + from_json(js, value); + return value; +} +template +inline json_value to_json(const T& value) { + auto js = json_value{}; + to_json(js, value); + return js; +} +template +inline bool from_json(const json_value& js, T& value, json_error& error) { + try { + from_json(js, value); + return true; + } catch (json_error& err) { + error = err; + return false; + } +} +template +inline bool to_json(const json_value& js, T& value, json_error& error) { + try { + to_json(js, value); + return true; + } catch (json_error& err) { + error = err; + return false; + } +} + +// Conversion from json to values +inline void from_json(const json_value& js, int64_t& value) { + value = (int64_t)js; +} +inline void from_json(const json_value& js, int32_t& value) { + value = (int32_t)js; +} +inline void from_json(const json_value& js, uint64_t& value) { + value = (uint64_t)js; +} +inline void from_json(const json_value& js, uint32_t& value) { + value = (uint32_t)js; +} +inline void from_json(const json_value& js, double& value) { + value = (double)js; +} +inline void from_json(const json_value& js, float& value) { value = (float)js; } +inline void from_json(const json_value& js, bool& value) { value = (bool)js; } +inline void from_json(const json_value& js, string& value) { + value = (string)js; +} +template +inline void from_json(const json_value& js, vector& value) { + value.clear(); + value.reserve(js.size()); + for (auto& ejs : js) from_json(ejs, value.emplace_back()); +} +template +inline void from_json(const json_value& js, array& value) { + if (js.size() != value.size()) throw json_error{"wrong array size"}; + for (auto idx = (size_t)0; idx < value.size(); idx++) + from_json(js.at(idx), value.at(idx)); +} + +// Conversion to json from values +inline void to_json(json_value& js, int64_t value) { js = value; } +inline void to_json(json_value& js, int32_t value) { js = value; } +inline void to_json(json_value& js, uint64_t value) { js = value; } +inline void to_json(json_value& js, uint32_t value) { js = value; } +inline void to_json(json_value& js, double value) { js = value; } +inline void to_json(json_value& js, float value) { js = value; } +inline void to_json(json_value& js, bool value) { js = value; } +inline void to_json(json_value& js, const string& value) { js = value; } +template +inline void to_json(json_value& js, const vector& value) { + js = json_array{}; + for (auto& v : value) to_json(js.emplace_back(), v); +} +template +inline void to_json(json_value& js, const array& value) { + js = json_array{}; + js.resize(value.size()); + for (auto idx = (size_t)0; idx < value.size(); idx++) + to_json(js.at(idx), value.at(idx)); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// JSON TREE DATA TYPE +// ----------------------------------------------------------------------------- +namespace yocto { + +// Get view from value +inline json_view get_root(json_tree& js) { return {&js, 0}; } +inline json_cview get_croot(json_tree& js) { return {&js, 0}; } +inline bool set_error(json_cview js, string_view error) { + if (!is_valid(js)) return false; + set_error(*js.root, string{error} + " at " + compute_path(js)); + return false; +} +inline json_view set_error_view(json_cview js, string_view error) { + if (!is_valid(js)) return {js.root}; + set_error(*js.root, string{error} + " at " + compute_path(js)); + return {js.root}; +} + +// Error handling +inline void set_error(json_tree& js, string_view error) { + if (!js.valid) return; + js.valid = false; + js.error = string{error}; +} +inline void clear_error(json_tree& js) { + js.valid = true; + js.error = ""; +} + +// Helpers +inline json_type& _get_type(json_view js) { + if (!is_valid(js)) throw std::invalid_argument{"bad json"}; + return js.root->types[js.index]; +} +inline const json_type& _get_type(json_cview js) { + if (!is_valid(js)) throw std::invalid_argument{"bad json"}; + return js.root->types[js.index]; +} +inline json_tree::json_value& _get_value(json_view js) { + if (!is_valid(js)) throw std::invalid_argument{"bad json"}; + return js.root->values[js.index]; +} +inline const json_tree::json_value& _get_value(json_cview js) { + if (!is_valid(js)) throw std::invalid_argument{"bad json"}; + return js.root->values[js.index]; +} +inline uint32_t _get_capacity(uint32_t length) { + if (length == 0) return 0; + if (length <= 4) return 4; + // TODO(fabio): faster pow2 + auto capacity = (uint32_t)4; + while (capacity < length) capacity *= 2; + return capacity; +} +inline string_view _get_key(json_cview js) { + if (!is_valid(js)) throw std::invalid_argument{"bad tree"}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::string_) throw std::invalid_argument{"bad key"}; + return {js.root->keys.data() + jsv._string.start, jsv._string.length}; +} +inline void _find_path(json_view js, vector& path); + +// Error check +inline bool is_valid(json_cview js) { + return js.root != nullptr && js.root->valid && js.index != (uint32_t)-1; +} +inline bool is_valid(json_view js) { + return js.root != nullptr && js.root->valid && js.index != (uint32_t)-1; +} +inline string get_error(json_cview js) { + if (js.root == nullptr) return "bad root"; + if (js.root->valid) return ""; + return js.root->error; +} +inline string get_error(json_view js) { + if (js.root == nullptr) return "bad root"; + if (js.root->valid) return ""; + return js.root->error; +} + +// Type +inline json_type get_type(json_cview js) { + if (!is_valid(js)) return json_type::null; + return _get_type(js); +} +inline bool is_null(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::null; +} +inline bool is_integer(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::integer; +} +inline bool is_unsigned(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::unsigned_; +} +inline bool is_real(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::real; +} +inline bool is_integral(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::integer || jst == json_type::unsigned_; +} +inline bool is_number(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::integer || jst == json_type::unsigned_ || + jst == json_type::real; +} +inline bool is_boolean(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::boolean; +} +inline bool is_string(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::string_; +} +inline bool is_array(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::array; +} +inline bool is_object(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::object; +} +inline bool is_binary(json_cview js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + return jst == json_type::binary; +} + +// Initialization to basic types +inline bool set_null(json_view js) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + jst = json_type::null; + return true; +} +inline bool set_integer(json_view js, int64_t value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::integer; + jsv._integer = value; + return true; +} +inline bool set_unsigned(json_view js, uint64_t value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::unsigned_; + jsv._unsigned = value; + return true; +} +inline bool set_real(json_view js, double value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::real; + jsv._real = value; + return true; +} +inline bool set_boolean(json_view js, bool value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::boolean; + jsv._boolean = value; + return true; +} +inline bool set_string(json_view js, const string& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::string_; + jsv._string.start = (uint32_t)js.root->strings.size(); + jsv._string.length = (uint32_t)value.size(); + js.root->strings.insert(js.root->strings.end(), value.begin(), value.end()); + js.root->strings.push_back(0); + return true; +} +inline bool set_integral(json_view js, int64_t value) { + return set_integer(js, value); +} +inline bool set_integral(json_view js, int32_t value) { + return set_integer(js, value); +} +inline bool set_integral(json_view js, uint64_t value) { + return set_unsigned(js, value); +} +inline bool set_integral(json_view js, uint32_t value) { + return set_unsigned(js, value); +} +inline bool set_number(json_view js, double value) { + return set_real(js, value); +} +inline bool set_number(json_view js, float value) { + return set_real(js, value); +} + +// Get basic values +inline bool get_integer(json_cview js, int64_t& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::integer) return set_error(js, "integer expected"); + value = jsv._integer; + return true; +} +inline bool get_unsigned(json_cview js, uint64_t& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::unsigned_) return set_error(js, "unsigned expected"); + value = jsv._unsigned; + return true; +} +inline bool get_real(json_cview js, double& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::real) return set_error(js, "real expected"); + value = jsv._real; + return true; +} +inline bool get_boolean(json_cview js, bool& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::boolean) return set_error(js, "boolean expected"); + value = jsv._boolean; + return true; +} +inline bool get_string(json_cview js, string& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::string_) return set_error(js, "string expected"); + value = string{js.root->strings.data() + jsv._string.start, + js.root->strings.data() + jsv._string.start + jsv._string.length}; + return true; +} +inline bool get_integral(json_cview js, int64_t& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::integer && jst != json_type::unsigned_) + return set_error(js, "integer expected"); + value = (jst == json_type::integer) ? (int64_t)jsv._integer + : (int64_t)jsv._unsigned; + return true; +} +inline bool get_integral(json_cview js, uint64_t& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::integer && jst != json_type::unsigned_) + return set_error(js, "integer expected"); + value = (jst == json_type::integer) ? (uint64_t)jsv._integer + : (uint64_t)jsv._unsigned; + return true; +} +inline bool get_number(json_cview js, double& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::real && jst != json_type::integer && + jst != json_type::unsigned_) + return set_error(js, "number expected"); + value = (jst == json_type::real) + ? (double)jsv._real + : (jst == json_type::integer) ? (double)jsv._integer + : (double)jsv._unsigned; + return true; +} +inline bool get_integral(json_cview js, int32_t& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::integer && jst != json_type::unsigned_) + return set_error(js, "integer expected"); + value = (jst == json_type::integer) ? (int32_t)jsv._integer + : (int32_t)jsv._unsigned; + return true; +} +inline bool get_integral(json_cview js, uint32_t& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::integer && jst != json_type::unsigned_) + return set_error(js, "integer expected"); + value = (jst == json_type::integer) ? (uint32_t)jsv._integer + : (uint32_t)jsv._unsigned; + return true; +} +inline bool get_number(json_cview js, float& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::real && jst != json_type::integer && + jst != json_type::unsigned_) + return set_error(js, "number expected"); + value = (jst == json_type::real) + ? (float)jsv._real + : (jst == json_type::integer) ? (float)jsv._integer + : (float)jsv._unsigned; + return true; +} + +// Get basic values +inline int64_t get_integer(json_cview js) { + auto value = (int64_t)0; + return get_integer(js, value) ? value : 0; +} +inline uint64_t get_unsigned(json_cview js) { + auto value = (uint64_t)0; + return get_unsigned(js, value) ? value : 0; +} +inline double get_real(json_cview js) { + auto value = (double)0; + return get_real(js, value) ? value : 0; +} +inline bool get_boolean(json_cview js) { + auto value = false; + return get_boolean(js, value) ? value : false; +} +inline string get_string(json_cview js) { + auto value = string{}; + return get_string(js, value) ? value : string{}; +} +inline int64_t get_integral(json_cview js) { + auto value = (int64_t)0; + return get_integral(js, value) ? value : 0; +} +inline double get_number(json_cview js) { + auto value = (double)0; + return get_number(js, value) ? value : 0; +} + +// Compound type +inline bool is_empty(json_cview js); +inline size_t get_size(json_cview js); + +// Array iteeration +inline auto iterate_array(json_view js) { + struct iterator { + json_view js; + bool operator!=(const iterator& other) { + return is_valid(js) && js.index != other.js.index; + } + iterator& operator++() { + if (!is_valid(js)) return *this; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + js.index += 1; + if (jst == json_type::array) js.index += jsv._array.skip; + if (jst == json_type::object) js.index += jsv._object.skip; + return *this; + } + json_view operator*() const { return js; } + }; + struct iterator_wrapper { + json_view begin_; + json_view end_; + iterator begin() { return {begin_}; } + iterator end() { return {end_}; } + }; + if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::array) { + set_error_view(js, "array expected"); + return iterator_wrapper{{js.root}, {js.root}}; + } + return iterator_wrapper{ + {js.root, js.index + 1}, {js.root, js.index + 1 + jsv._array.skip}}; +} +inline auto iterate_array(json_cview js) { + struct iterator { + json_cview js; + bool operator!=(const iterator& other) { + return is_valid(js) && js.index != other.js.index; + } + iterator& operator++() { + if (!is_valid(js)) return *this; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + js.index += 1; + if (jst == json_type::array) js.index += jsv._array.skip; + if (jst == json_type::object) js.index += jsv._object.skip; + return *this; + } + json_cview operator*() const { return js; } + }; + struct iterator_wrapper { + json_cview begin_; + json_cview end_; + iterator begin() { return {begin_}; } + iterator end() { return {end_}; } + }; + if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::array) { + set_error_view(js, "array expected"); + return iterator_wrapper{{js.root}, {js.root}}; + } + return iterator_wrapper{ + {js.root, js.index + 1}, + {js.root, js.index + 1 + jsv._array.skip}, + }; +} + +// Array +inline bool set_array(json_view js) { + if (!is_valid(js)) return false; + if (js.index != js.root->values.size() - 1) + throw std::out_of_range{"can only add at the end"}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::array; + jsv._array = {0, 0}; + return true; +} +inline bool array_size(json_cview js, size_t& size) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::array) return set_error(js, "array expected"); + size = (size_t)jsv._array.length; + return true; +} +inline json_view get_element(json_view js, size_t idx) { + if (!is_valid(js)) return {js.root}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::array) return set_error_view(js, "array expected"); + if (idx >= jsv._array.length) + return set_error_view(js, "index out of bounds"); + if (jsv._array.length == jsv._array.skip) { + return {js.root, js.index + 1 + (uint32_t)idx}; + } else { + auto count = 0; + for (auto ejs : iterate_array(js)) { + if (count++ == idx) return ejs; + } + return set_error_view(js, "index out of bounds"); + } +} +inline json_cview get_element(json_cview js, size_t idx) { + if (!is_valid(js)) return {js.root}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::array) return set_error_view(js, "array expected"); + if (idx >= jsv._array.length) + return set_error_view(js, "index out of bounds"); + if (jsv._array.length == jsv._array.skip) { + return {js.root, js.index + 1 + (uint32_t)idx}; + } else { + auto count = 0; + for (auto ejs : iterate_array(js)) { + if (count++ == idx) return ejs; + } + return set_error_view(js, "index out of bounds"); + } +} +inline json_view append_element(json_view js) { + if (!is_valid(js)) return {js.root}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::array) return set_error_view(js, "array expected"); + if (js.index + 1 + jsv._array.skip != js.root->values.size()) + throw std::out_of_range{"can only add at the end"}; + jsv._array.length += 1; + auto index = (uint32_t)js.root->values.size(); + js.root->types.emplace_back(json_type::null); + js.root->values.emplace_back(); + auto stack = vector{}; + _find_path(js, stack); + for (auto jss : stack) { + auto& jsst = _get_type(jss); + auto& jssv = _get_value(jss); + if (jsst == json_type::array) { + jssv._array.skip += 1; + } else if (jsst == json_type::object) { + jssv._object.skip += 1; + } else { + throw std::runtime_error{"bad stack"}; + } + } + return {js.root, index}; +} + +// Object iteration +inline auto iterate_object(json_view js) { + struct iterator { + json_view js; + bool operator!=(const iterator& other) { + return is_valid(js) && js.index != other.js.index; + } + iterator& operator++() { + if (!is_valid(js)) return *this; + auto& jst = _get_type(json_view{js.root, js.index + 1}); + auto& jsv = _get_value(json_view{js.root, js.index + 1}); + js.index += 2; + if (jst == json_type::array) js.index += jsv._array.skip; + if (jst == json_type::object) js.index += jsv._object.skip; + return *this; + } + pair operator*() const { + return {_get_key(js), json_view{js.root, js.index + 1}}; + } + }; + struct iterator_wrapper { + json_view begin_; + json_view end_; + iterator begin() { return {begin_}; } + iterator end() { return {end_}; } + }; + if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::object) { + set_error_view(js, "object expected"); + return iterator_wrapper{{js.root}, {js.root}}; + } + return iterator_wrapper{ + {js.root, js.index + 1}, {js.root, js.index + 1 + jsv._object.skip}}; +} +inline auto iterate_object(json_cview js) { + struct iterator { + json_cview js; + bool operator!=(const iterator& other) { + return is_valid(js) && js.index != other.js.index; + } + iterator& operator++() { + if (!is_valid(js)) return *this; + auto& jst = _get_type(json_cview{js.root, js.index + 1}); + auto& jsv = _get_value(json_cview{js.root, js.index + 1}); + js.index += 2; + if (jst == json_type::array) js.index += jsv._array.skip; + if (jst == json_type::object) js.index += jsv._object.skip; + return *this; + } + pair operator*() const { + return {_get_key(js), json_cview{js.root, js.index + 1}}; + } + }; + struct iterator_wrapper { + json_cview begin_; + json_cview end_; + iterator begin() { return {begin_}; } + iterator end() { return {end_}; } + }; + if (!is_valid(js)) return iterator_wrapper{{js.root}, {js.root}}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::object) { + set_error_view(js, "object expected"); + return iterator_wrapper{{js.root}, {js.root}}; + } + return iterator_wrapper{ + {js.root, js.index + 1}, {js.root, js.index + 1 + jsv._object.skip}}; +} + +// Object +inline bool set_object(json_view js) { + if (!is_valid(js)) return false; + if (js.index != js.root->values.size() - 1) + throw std::out_of_range{"can only add at the end"}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + jst = json_type::object; + jsv._array = {0, 0}; + return true; +} +inline bool object_size(json_cview js, size_t& size) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::object) return set_error(js, "object expected"); + size = (size_t)jsv._object.length; + return true; +} +inline json_view get_element(json_view js, string_view key) { + if (!is_valid(js)) return {js.root}; + auto& jst = _get_type(js); + if (jst != json_type::object) return set_error_view(js, "object expected"); + for (auto [okey, ejs] : iterate_object(js)) { + if (okey == key) return ejs; + } + return set_error_view(js, "missing key " + string{key}); +} +inline json_cview get_element(json_cview js, string_view key) { + if (!is_valid(js)) return {js.root}; + auto& jst = _get_type(js); + if (jst != json_type::object) return set_error_view(js, "object expected"); + for (auto [okey, value] : iterate_object(js)) { + if (okey == key) return value; + } + return set_error_view(js, "missing key " + string{key}); +} +inline bool has_element(json_view js, string_view key) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + if (jst != json_type::object) return set_error(js, "object expected"); + for (auto [okey, value] : iterate_object(js)) { + if (okey == key) return true; + } + return false; +} +inline bool has_element(json_cview js, string_view key) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + if (jst != json_type::object) return set_error(js, "object expected"); + for (auto [okey, value] : iterate_object(js)) { + if (okey == key) return true; + } + return false; +} +inline json_view insert_element(json_view js, string_view key) { + if (!is_valid(js)) return {js.root}; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::object) return set_error_view(js, "object expected"); + if (js.index + 1 + jsv._object.skip != js.root->values.size()) + throw std::out_of_range{"can only add at the end"}; + for (auto [okey, ejs] : iterate_object(js)) { + if (okey == key) return ejs; + } + jsv._object.length += 1; + auto& jkt = js.root->types.emplace_back(); + auto& jkv = js.root->values.emplace_back(); + jkt = json_type::string_; + for (auto kv : js.root->key_list) { + auto okey = string_view{ + js.root->keys.data() + kv._string.start, kv._string.length}; + if (okey == key) jkv = kv; + } + if (jkv._string.length == 0) { + jkv._string.start = (uint32_t)js.root->keys.size(); + jkv._string.length = (uint32_t)key.size(); + js.root->keys.insert(js.root->keys.end(), key.begin(), key.end()); + js.root->keys.push_back(0); + } + auto index = (uint32_t)js.root->values.size(); + js.root->types.emplace_back(json_type::null); + js.root->values.emplace_back(); + auto stack = vector{}; + _find_path(js, stack); + for (auto jss : stack) { + auto& jsst = _get_type(jss); + auto& jssv = _get_value(jss); + if (jsst == json_type::array) { + jssv._array.skip += 2; + } else if (jsst == json_type::object) { + jssv._object.skip += 2; + } else { + throw std::runtime_error{"bad stack"}; + } + } + return {js.root, index}; +} + +// Binary +inline bool set_binary(json_view js, const json_binary& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + // TODO(fabio): implement reuse + jst = json_type::binary; + jsv._binary.start = (uint32_t)js.root->binaries.size(); + jsv._binary.length = (uint32_t)value.size(); + js.root->binaries.insert(js.root->binaries.end(), value.begin(), value.end()); + return true; +} +inline bool get_binary(json_cview js, json_binary& value) { + if (!is_valid(js)) return false; + auto& jst = _get_type(js); + auto& jsv = _get_value(js); + if (jst != json_type::binary) return set_error(js, "binary expected"); + value = json_binary{js.root->binaries.data() + jsv._binary.start, + js.root->binaries.data() + jsv._binary.start + jsv._binary.length}; + return true; +} + +// Get the path of a json view +inline bool _compute_path(json_cview js, json_cview jsv, string& path) { + if (!is_valid(js) || !is_valid(jsv)) { + return false; + } else if (js.index == jsv.index) { + path = "/"; + return true; + } else if (is_array(js)) { + auto idx = 0; + for (auto ejs : iterate_array(js)) { + if (!_compute_path(ejs, jsv, path)) continue; + if (path.back() == '/') path.pop_back(); + path = "/"s + std::to_string(idx) + path; + return true; + } + return false; + } else if (is_object(js)) { + for (auto [key, ejs] : iterate_object(js)) { + if (!_compute_path(ejs, jsv, path)) continue; + if (path.back() == '/') path.pop_back(); + path = "/" + string{key} + path; + return true; + } + return false; + } else { + return false; + } +} +inline string compute_path(json_cview js) { + auto path = string{}; + if (_compute_path({js.root, 0}, js, path)) { + return path; + } else { + return ""; + } +} + +// Conversion from json to values +inline bool get_value(json_cview js, int64_t& value) { + return get_integral(js, value); +} +inline bool get_value(json_cview js, int32_t& value) { + return get_integral(js, value); +} +inline bool get_value(json_cview js, uint64_t& value) { + return get_integral(js, value); +} +inline bool get_value(json_cview js, uint32_t& value) { + return get_integral(js, value); +} +inline bool get_value(json_cview js, double& value) { + return get_number(js, value); +} +inline bool get_value(json_cview js, float& value) { + return get_number(js, value); +} +inline bool get_value(json_cview js, bool& value) { + return get_boolean(js, value); +} +inline bool get_value(json_cview js, string& value) { + return get_string(js, value); +} +template +inline bool get_value(json_cview js, vector& value) { + if (!is_valid(js)) return false; + if (!is_array(js)) return set_error(js, "array expected"); + value.clear(); + auto size = (size_t)0; + array_size(js, size); + value.reserve(size); + for (auto ejs : iterate_array(js)) { + if (!get_value(ejs, value.emplace_back())) return false; + } + return true; +} +template +inline bool get_value(json_cview js, array& value) { + if (!is_valid(js)) return false; + if (!is_array(js)) return set_error(js, "array expected"); + auto size = (size_t)0; + array_size(js, size); + if (size != N) return set_error(js, "size mismatched"); + auto idx = 0; + for (auto ejs : iterate_array(js)) { + if (!get_value(ejs, value.at(idx++))) return false; + } + return true; +} + +// Get value at a key or index +template +inline bool get_value_at(json_cview js, string_view key, T& value) { + if (!is_valid(js)) return false; + auto element = get_element(js, key); + return get_value(element, value); +} +template +inline bool get_value_at(json_cview js, size_t idx, T& value) { + if (!is_valid(js)) return false; + auto element = get_element(js, idx); + if (!is_valid(element)) return false; + return get_value(element, value); +} + +// Get value at a key or nothing is key is not preesent +template +inline bool get_value_if(json_cview js, string_view key, T& value) { + if (!is_valid(js)) return false; + if (!has_element(js, key)) return true; + auto element = get_element(js, key); + if (!is_valid(element)) return false; + return get_value(element, value); +} + +// Conversion to json from values +inline bool set_value(json_view js, int64_t value) { + return set_integral(js, value); +} +inline bool set_value(json_view js, int32_t value) { + return set_integral(js, value); +} +inline bool set_value(json_view js, uint64_t value) { + return set_integral(js, value); +} +inline bool set_value(json_view js, uint32_t value) { + return set_integral(js, value); +} +inline bool set_value(json_view js, double value) { + return set_number(js, value); +} +inline bool set_value(json_view js, float value) { + return set_number(js, value); +} +inline bool set_value(json_view js, bool value) { + return set_boolean(js, value); +} +inline bool set_value(json_view js, const string& value) { + return set_string(js, value); +} +inline bool set_value(json_view js, const char* value) { + return set_string(js, value); +} +template +inline bool set_value(json_view js, const vector& value) { + if (!set_array(js)) return false; + for (auto& v : value) { + if (!set_value(append_element(js), v)) return false; + } + return true; +} +template +inline bool set_value(json_view js, const array& value) { + if (!set_array(js)) return false; + for (auto& v : value) { + if (!set_value(append_element(js), v)) return false; + } + return true; +} + +// Helpers for user-defined types +inline bool check_array(json_cview js) { + if (!is_valid(js)) return false; + if (!is_array(js)) return set_error(js, "array expected"); + return true; +} +inline bool check_array(json_cview js, size_t size_) { + if (!is_valid(js)) return false; + if (!is_array(js)) return set_error(js, "array expected"); + auto size = (size_t)0; + if (!array_size(js, size) || size != size_) + return set_error(js, "mismatched size"); + return true; +} +inline bool check_object(json_cview js) { + if (!is_valid(js)) return false; + if (!is_object(js)) return set_error(js, "array expected"); + return true; +} + +// Helpers for user-defined types +template +inline bool set_value_at(json_view js, size_t idx, const T& value) { + if (!is_valid(js)) return false; + auto ejs = get_element(js, idx); + if (!is_valid(ejs)) return false; + return set_value(ejs, value); +} +template +inline bool append_value(json_view js, const T& value) { + if (!is_valid(js)) return false; + auto ejs = append_element(js); + if (!is_valid(ejs)) return false; + return set_value(ejs, value); +} +inline json_view append_array(json_view js) { + if (!is_valid(js)) return {js.root}; + auto ejs = append_element(js); + if (!is_valid(ejs)) return {js.root}; + if (!set_array(ejs)) return {js.root}; + return ejs; +} +inline json_view append_object(json_view js) { + if (!is_valid(js)) return {js.root}; + auto ejs = append_element(js); + if (!is_valid(ejs)) return {js.root}; + if (!set_object(ejs)) return {js.root}; + return ejs; +} + +// Helpers for user-defined types +template +inline bool set_value_at(json_view js, string_view key, const T& value) { + if (!is_valid(js)) return false; + auto ejs = get_element(js, key); + if (!is_valid(ejs)) return false; + return set_value(ejs, value); +} +template +inline bool insert_value(json_view js, string_view key, const T& value) { + if (!is_valid(js)) return false; + auto ejs = insert_element(js, key); + if (!is_valid(ejs)) return false; + return set_value(ejs, value); +} +template +inline bool insert_value_if( + json_view js, string_view key, const T& value, const T& default_) { + if (!is_valid(js)) return false; + if (value == default_) return true; + auto ejs = insert_element(js, key); + if (!is_valid(ejs)) return false; + return set_value(ejs, value); +} +inline json_view insert_array(json_view js, string_view key) { + if (!is_valid(js)) return {js.root}; + auto ejs = insert_element(js, key); + if (!is_valid(ejs)) return {js.root}; + if (!set_array(ejs)) return {js.root}; + return ejs; +} +inline json_view insert_object(json_view js, string_view key) { + if (!is_valid(js)) return {js.root}; + auto ejs = insert_element(js, key); + if (!is_valid(ejs)) return {js.root}; + if (!set_object(ejs)) return {js.root}; + return ejs; +} + +// Helpers that need to be declared here +inline bool _find_anchestors( + json_view js, uint32_t index, vector& path) { + auto& jst = _get_type(js); + if (jst == json_type::array) { + if (js.index == index) { + path.push_back(js); + return true; + } + // if (index <= js.index || index >= js.index + 1 + jsv._array.skip) + // return false; + // path.push_back(js.index); + for (auto ejs : iterate_array(js)) { + if (_find_anchestors(ejs, index, path)) { + path.push_back(ejs); + return true; + } + } + return false; + } else if (jst == json_type::object) { + if (js.index == index) { + path.push_back(js); + return true; + } + // if (index <= js.index || index >= js.index + 1 + jsv._object.skip) + // return false; + // path.push_back(js.index); + for (auto [okey, ejs] : iterate_object(js)) { + if (_find_anchestors(ejs, index, path)) { + path.push_back(js); + return true; + } + } + return false; + } else { + return false; + } +} +inline void _find_path(json_view js, vector& path) { + path.clear(); + _find_anchestors({js.root, 0}, js.index, path); +} + +} // namespace yocto + +#endif diff --git a/libs/yocto/yocto_modelio.cpp b/libs/yocto/yocto_modelio.cpp index 021fac4ce..744280757 100644 --- a/libs/yocto/yocto_modelio.cpp +++ b/libs/yocto/yocto_modelio.cpp @@ -2649,10 +2649,10 @@ bool load_stl(const string& filename, stl_model* stl, string& error, // switch on type if (binary) { - // open file - auto fs = open_file(filename, "rb"); - if (!fs) return open_error(); - + // open file + auto fs = open_file(filename, "rb"); + if (!fs) return open_error(); + // skip header auto header = array{}; if (!read_value(fs, header)) return read_error(); @@ -2733,8 +2733,9 @@ bool load_stl(const string& filename, stl_model* stl, string& error, // check that it was a triangle auto last_pos = (int)stl->shapes.back()->positions.size() - 3; if (stl->shapes.back()->triangles.empty() && last_pos != 0) - return parse_error(); - if (!stl->shapes.back()->triangles.empty() && last_pos != stl->shapes.back()->triangles.back().z + 1) + return parse_error(); + if (!stl->shapes.back()->triangles.empty() && + last_pos != stl->shapes.back()->triangles.back().z + 1) return parse_error(); // add triangle stl->shapes.back()->triangles.push_back( diff --git a/mkdocs.yml b/mkdocs.yml index b5ffa15b8..6b71d286b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,6 +39,7 @@ nav: - Model serialization: yocto/yocto_modelio.md - Scene serialization: yocto/yocto_sceneio.md - Path tracing: yocto/yocto_trace.md + - JSON utilities: yocto/yocto_json.md - Command-line utilities: yocto/yocto_commonio.md - Generic utilities: yocto/yocto_common.md - Concurrency utilities: yocto/yocto_parallel.md diff --git a/readme.md b/readme.md index 9561b63a5..6484e95a6 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,8 @@ See each header file for documentation. - `yocto/yocto_modelio.{h,cpp}`: parsing and writing for Ply/Obj/Pbrt formats - `yocto/yocto_commonio.h`: printing utilities, file io utilities, command line parsing +- `yocto/yocto_json.h`: JSON data type, json io utilities, + command line parsing - `yocto/yocto_common.h`: container and iterator utilities - `yocto/yocto_parallel.h`: concurrency utilities