diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 54691da3f41e..36f1e0cd50f1 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -81,6 +81,8 @@ add_library(${TARGET} imatrix-loader.cpp imatrix-loader.h json-schema-to-grammar.cpp + json.cpp + json.h llguidance.cpp log.cpp log.h diff --git a/common/arg.cpp b/common/arg.cpp index 3da1d61f4278..86f8610a56d0 100644 --- a/common/arg.cpp +++ b/common/arg.cpp @@ -5,6 +5,7 @@ #include "common.h" #include "download.h" #include "json-schema-to-grammar.h" +#include "json.h" #include "llama.h" #include "log.h" #include "sampling.h" @@ -21,9 +22,6 @@ #include #endif -#define JSON_ASSERT GGML_ASSERT -#include - #include #include #include @@ -32,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -55,7 +54,7 @@ #define LLAMA_MAX_URL_LENGTH 2084 // Maximum URL Length in Chrome: 2083 -using json = nlohmann::ordered_json; +using json = common_json; using namespace common_arg_utils; static std::initializer_list mmproj_examples = { diff --git a/common/chat-auto-parser-generator.cpp b/common/chat-auto-parser-generator.cpp index af84ff323daf..d7e117e4d98b 100644 --- a/common/chat-auto-parser-generator.cpp +++ b/common/chat-auto-parser-generator.cpp @@ -5,13 +5,12 @@ #include "common.h" #include "json-schema-to-grammar.h" #include "log.h" -#include "nlohmann/json.hpp" #include "peg-parser.h" #include #include -using json = nlohmann::ordered_json; +using json = common_json; // Helper to iterate over tools/functions static void foreach_function(const json & tools, const std::function & fn) { @@ -391,7 +390,7 @@ common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_conte std::set required; if (params.contains("required")) { - params.at("required").get_to(required); + required = params.at("required").get>(); } auto schema_info = common_schema_info(); diff --git a/common/chat-auto-parser-helpers.cpp b/common/chat-auto-parser-helpers.cpp index 81b17e5e1d27..b37906bdf85a 100644 --- a/common/chat-auto-parser-helpers.cpp +++ b/common/chat-auto-parser-helpers.cpp @@ -4,14 +4,11 @@ #include "chat-peg-parser.h" #include "chat.h" #include "log.h" -#include "nlohmann/json.hpp" #include "peg-parser.h" #include #include -using json = nlohmann::ordered_json; - std::string trim_whitespace(const std::string & str) { size_t start = 0; while (start < str.length() && std::isspace(static_cast(str[start]))) { diff --git a/common/chat-auto-parser.h b/common/chat-auto-parser.h index 074216b11ee1..8ae15c91e1e5 100644 --- a/common/chat-auto-parser.h +++ b/common/chat-auto-parser.h @@ -4,7 +4,7 @@ #include "common.h" #include "jinja/caps.h" #include "peg-parser.h" -#include "nlohmann/json.hpp" +#include "json.h" #include #include @@ -12,7 +12,7 @@ #include #include -using json = nlohmann::ordered_json; +using json = common_json; class common_chat_peg_builder; diff --git a/common/chat-diff-analyzer.cpp b/common/chat-diff-analyzer.cpp index d6d2af2d50fe..a7e370578fd9 100644 --- a/common/chat-diff-analyzer.cpp +++ b/common/chat-diff-analyzer.cpp @@ -4,11 +4,11 @@ #include "chat.h" #include "common.h" #include "log.h" -#include "nlohmann/json.hpp" #include "peg-parser.h" #include #include +#include #include #include @@ -17,7 +17,7 @@ #define ANSI_ORANGE "\033[1m\x1b[38;5;214m" #define ANSI_RED "\033[1m\x1b[38;5;196m" -using json = nlohmann::ordered_json; +using json = common_json; namespace autoparser { @@ -929,7 +929,7 @@ void analyze_tools::analyze_tool_call_format_json_native(const std::string & cle int json_end = clean_haystack.find_last_of('}'); std::string cut = clean_haystack.substr(json_start, json_end - json_start + 1); json call_struct = json::parse(cut); - auto register_field = [&](const std::string & prefix, const nlohmann::detail::iteration_proxy_value & subel) { + auto register_field = [&](const std::string & prefix, const common_json_entry & subel) { if (subel.value().is_string() && std::string(subel.value()).find("call0000") != std::string::npos) { format.id_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key(); } else if (subel.value().is_string() && std::string(subel.value()) == fun_name_needle) { diff --git a/common/chat-peg-parser.cpp b/common/chat-peg-parser.cpp index 06737b165c05..79b97a80f1b2 100644 --- a/common/chat-peg-parser.cpp +++ b/common/chat-peg-parser.cpp @@ -4,12 +4,10 @@ #include "ggml.h" #include "peg-parser.h" -#include - #include #include -using ordered_json = nlohmann::ordered_json; +using ordered_json = common_json; static std::string_view trim_trailing_space(std::string_view sv, int max = -1) { int count = 0; diff --git a/common/chat-peg-parser.h b/common/chat-peg-parser.h index 5d764dbaa0ec..114fa049fa74 100644 --- a/common/chat-peg-parser.h +++ b/common/chat-peg-parser.h @@ -128,7 +128,7 @@ class common_chat_peg_builder : public common_peg_parser_builder { // parameters_order: order in which JSON fields should be parsed common_peg_parser standard_json_tools(const std::string & section_start, const std::string & section_end, - const nlohmann::ordered_json & tools, + const common_json & tools, bool parallel_tool_calls, bool force_tool_calls, const std::string & name_key = "", @@ -143,13 +143,13 @@ class common_chat_peg_builder : public common_peg_parser_builder { // Legacy-compatible helper for building XML/tagged style tool calls // Used by tests and manual parsers common_peg_parser standard_constructed_tools(const std::map & markers, - const nlohmann::ordered_json & tools, + const common_json & tools, bool parallel_tool_calls, bool force_tool_calls); // Helper for Python-style function call format: name(arg1="value1", arg2=123) // Used by LFM2 and similar templates - common_peg_parser python_style_tool_calls(const nlohmann::ordered_json & tools, + common_peg_parser python_style_tool_calls(const common_json & tools, bool parallel_tool_calls, bool allow_json_literals); @@ -158,19 +158,19 @@ class common_chat_peg_builder : public common_peg_parser_builder { common_peg_parser python_or_json_value(); // Implementation helpers for standard_json_tools — one per JSON tool call layout mode - common_peg_parser build_json_tools_function_is_key(const nlohmann::ordered_json & tools, + common_peg_parser build_json_tools_function_is_key(const common_json & tools, const std::string & args_key, const std::string & effective_args_key, const std::string & call_id_key, const std::string & gen_call_id_key); - common_peg_parser build_json_tools_nested_keys(const nlohmann::ordered_json & tools, + common_peg_parser build_json_tools_nested_keys(const common_json & tools, const std::string & effective_name_key, const std::string & effective_args_key, const std::string & call_id_key, const std::string & gen_call_id_key); - common_peg_parser build_json_tools_flat_keys(const nlohmann::ordered_json & tools, + common_peg_parser build_json_tools_flat_keys(const common_json & tools, const std::string & effective_name_key, const std::string & effective_args_key, const std::string & call_id_key, diff --git a/common/chat.cpp b/common/chat.cpp index 39761f12acdd..24618d35aee5 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -6,6 +6,7 @@ #include "common.h" #include "ggml.h" #include "json-schema-to-grammar.h" +#include "json.h" #include "log.h" #include "jinja/value.h" @@ -13,14 +14,13 @@ #include "jinja/caps.h" #include "peg-parser.h" -#include "nlohmann/json.hpp" - #include #include #include #include #include #include +#include #include #include @@ -30,7 +30,7 @@ #include #include -using json = nlohmann::ordered_json; +using json = common_json; static std::string format_time(const std::chrono::system_clock::time_point & now, const std::string & format) { auto time = std::chrono::system_clock::to_time_t(now); @@ -48,7 +48,7 @@ static json safe_args_parse(const std::string & to_parse) { } try { return json::parse(stripped); - } catch (json::exception & e) { + } catch (const common_json_error & e) { return stripped; } } @@ -488,17 +488,17 @@ struct messages_inp_normalizer { json normalized = json::array(); for (const auto & msg : messages) { json copy = msg; - auto it = copy.find("content"); - if (it != copy.end()) { - if (only_typed && it->is_string()) { - *it = json::array({ + if (copy.contains("content")) { + json & it = copy.at("content"); + if (only_typed && it.is_string()) { + it = json::array({ json{ {"type", "text"}, - {"text", it->get()}, + {"text", it.get()}, } }); - } else if (only_string && it->is_array()) { - *it = concat_content_parts(*it); + } else if (only_string && it.is_array()) { + it = concat_content_parts(it); } } normalized.push_back(std::move(copy)); @@ -608,7 +608,7 @@ std::vector common_chat_tools_parse_oaicompat(const json & too return result; } -common_chat_continuation common_chat_continuation_parse(const nlohmann::ordered_json & value) { +common_chat_continuation common_chat_continuation_parse(const common_json & value) { if (value.is_boolean() && value.get()) { return COMMON_CHAT_CONTINUATION_AUTO; } @@ -920,7 +920,7 @@ static void foreach_parameter(const json & const auto & props = params.at("properties"); std::set required; if (params.contains("required") && params.at("required").is_array()) { - params.at("required").get_to(required); + required = params.at("required").get>(); } for (const auto & [name, prop] : props.items()) { bool is_required = (required.find(name) != required.end()); @@ -937,7 +937,7 @@ static std::string common_chat_template_direct_apply_impl( jinja::context ctx(tmpl.source()); // messages_override is already built for this template, do not touch its content parts - nlohmann::ordered_json inp = nlohmann::ordered_json{ + json inp = json{ {"messages", messages_override.has_value() ? *messages_override : messages_inp_normalizer(tmpl.original_caps()).normalize(inputs.messages)}, @@ -1058,7 +1058,7 @@ static common_chat_params common_chat_params_init_ministral_3(const common_chat_ }); } else if (msg.at("content").is_array()) { auto blocks = msg.at("content"); - content.insert(content.end(), blocks.begin(), blocks.end()); + content.insert(blocks); } } @@ -2238,7 +2238,7 @@ static common_chat_params common_chat_params_init_deepseek_v3_2(const common_cha std::set required; if (params.contains("required")) { - params.at("required").get_to(required); + required = params.at("required").get>(); } auto schema_info = common_schema_info(); @@ -2860,7 +2860,7 @@ static common_chat_params common_chat_params_init_minimax_m3(const common_chat_t std::set required; if (schema.contains("required")) { - schema.at("required").get_to(required); + required = schema.at("required").get>(); } std::vector required_elements; @@ -2972,10 +2972,10 @@ static void system_message_not_supported(json & messages) { auto & second_msg = messages[1]; second_msg["content"] = first_msg.at("content").get() + "\n" + second_msg.at("content").get(); - messages.erase(messages.begin()); + messages.erase(0); } else { LOG_WRN("Removing system prompt due to template not supporting system role\n"); - messages.erase(messages.begin()); + messages.erase(0); } } } diff --git a/common/chat.h b/common/chat.h index 6d5b220aebb5..cb39e3458f44 100644 --- a/common/chat.h +++ b/common/chat.h @@ -8,7 +8,7 @@ #include "jinja/runtime.h" #include "jinja/caps.h" -#include "nlohmann/json_fwd.hpp" +#include "json.h" #include #include @@ -17,7 +17,6 @@ #include using chat_template_caps = jinja::caps; -using json = nlohmann::ordered_json; struct common_chat_templates; @@ -87,7 +86,7 @@ struct common_chat_msg { std::string tool_name; std::string tool_call_id; - nlohmann::ordered_json to_json_oaicompat(bool concat_typed_text = false) const; + common_json to_json_oaicompat(bool concat_typed_text = false) const; std::string render_content(const std::string & delimiter = "\n\n") const; @@ -211,7 +210,7 @@ struct common_chat_msg_delimiters { // split tokens into message spans. skips maps a start index to a length of a region to jump over without matching common_chat_msg_spans split(const llama_tokens & tokens, const std::map & skips = {}) const; - nlohmann::ordered_json to_json() const; + common_json to_json() const; }; struct common_chat_tool { @@ -350,16 +349,16 @@ common_chat_tool_choice common_chat_tool_choice_parse_oaicompat(const std::strin bool common_chat_templates_support_enable_thinking(const common_chat_templates * chat_templates); // Parses a JSON array of messages in OpenAI's chat completion API format. -std::vector common_chat_msgs_parse_oaicompat(const nlohmann::ordered_json & messages); +std::vector common_chat_msgs_parse_oaicompat(const common_json & messages); -std::vector common_chat_tools_parse_oaicompat(const nlohmann::ordered_json & tools); +std::vector common_chat_tools_parse_oaicompat(const common_json & tools); -common_chat_continuation common_chat_continuation_parse(const nlohmann::ordered_json & value); +common_chat_continuation common_chat_continuation_parse(const common_json & value); // DEPRECATED: only used in tests -nlohmann::ordered_json common_chat_msgs_to_json_oaicompat(const std::vector & msgs, bool concat_typed_text = false); +common_json common_chat_msgs_to_json_oaicompat(const std::vector & msgs, bool concat_typed_text = false); -nlohmann::ordered_json common_chat_tools_to_json_oaicompat(const std::vector & tools); +common_json common_chat_tools_to_json_oaicompat(const std::vector & tools); // get template caps, useful for reporting to server /props endpoint std::map common_chat_templates_get_caps(const common_chat_templates * chat_templates); @@ -386,4 +385,4 @@ struct common_chat_prompt_preset { common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates); -common_chat_msg_delimiters common_chat_msg_delimiters_parse(const nlohmann::ordered_json & delimiters); +common_chat_msg_delimiters common_chat_msg_delimiters_parse(const common_json & delimiters); diff --git a/common/download.cpp b/common/download.cpp index 2509f75ab996..4b28a708c86e 100644 --- a/common/download.cpp +++ b/common/download.cpp @@ -5,9 +5,7 @@ #include "log.h" #include "download.h" #include "hf-cache.h" - -#define JSON_ASSERT GGML_ASSERT -#include +#include "json.h" #include #include @@ -44,8 +42,6 @@ #include #endif -using json = nlohmann::ordered_json; - // // downloader // @@ -856,8 +852,8 @@ static std::string common_docker_get_token(const std::string & repo) { throw std::runtime_error("Failed to get Docker registry token, HTTP code: " + std::to_string(res.first)); } - std::string response_str(res.second.begin(), res.second.end()); - nlohmann::ordered_json response = nlohmann::ordered_json::parse(response_str); + std::string response_str(res.second.begin(), res.second.end()); + common_json response = common_json::parse(response_str); if (!response.contains("token")) { throw std::runtime_error("Docker registry token response missing 'token' field"); @@ -919,9 +915,9 @@ std::string common_docker_resolve_model(const std::string & docker) { throw std::runtime_error("Failed to get Docker manifest, HTTP code: " + std::to_string(manifest_res.first)); } - std::string manifest_str(manifest_res.second.begin(), manifest_res.second.end()); - nlohmann::ordered_json manifest = nlohmann::ordered_json::parse(manifest_str); - std::string gguf_digest; // Find the GGUF layer + std::string manifest_str(manifest_res.second.begin(), manifest_res.second.end()); + common_json manifest = common_json::parse(manifest_str); + std::string gguf_digest; // Find the GGUF layer if (manifest.contains("layers")) { for (const auto & layer : manifest["layers"]) { if (layer.contains("mediaType")) { diff --git a/common/hf-cache.cpp b/common/hf-cache.cpp index f1dacaa4778d..50d6dd6105c4 100644 --- a/common/hf-cache.cpp +++ b/common/hf-cache.cpp @@ -4,9 +4,7 @@ #include "common.h" #include "log.h" #include "http.h" - -#define JSON_ASSERT GGML_ASSERT -#include +#include "json.h" #include #include @@ -15,8 +13,6 @@ #include #include -namespace nl = nlohmann; - #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX @@ -195,8 +191,8 @@ static void safe_write_file(const fs::path & path, const std::string & data) { } } -static nl::json api_get(const std::string & url, - const std::string & token) { +static common_json api_get(const std::string & url, + const std::string & token) { auto [cli, parts] = common_http_client(url); httplib::Headers headers = { @@ -214,10 +210,10 @@ static nl::json api_get(const std::string & url, auto body = res->body; if (res->status == 200) { - return nl::json::parse(res->body); + return common_json::parse(res->body); } try { - body = nl::json::parse(res->body)["error"].get(); + body = common_json::parse(res->body)["error"].get(); } catch (...) { } throw std::runtime_error("GET failed (" + std::to_string(res->status) + "): " + body); @@ -280,7 +276,7 @@ static std::string get_repo_commit(const std::string & repo_id, safe_write_file(refs_path / name, commit); return commit; - } catch (const nl::json::exception & e) { + } catch (const common_json_error & e) { LOG_ERR("%s: JSON error: %s\n", __func__, e.what()); } catch (const std::exception & e) { LOG_ERR("%s: error: %s\n", __func__, e.what()); @@ -358,7 +354,7 @@ hf_files get_repo_files(const std::string & repo_id, files.push_back(file); } - } catch (const nl::json::exception & e) { + } catch (const common_json_error & e) { LOG_ERR("%s: JSON error: %s\n", __func__, e.what()); } catch (const std::exception & e) { LOG_ERR("%s: error: %s\n", __func__, e.what()); diff --git a/common/jinja/README.md b/common/jinja/README.md index 8291240767e8..5b97fc92c5a7 100644 --- a/common/jinja/README.md +++ b/common/jinja/README.md @@ -7,7 +7,7 @@ The implementation can be found in the `common/jinja` directory. ## Key Features - Input marking: security against special token injection -- Decoupled from `nlohmann::json`: this dependency is only used for JSON-to-internal type translation and is completely optional +- Decoupled from the JSON library: `common_json` is only used for JSON-to-internal type translation and is completely optional - Minimal primitive types: int, float, bool, string, array, object, none, undefined - Detailed logging: allow source tracing on error - Clean architecture: workarounds are applied to input data before entering the runtime (see `common/chat.cpp`) diff --git a/common/jinja/caps.cpp b/common/jinja/caps.cpp index 6e3a1e9b298a..9971c021e188 100644 --- a/common/jinja/caps.cpp +++ b/common/jinja/caps.cpp @@ -4,14 +4,14 @@ // note: the json dependency is only for defining input in a convenient way // we can remove it in the future when we figure out a better way to define inputs using jinja::value -#include +#include "json.h" #include #include #define FILENAME "jinja-caps" -using json = nlohmann::ordered_json; +using json = common_json; namespace jinja { diff --git a/common/jinja/value.cpp b/common/jinja/value.cpp index 870596d617fb..6999ef7d6706 100644 --- a/common/jinja/value.cpp +++ b/common/jinja/value.cpp @@ -3,7 +3,7 @@ #include "value.h" // for converting from JSON to jinja values -#include +#include "json.h" #include #include @@ -1355,7 +1355,7 @@ const func_builtins & value_undefined_t::get_builtins() const { ////////////////////////////////// -static value from_json(const nlohmann::ordered_json & j, bool mark_input) { +static value from_json(const common_json & j, bool mark_input) { if (j.is_null()) { return mk_val(); } else if (j.is_boolean()) { @@ -1452,7 +1452,7 @@ bool value_compare(const value & a, const value & b, value_compare_op op) { } template<> -void global_from_json(context & ctx, const nlohmann::ordered_json & json_obj, bool mark_input) { +void global_from_json(context & ctx, const common_json & json_obj, bool mark_input) { // printf("global_from_json: %s\n" , json_obj.dump(2).c_str()); if (json_obj.is_null() || !json_obj.is_object()) { throw std::runtime_error("global_from_json: input JSON value must be an object"); diff --git a/common/jinja/value.h b/common/jinja/value.h index 5cf85e4f5443..4926fb68016d 100644 --- a/common/jinja/value.h +++ b/common/jinja/value.h @@ -86,7 +86,7 @@ struct context; // forward declaration // marking input can be useful for tracking data provenance // and preventing template injection attacks // -// Note: T_JSON can be nlohmann::ordered_json +// Note: T_JSON can be common_json template void global_from_json(context & ctx, const T_JSON & json_obj, bool mark_input); diff --git a/common/json-schema-to-grammar.cpp b/common/json-schema-to-grammar.cpp index 955b4e014b09..0aee51b26e84 100644 --- a/common/json-schema-to-grammar.cpp +++ b/common/json-schema-to-grammar.cpp @@ -1,9 +1,8 @@ #include "json-schema-to-grammar.h" #include "common.h" -#include - #include +#include #include #include #include @@ -12,7 +11,7 @@ #include #include -using json = nlohmann::ordered_json; +using json = common_json; static std::string build_repetition(const std::string & item_rule, int min_items, int max_items, const std::string & separator_rule = "") { auto has_max = max_items != std::numeric_limits::max(); @@ -917,7 +916,11 @@ class common_schema_converter { return _add_rule(rule_name, _resolve_ref(schema["$ref"])); } if (schema.contains("oneOf") || schema.contains("anyOf")) { - std::vector alt_schemas = schema.contains("oneOf") ? schema["oneOf"].get>() : schema["anyOf"].get>(); + const json & alts = schema.contains("oneOf") ? schema.at("oneOf") : schema.at("anyOf"); + std::vector alt_schemas; + for (const auto & alt : alts) { + alt_schemas.push_back(alt); + } return _add_rule(rule_name, _generate_union_rule(name, alt_schemas)); } if (schema_type.is_array()) { @@ -1111,7 +1114,7 @@ common_schema_info::~common_schema_info() = default; common_schema_info::common_schema_info(common_schema_info &&) noexcept = default; common_schema_info & common_schema_info::operator=(common_schema_info &&) noexcept = default; -void common_schema_info::resolve_refs(nlohmann::ordered_json & schema) { +void common_schema_info::resolve_refs(common_json & schema) { impl_->resolve_refs(schema, ""); } @@ -1119,7 +1122,7 @@ void common_schema_info::resolve_refs(nlohmann::ordered_json & schema) { // Some models emit raw string values rather than JSON-encoded strings for string parameters. // If any branch of the schema (via oneOf, anyOf, $ref, etc.) permits a string, this returns // true, allowing callers to handle the value as a raw string for simplicity. -bool common_schema_info::resolves_to_string(const nlohmann::ordered_json & schema) { +bool common_schema_info::resolves_to_string(const common_json & schema) { std::unordered_set visited_refs; std::function check = [&](const json & s) -> bool { @@ -1227,7 +1230,7 @@ bool common_schema_info::resolves_to_string(const nlohmann::ordered_json & schem return check(schema); } -std::string json_schema_to_grammar(const json & schema, bool force_gbnf) { +std::string json_schema_to_grammar(const common_json & schema, bool force_gbnf) { #ifdef LLAMA_USE_LLGUIDANCE if (!force_gbnf) { return "%llguidance {}\nstart: %json " + schema.dump(); @@ -1248,10 +1251,10 @@ std::string build_grammar(const std::function +#include "json.h" #include #include #include -std::string json_schema_to_grammar(const nlohmann::ordered_json & schema, +std::string json_schema_to_grammar(const common_json & schema, bool force_gbnf = false); class common_schema_converter; @@ -24,14 +24,14 @@ class common_schema_info { common_schema_info(common_schema_info &&) noexcept; common_schema_info & operator=(common_schema_info &&) noexcept; - void resolve_refs(nlohmann::ordered_json & schema); - bool resolves_to_string(const nlohmann::ordered_json & schema); + void resolve_refs(common_json & schema); + bool resolves_to_string(const common_json & schema); }; struct common_grammar_builder { std::function add_rule; - std::function add_schema; - std::function resolve_refs; + std::function add_schema; + std::function resolve_refs; }; struct common_grammar_options { diff --git a/common/json.cpp b/common/json.cpp new file mode 100644 index 000000000000..547542bb7d74 --- /dev/null +++ b/common/json.cpp @@ -0,0 +1,437 @@ +#include "json.h" + +#include "ggml.h" + +#define JSON_ASSERT GGML_ASSERT +#include + +#include +#include +#include +#include +#include + +using nlohmann::ordered_json; + +// a common_json is the backing value, so any value of a tree can be used as a common_json +static_assert(sizeof(ordered_json) <= sizeof(common_json), "common_json storage is too small"); +static_assert(alignof(ordered_json) <= alignof(common_json), "common_json alignment is too weak"); + +// runs fn and gives every error of the backing library as a common_json_error +template +static decltype(auto) guard(F && fn) { + try { + return fn(); + } catch (const ordered_json::exception & e) { + throw common_json_error(e.what()); + } +} + +static ordered_json & as_json(common_json * self) { + return *reinterpret_cast(self); +} + +static const ordered_json & as_json(const common_json * self) { + return *reinterpret_cast(self); +} + +static common_json & as_common(ordered_json & json) { + return *reinterpret_cast(&json); +} + +static const common_json & as_common(const ordered_json & json) { + return *reinterpret_cast(&json); +} + +static ordered_json to_json(const common_json_value & val) { + switch (val.type) { + case common_json_value::VAL_NULL: return nullptr; + case common_json_value::VAL_BOOL: return val.val_bool; + case common_json_value::VAL_INT: return val.val_int; + case common_json_value::VAL_UINT: return val.val_uint; + case common_json_value::VAL_DOUBLE: return val.val_double; + case common_json_value::VAL_STRING: return val.val_string; + case common_json_value::VAL_JSON: + // one owner means no one else can see this tree, so it is safe to move it out + // note: this makes a value single use, same as the json_ref of the backing library + if (val.val_json.use_count() == 1) { + return std::move(as_json(val.val_json.get())); + } + return as_json(val.val_json.get()); + } + + return nullptr; +} + +common_json_value::common_json_value(const char * val) { + if (val) { + type = VAL_STRING; + val_string = val; + } else { + type = VAL_NULL; + } +} + +common_json_value::common_json_value(const common_json & val) : + type(VAL_JSON), val_json(std::make_shared(val)) {} + +common_json_value::common_json_value(common_json && val) : + type(VAL_JSON), val_json(std::make_shared(std::move(val))) {} + +template +common_json_value::common_json_value(const std::set & vals) : type(VAL_JSON) { + common_json out = common_json::array(); + + for (const auto & val : vals) { + out.push_back(val); + } + + val_json = std::make_shared(std::move(out)); +} + +// a set value is usable only for the types below +#define COMMON_JSON_SET(...) template common_json_value::common_json_value(const std::set<__VA_ARGS__> &); + +COMMON_JSON_SET(int) +COMMON_JSON_SET(std::string) + +#undef COMMON_JSON_SET + +template +common_json_value::common_json_value(const std::map & vals) : type(VAL_JSON) { + common_json out = common_json::object(); + + for (const auto & val : vals) { + out.set({ val.first, val.second }); + } + + val_json = std::make_shared(std::move(out)); +} + +// a map value is usable only for the types below +#define COMMON_JSON_MAP(...) template common_json_value::common_json_value(const std::map &); + +COMMON_JSON_MAP(bool) +COMMON_JSON_MAP(std::string) + +#undef COMMON_JSON_MAP + +template +common_json_value::common_json_value(const std::unordered_map & vals) : type(VAL_JSON) { + common_json out = common_json::object(); + + for (const auto & val : vals) { + out.set({ val.first, val.second }); + } + + val_json = std::make_shared(std::move(out)); +} + +// an unordered map value is usable only for the types below +#define COMMON_JSON_UMAP(...) template common_json_value::common_json_value(const std::unordered_map &); + +COMMON_JSON_UMAP(size_t) + +#undef COMMON_JSON_UMAP + +template +common_json_value::common_json_value(const std::vector & vals) : type(VAL_JSON) { + common_json out = common_json::array(); + + for (const auto & val : vals) { + out.push_back(val); + } + + val_json = std::make_shared(std::move(out)); +} + +// a vector value is usable only for the types below +// note: std::vector is not here, its proxy reference does not convert +#define COMMON_JSON_VEC(...) template common_json_value::common_json_value(const std::vector<__VA_ARGS__> &); + +COMMON_JSON_VEC(int) +COMMON_JSON_VEC(unsigned char) +COMMON_JSON_VEC(unsigned int) +COMMON_JSON_VEC(long) +COMMON_JSON_VEC(unsigned long) +COMMON_JSON_VEC(long long) +COMMON_JSON_VEC(unsigned long long) +COMMON_JSON_VEC(float) +COMMON_JSON_VEC(double) +COMMON_JSON_VEC(std::string) +COMMON_JSON_VEC(std::vector) +COMMON_JSON_VEC(common_json) + +#undef COMMON_JSON_VEC + +common_json_value::common_json_value(std::initializer_list items) : + type(VAL_JSON), val_json(std::make_shared(items)) {} + +// null, same as the backing library +// operator[] turns it into an object, push_back() into an array +common_json::common_json() { + new (storage) ordered_json(); +} + +common_json::common_json(const common_json & other) { + new (storage) ordered_json(as_json(&other)); +} + +common_json::common_json(common_json && other) noexcept { + new (storage) ordered_json(std::move(as_json(&other))); +} + +common_json::common_json(std::initializer_list items) { + new (storage) ordered_json(ordered_json::object()); + + for (const auto & item : items) { + set(item); + } +} + +common_json::common_json(const common_json_value & val) { + new (storage) ordered_json(to_json(val)); +} + +common_json::common_json(std::nullptr_t) { + new (storage) ordered_json(nullptr); +} + +common_json & common_json::operator=(common_json other) noexcept { + as_json(this).swap(as_json(&other)); + + return *this; +} + +common_json::~common_json() { + as_json(this).~basic_json(); +} + +common_json common_json::parse(const std::string & text) { + try { + // the assignment moves the parsed tree in, it does not copy + common_json out; + as_json(&out) = ordered_json::parse(text); + return out; + } catch (const std::exception & e) { + throw common_json_error(e.what()); + } +} + +common_json common_json::parse_no_throw(const std::string & text) { + common_json out; + as_json(&out) = ordered_json::parse(text, nullptr, false); + return out; +} + +bool common_json::is_discarded() const { + return as_json(this).is_discarded(); +} + +common_json common_json::array() { + common_json out; + as_json(&out) = ordered_json::array(); + return out; +} + +common_json common_json::array(std::initializer_list vals) { + common_json out; + ordered_json & arr = as_json(&out); + arr = ordered_json::array(); + + for (const auto & val : vals) { + arr.push_back(to_json(val)); + } + + return out; +} + +common_json common_json::object() { + common_json out; + as_json(&out) = ordered_json::object(); + return out; +} + +common_json common_json::object(std::initializer_list items) { + return common_json(items); +} + +common_json common_json::make(const common_json_value & val) { + return common_json(val); +} + +bool common_json::is_null() const { return as_json(this).is_null(); } +bool common_json::is_object() const { return as_json(this).is_object(); } +bool common_json::is_array() const { return as_json(this).is_array(); } +bool common_json::is_string() const { return as_json(this).is_string(); } +bool common_json::is_boolean() const { return as_json(this).is_boolean(); } +bool common_json::is_number() const { return as_json(this).is_number(); } +bool common_json::is_number_integer() const { return as_json(this).is_number_integer(); } +bool common_json::is_number_float() const { return as_json(this).is_number_float(); } + +bool common_json::empty() const { return as_json(this).empty(); } +size_t common_json::size() const { return as_json(this).size(); } + +bool common_json::contains(const std::string & key) const { + return as_json(this).contains(key); +} + +bool common_json::operator==(const common_json_value & val) const { + // compare a tree in place, to_json() would copy it + if (val.type == common_json_value::VAL_JSON) { + return as_json(this) == as_json(val.val_json.get()); + } + return as_json(this) == to_json(val); +} + +bool common_json::operator!=(const common_json_value & val) const { + return !(*this == val); +} + +common_json & common_json::at(const std::string & key) { return guard([&]() -> common_json & { return as_common(as_json(this).at(key)); }); } +const common_json & common_json::at(const std::string & key) const { return guard([&]() -> const common_json & { return as_common(as_json(this).at(key)); }); } +common_json & common_json::at(size_t idx) { return guard([&]() -> common_json & { return as_common(as_json(this).at(idx)); }); } +const common_json & common_json::at(size_t idx) const { return guard([&]() -> const common_json & { return as_common(as_json(this).at(idx)); }); } + +common_json & common_json::operator[](const std::string & key) { return guard([&]() -> common_json & { return as_common(as_json(this)[key]); }); } +const common_json & common_json::operator[](const std::string & key) const { return guard([&]() -> const common_json & { return as_common(as_json(this).at(key)); }); } +common_json & common_json::operator[](size_t idx) { return guard([&]() -> common_json & { return as_common(as_json(this)[idx]); }); } +const common_json & common_json::operator[](size_t idx) const { return guard([&]() -> const common_json & { return as_common(as_json(this).at(idx)); }); } + +common_json & common_json::front() { return as_common(as_json(this).front()); } +const common_json & common_json::front() const { return as_common(as_json(this).front()); } +common_json & common_json::back() { return as_common(as_json(this).back()); } +const common_json & common_json::back() const { return as_common(as_json(this).back()); } + +void common_json::clear() { + as_json(this).clear(); +} + +void common_json::erase(const std::string & key) { + guard([&] { as_json(this).erase(key); }); +} + +void common_json::erase(size_t idx) { + guard([&] { as_json(this).erase(idx); }); +} + +void common_json::assign(const common_json_value & val) { + as_json(this) = to_json(val); +} + +void common_json::set(const common_json_item & item) { + guard([&] { as_json(this)[item.key] = to_json(item.val); }); +} + +void common_json::push_back(const common_json_value & val) { + guard([&] { as_json(this).push_back(to_json(val)); }); +} + +void common_json::push_back(std::initializer_list items) { + common_json val(items); + + guard([&] { as_json(this).push_back(std::move(as_json(&val))); }); +} + +size_t common_json::count(const std::string & key) const { + return as_json(this).count(key); +} + +void common_json::insert(const common_json & vals) { + guard([&] { + ordered_json & self = as_json(this); + + self.insert(self.end(), as_json(&vals).begin(), as_json(&vals).end()); + }); +} + +std::string common_json::dump(int indent) const { + return guard([&] { return as_json(this).dump(indent); }); +} + +std::string common_json::dump_safe(int indent) const { + return as_json(this).dump(indent, ' ', false, ordered_json::error_handler_t::replace); +} + +// an array is indexed directly, an object needs a walk from the start +common_json & common_json::iterator::operator*() const { + return guard([&]() -> common_json & { + ordered_json & j = as_json(node); + + if (j.is_object()) { + return as_common(std::next(j.begin(), idx).value()); + } + if (j.is_array()) { + return as_common(j[idx]); + } + + // a plain value gives itself once, same as the backing library + return *node; + }); +} + +std::string common_json::iterator::key() const { + return guard([&] { return std::next(as_json(node).begin(), idx).key(); }); +} + +common_json::iterator common_json::begin() const { + return iterator(const_cast(this), 0); +} + +common_json::iterator common_json::end() const { + return iterator(const_cast(this), size()); +} + +// the keys follow the backing library: the index for an array, "" for a plain value +common_json::items_view::entry common_json::items_view::iterator::operator*() const { + return guard([&]() -> entry { + ordered_json & j = as_json(node); + + if (j.is_object()) { + auto it = std::next(j.begin(), idx); + + return { it.key(), as_common(it.value()) }; + } + if (j.is_array()) { + return { std::to_string(idx), as_common(j[idx]) }; + } + + return { std::string(), *node }; + }); +} + +common_json::items_view common_json::items() const { + return items_view(const_cast(this), size()); +} + +template T common_json::get() const { + return guard([&] { return as_json(this).get(); }); +} + +// the backing library cannot build a common_json, so this one is just a copy +template <> common_json common_json::get() const { + return *this; +} + +// get() is usable only for the types below + +#define COMMON_JSON_GET(...) template __VA_ARGS__ common_json::get<__VA_ARGS__>() const; + +COMMON_JSON_GET(bool) +COMMON_JSON_GET(int) +COMMON_JSON_GET(unsigned int) +COMMON_JSON_GET(long) +COMMON_JSON_GET(unsigned long) +COMMON_JSON_GET(long long) +COMMON_JSON_GET(unsigned long long) +COMMON_JSON_GET(float) +COMMON_JSON_GET(double) +COMMON_JSON_GET(std::string) +COMMON_JSON_GET(std::vector) +COMMON_JSON_GET(std::vector) +COMMON_JSON_GET(std::set) +COMMON_JSON_GET(std::vector) +COMMON_JSON_GET(std::vector) +COMMON_JSON_GET(std::unordered_map) + +#undef COMMON_JSON_GET diff --git a/common/json.h b/common/json.h new file mode 100644 index 000000000000..9e20a2adb2f0 --- /dev/null +++ b/common/json.h @@ -0,0 +1,354 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// common_json, a thin wrapper around vendor json library +// the underlay library is pimpl, we are using nlohmann::json for now +// +// many features of the library are deliberately left out, to keep this interface small and generic and to keep compile time down +// +// some main differences compared to nlohmann::json : +// - object keys keep the order in which they are added +// - errors are always throw as common_json_error +// - obj.push_back({key, val}) is intentionally unsupported to avoid confusion with push_back on a vector; write it as obj[key] = val for clarity +// - a braced pair in value position does not build, e.g. {"key", {"a", "b"}}; write array({"a", "b"}) where nlohmann made an array +// +// in doubt, search the code base for an existing usage example; do not add anything to this header unless absolutely necessary + +class common_json; + +// common_json_value holds a list of these, and each of them holds a value, so one must come first +struct common_json_item; + +struct common_json_error : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +// one value, tagged so that this header stays free of the backing library +// note: a value that holds a tree is single use, the second use gives null +struct common_json_value { + enum value_type { + VAL_NULL, + VAL_BOOL, + VAL_INT, + VAL_UINT, + VAL_DOUBLE, + VAL_STRING, + VAL_JSON, + }; + + value_type type = VAL_NULL; + + union { + bool val_bool; + int64_t val_int; + uint64_t val_uint = 0; + double val_double; + }; + + std::string val_string; + std::shared_ptr val_json; + + common_json_value(std::nullptr_t = nullptr) : type(VAL_NULL) {} + common_json_value(bool val) : type(VAL_BOOL), val_bool(val) {} + common_json_value(std::string val) : type(VAL_STRING), val_string(std::move(val)) {} + // without this a string_view lands on the common_json ctor below and recurses + common_json_value(std::string_view val) : type(VAL_STRING), val_string(val) {} + common_json_value(const char * val); + common_json_value(const common_json & val); + common_json_value(common_json && val); + // only for the types instantiated in json.cpp, the rest fails at link time + template common_json_value(const std::vector & vals); + // a set becomes an array, in the set's own order + template common_json_value(const std::set & vals); + // a map becomes an object, keyed in the map's own order + template common_json_value(const std::map & vals); + template common_json_value(const std::unordered_map & vals); + + // nested object, e.g. {"fn", {{"name", "x"}}} + // note: a nested pair {"a", "b"} does not build, use common_json::array({"a", "b"}) for an array + common_json_value(std::initializer_list items); + + template ::value && !std::is_same::value, int>::type = 0> + common_json_value(T val) : type(std::is_signed::value ? VAL_INT : VAL_UINT) { + if (std::is_signed::value) { + val_int = (int64_t) val; + } else { + val_uint = (uint64_t) val; + } + } + + template ::value, int>::type = 0> + common_json_value(T val) : type(VAL_DOUBLE), val_double((double) val) {} +}; + +struct common_json_item { + std::string key; + common_json_value val; + + template + common_json_item(std::string key, T && val) : + key(std::move(key)), val(std::forward(val)) {} + + // a braced list cannot deduce T, so it needs its own overload + common_json_item(std::string key, std::initializer_list items) : + key(std::move(key)), val(items) {} +}; + +// the types common_json_value holds on its own +// anything else reaches its common_json ctor and recurses forever +template struct common_json_is_value : std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value> {}; + +template +struct common_json_is_value> : std::true_type {}; + +template +struct common_json_is_value> : std::true_type {}; + +template +struct common_json_is_value> : std::true_type {}; + +template +struct common_json_is_value> : std::true_type {}; + +class common_json { + public: + common_json(); + common_json(const common_json & other); + common_json(common_json && other) noexcept; + common_json(std::initializer_list items); + common_json(const common_json_value & val); + + // direct, a value would need two conversions in a row + common_json(std::nullptr_t); + + // one step, so that "abc" or a vector can go straight into a common_json + template ::type, common_json>::value && + !std::is_same::type, common_json_value>::value, int>::type = 0> + common_json(T && val) : common_json(common_json_value(std::forward(val))) { + static_assert(common_json_is_value::type>::value, + "no common_json_value ctor holds this type, add one instead of letting it recurse"); + } + + // by value, same as the backing library + // the right side is copied before the left side can invalidate it, e.g. msg["a"] = msg.at("b") + common_json & operator=(common_json other) noexcept; + + ~common_json(); + + // throws common_json_error if the text is not valid JSON + static common_json parse(const std::string & text); + + // gives a discarded value instead of throwing, check it with is_discarded() + static common_json parse_no_throw(const std::string & text); + + bool is_discarded() const; + + static common_json array(); + static common_json array(std::initializer_list vals); + static common_json object(); + static common_json object(std::initializer_list items); + + // holds a single value, e.g. make("abc").dump() gives "\"abc\"" + static common_json make(const common_json_value & val); + + bool is_null() const; + bool is_object() const; + bool is_array() const; + bool is_string() const; + bool is_boolean() const; + bool is_number() const; + bool is_number_integer() const; + bool is_number_float() const; + + bool empty() const; + size_t size() const; + + bool contains(const std::string & key) const; + + bool operator==(const common_json_value & val) const; + bool operator!=(const common_json_value & val) const; + + // at() throws common_json_error if the key is missing, operator[] adds a null value instead + // note: a const operator[] cannot add, it throws like at() + common_json & at(const std::string & key); + const common_json & at(const std::string & key) const; + common_json & at(size_t idx); + const common_json & at(size_t idx) const; + + common_json & operator[](const std::string & key); + const common_json & operator[](const std::string & key) const; + common_json & operator[](const char * key) { return (*this)[std::string(key)]; } + const common_json & operator[](const char * key) const { return (*this)[std::string(key)]; } + common_json & operator[](int idx) { return (*this)[to_idx(idx)]; } + const common_json & operator[](int idx) const { return (*this)[to_idx(idx)]; } + common_json & operator[](size_t idx); + const common_json & operator[](size_t idx) const; + + common_json & front(); + const common_json & front() const; + common_json & back(); + const common_json & back() const; + + void clear(); + + void erase(const std::string & key); + void erase(size_t idx); + + // only for the types instantiated in json.cpp, the rest fails at link time + template T get() const; + + // implicit get() for plain values, so they can be assigned to their C++ type directly + // note: kept to this short list on purpose, a wider one makes j["key"] ambiguous + // note: a numeric one would make "str = json;" ambiguous, a number converts to char too + operator std::string() const { return get(); } + + template + T value(const std::string & key, T def) const { + return contains(key) ? at(key).get() : def; + } + + std::string value(const std::string & key, const char * def) const { + return contains(key) ? at(key).get() : std::string(def); + } + + // a JSON default needs no get(), it is already the right type + common_json value(const std::string & key, const common_json & def) const { + return contains(key) ? at(key) : def; + } + + void assign(const common_json_value & val); + void set(const common_json_item & item); + void push_back(const common_json_value & val); + + // appends one object, e.g. push_back({{"a", 1}}) + void push_back(std::initializer_list items); + + // 1 if the key is there, 0 if not + size_t count(const std::string & key) const; + + // appends every value of another array; inserting an array into itself throws + void insert(const common_json & vals); + + // a common_json goes through the copy assignment above, everything else becomes a value + template ::type, common_json>::value, int>::type = 0> + common_json & operator=(T && val) { + assign(common_json_value(std::forward(val))); + return *this; + } + + std::string dump(int indent = -1) const; + + // same as dump(), but bad UTF-8 gets replaced instead of throwing + std::string dump_safe(int indent = -1) const; + + // walks an array by index, or an object in insertion order + // a plain value gives itself once, same as the backing library + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = common_json; + using difference_type = std::ptrdiff_t; + using pointer = common_json *; + using reference = common_json &; + + iterator(common_json * node, size_t idx) : node(node), idx(idx) {} + + common_json & operator*() const; + common_json & value() const { return **this; } + std::string key() const; + + iterator & operator++() { + idx++; + return *this; + } + + bool operator!=(const iterator & other) const { return idx != other.idx; } + bool operator==(const iterator & other) const { return idx == other.idx; } + + private: + common_json * node; + size_t idx; + }; + + iterator begin() const; + iterator end() const; + + // allows: for (const auto & [key, val] : obj.items()) + class items_view { + public: + // the members are public, so an entry also works with structured bindings + struct entry { + std::string k; + common_json & v; + + const std::string & key() const { return k; } + common_json & value() const { return v; } + }; + + items_view(common_json * node, size_t n) : node(node), n(n) {} + + class iterator { + public: + iterator(common_json * node, size_t idx) : node(node), idx(idx) {} + + entry operator*() const; + + iterator & operator++() { + idx++; + return *this; + } + + bool operator!=(const iterator & other) const { return idx != other.idx; } + + private: + common_json * node; + size_t idx; + }; + + iterator begin() const { return iterator(node, 0); } + iterator end() const { return iterator(node, n); } + + private: + common_json * node; + size_t n; + }; + + items_view items() const; + + private: + // a negative index must not turn into a huge size_t + static size_t to_idx(int idx) { + if (idx < 0) { + throw common_json_error("negative array index"); + } + return (size_t) idx; + } + + // the backing value is built here, json.cpp checks that it fits + // it cannot be a pointer: a value inside a tree would then not be a common_json + // at() could then only give back a copy instead of a real reference + alignas(8) unsigned char storage[32]; +}; + +using common_json_entry = common_json::items_view::entry; diff --git a/common/peg-parser.cpp b/common/peg-parser.cpp index 4a4be7cf789f..46fc29bf2f8b 100644 --- a/common/peg-parser.cpp +++ b/common/peg-parser.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -1120,8 +1119,8 @@ common_peg_parser common_peg_parser_builder::chars(const std::string & classes, return wrap(arena_.add_parser(common_peg_chars_parser{classes, ranges, negated, min, max})); } -common_peg_parser common_peg_parser_builder::schema(const common_peg_parser & p, const std::string & name, const nlohmann::ordered_json & schema, bool raw) { - return wrap(arena_.add_parser(common_peg_schema_parser{p.id(), name, std::make_shared(schema), raw})); +common_peg_parser common_peg_parser_builder::schema(const common_peg_parser & p, const std::string & name, const common_json & schema, bool raw) { + return wrap(arena_.add_parser(common_peg_schema_parser{p.id(), name, std::make_shared(schema), raw})); } common_peg_parser common_peg_parser_builder::rule(const std::string & name, const common_peg_parser & p, bool trigger) { @@ -1805,8 +1804,8 @@ void common_peg_arena::build_grammar(const common_grammar_builder & builder, boo } } -static nlohmann::json serialize_parser_variant(const common_peg_parser_variant & variant) { - using json = nlohmann::json; +static common_json serialize_parser_variant(const common_peg_parser_variant & variant) { + using json = common_json; return std::visit([](const auto & p) -> json { using T = std::decay_t; @@ -1860,7 +1859,7 @@ static nlohmann::json serialize_parser_variant(const common_peg_parser_variant & {"type", "schema"}, {"child", p.child}, {"name", p.name}, - {"schema", p.schema ? *p.schema : nullptr}, + {"schema", p.schema ? *p.schema : json(nullptr)}, {"raw", p.raw} }; } else if constexpr (std::is_same_v) { @@ -1888,19 +1887,19 @@ static nlohmann::json serialize_parser_variant(const common_peg_parser_variant & }, variant); } -nlohmann::json common_peg_arena::to_json() const { - auto parsers = nlohmann::json::array(); +common_json common_peg_arena::to_json() const { + auto parsers = common_json::array(); for (const auto & parser : parsers_) { parsers.push_back(serialize_parser_variant(parser)); } - return nlohmann::json{ + return common_json{ {"parsers", parsers}, {"rules", rules_}, {"root", root_} }; } -static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json & j) { +static common_peg_parser_variant deserialize_parser_variant(const common_json & j) { if (!j.contains("type") || !j["type"].is_string()) { throw std::runtime_error("Parser variant JSON missing or invalid 'type' field"); } @@ -1969,9 +1968,9 @@ static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json } common_peg_chars_parser parser; parser.pattern = j["pattern"]; - parser.negated = j["negated"]; - parser.min_count = j["min_count"]; - parser.max_count = j["max_count"]; + parser.negated = j["negated"].get(); + parser.min_count = j["min_count"].get(); + parser.max_count = j["max_count"].get(); for (const auto & range_json : j["ranges"]) { if (!range_json.contains("start") || !range_json.contains("end")) { throw std::runtime_error("char_range missing 'start' or 'end' field"); @@ -2007,7 +2006,7 @@ static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json parser.child = j["child"].get(); parser.name = j["name"]; if (!j["schema"].is_null()) { - parser.schema = std::make_shared(j["schema"]); + parser.schema = std::make_shared(j["schema"]); } parser.raw = j["raw"].get(); return parser; @@ -2069,7 +2068,7 @@ static common_peg_parser_variant deserialize_parser_variant(const nlohmann::json throw std::runtime_error("Unknown parser type: " + type); } -common_peg_arena common_peg_arena::from_json(const nlohmann::json & j) { +common_peg_arena common_peg_arena::from_json(const common_json & j) { if (!j.contains("parsers") || !j["parsers"].is_array()) { throw std::runtime_error("JSON missing or invalid 'parsers' array"); } @@ -2109,7 +2108,7 @@ std::string common_peg_arena::save() const { } void common_peg_arena::load(const std::string & data) { - *this = from_json(nlohmann::json::parse(data)); + *this = from_json(common_json::parse(data)); } common_peg_arena build_peg_parser(const std::function & fn) { diff --git a/common/peg-parser.h b/common/peg-parser.h index c198499dd934..ab095cc7d671 100644 --- a/common/peg-parser.h +++ b/common/peg-parser.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "json.h" #include #include @@ -245,7 +245,7 @@ struct common_peg_until_parser { struct common_peg_schema_parser { common_peg_parser_id child; std::string name; - std::shared_ptr schema; + std::shared_ptr schema; // Indicates if the GBNF should accept a raw string that matches the schema. bool raw; @@ -332,8 +332,8 @@ class common_peg_arena { std::string dump(common_peg_parser_id id) const; - nlohmann::json to_json() const; - static common_peg_arena from_json(const nlohmann::json & j); + common_json to_json() const; + static common_peg_arena from_json(const common_json & j); std::string save() const; void load(const std::string & data); @@ -490,7 +490,7 @@ class common_peg_parser_builder { // Wraps a parser with JSON schema metadata for grammar generation. // Used internally to convert JSON schemas to GBNF grammar rules. - common_peg_parser schema(const common_peg_parser & p, const std::string & name, const nlohmann::ordered_json & schema, bool raw = false); + common_peg_parser schema(const common_peg_parser & p, const std::string & name, const common_json & schema, bool raw = false); // Creates a named rule, stores it in the grammar, and returns a ref. // If trigger=true, marks this rule as an entry point for lazy grammar generation. diff --git a/tests/peg-parser/test-json-serialization.cpp b/tests/peg-parser/test-json-serialization.cpp index a85801060c0b..da63a23bf218 100644 --- a/tests/peg-parser/test-json-serialization.cpp +++ b/tests/peg-parser/test-json-serialization.cpp @@ -8,7 +8,7 @@ void test_json_serialization(testing &t) { auto json_serialized = original.to_json().dump(); t.test("compare before/after", [&](testing &t) { - auto deserialized = common_peg_arena::from_json(nlohmann::json::parse(json_serialized)); + auto deserialized = common_peg_arena::from_json(common_json::parse(json_serialized)); // Test complex JSON std::string input = R"({"name": "test", "values": [1, 2, 3], "nested": {"a": true}})"; @@ -23,6 +23,6 @@ void test_json_serialization(testing &t) { }); t.bench("deserialize", [&]() { - auto deserialized = common_peg_arena::from_json(nlohmann::json::parse(json_serialized)); + auto deserialized = common_peg_arena::from_json(common_json::parse(json_serialized)); }, 100); } diff --git a/tests/peg-parser/tests.h b/tests/peg-parser/tests.h index debd4286c50a..00e81815b621 100644 --- a/tests/peg-parser/tests.h +++ b/tests/peg-parser/tests.h @@ -1,7 +1,7 @@ #pragma once // Common includes for all test files -#include +#include "json.h" #include #include @@ -11,9 +11,9 @@ #include "simple-tokenize.h" struct bench_tool_call { - std::string id; - std::string name; - nlohmann::ordered_json args; + std::string id; + std::string name; + common_json args; }; // Test function declarations diff --git a/tests/test-chat-peg-parser.cpp b/tests/test-chat-peg-parser.cpp index 3ab7a67b6a82..793891394ce6 100644 --- a/tests/test-chat-peg-parser.cpp +++ b/tests/test-chat-peg-parser.cpp @@ -11,9 +11,9 @@ #include #include -#include "nlohmann/json.hpp" +#include "json.h" -using json = nlohmann::ordered_json; +using json = common_json; static json create_tools(); static void test_example_native(testing & t); @@ -63,10 +63,10 @@ static json create_tools() { { { "type", "string" }, { "description", "The city and state, e.g. San Francisco, CA" } } }, { "unit", { { "type", "string" }, - { "enum", { "celsius", "fahrenheit" } }, + { "enum", json::array({ "celsius", "fahrenheit" }) }, { "description", "The temperature unit to use. Infer this from the users location." } } } } }, - { "required", { "location", "unit" } }, + { "required", json::array({ "location", "unit" }) }, } }, } } }; @@ -86,14 +86,14 @@ static json create_tools() { { { "type", "string" }, { "description", "The city and state, e.g. San Francisco, CA" } } }, { "unit", { { "type", "string" }, - { "enum", { "celsius", "fahrenheit" } }, + { "enum", json::array({ "celsius", "fahrenheit" }) }, { "description", "The temperature unit to use. Infer this from the users location." } } }, { "days", { { "type", "integer" }, { "description", "Number of days to forecast (1-10)" }, { "minimum", 1 }, { "maximum", 10 } } } } }, - { "required", { "location", "unit" } }, + { "required", json::array({ "location", "unit" }) }, } }, } } }; @@ -114,9 +114,9 @@ static json create_tools() { { "default", 5 } } }, { "category", { { "type", "string" }, - { "enum", { "api", "troubleshooting", "billing", "general" } }, + { "enum", json::array({ "api", "troubleshooting", "billing", "general" }) }, { "description", "Filter search by specific category." } } } } }, - { "required", { "query", "category" } }, + { "required", json::array({ "query", "category" }) }, { "additionalProperties", false } } }, { "strict", true } } } }; @@ -341,7 +341,7 @@ static void test_example_native(testing & t) { { { "invoice_number", { { "type", "string" } } }, { "amount", { { "type", "number" } } }, { "due_date", { { "type", "string" } } } } }, - { "required", { "invoice_number", "amount", "due_date" } } }, + { "required", json::array({ "invoice_number", "amount", "due_date" }) } }, /* .parallel_tool_calls = */ false, /* .generation_prompt = */ "", /* .input = */ @@ -406,7 +406,7 @@ static void test_example_qwen3_coder(testing & t) { std::set required_properties; if (function.contains("required")) { - function.at("required").get_to(required_properties); + required_properties = function.at("required").get>(); } std::vector arg_parsers; @@ -661,8 +661,8 @@ void test_command7_parser_compare(testing & t) { "5. Provide a detailed cost breakdown that includes accommodation, transportation, meals, and entry fees " "to attractions."; - std::vector> tool_calls = { - { "call_0", "plan_trip", nlohmann::json::parse(R"({ + std::vector> tool_calls = { + { "call_0", "plan_trip", common_json::parse(R"({ "destination": "Japan", "duration": 14, "budget": 4000, @@ -686,16 +686,16 @@ void test_command7_parser_compare(testing & t) { if (!tool_calls.empty()) { tokens.emplace_back("<|START_ACTION|>"); - auto json = nlohmann::json::array(); + auto json = common_json::array(); for (const auto & tc : tool_calls) { - auto tc_json = nlohmann::json::object(); + auto tc_json = common_json::object(); tc_json["tool_call_id"] = std::get<0>(tc); tc_json["tool_name"] = std::get<1>(tc); tc_json["parameters"] = std::get<2>(tc); json.push_back(tc_json); } - auto tokenized = simple_tokenize(json.dump(-1, ' ', true)); + auto tokenized = simple_tokenize(json.dump(-1)); tokens.insert(tokens.end(), tokenized.begin(), tokenized.end()); tokens.emplace_back("<|END_ACTION|>"); @@ -737,7 +737,7 @@ static void test_prefix_tool_names(testing & t) { { { "arg1", { { "type", "integer" } } }, } }, - { "required", { "arg1" } }, + { "required", json::array({ "arg1" }) }, } }, } } }; @@ -757,7 +757,7 @@ static void test_prefix_tool_names(testing & t) { { "arg1", { { "type", "integer" } } }, { "arg2", { { "type", "integer" } } }, } }, - { "required", { "arg1" } }, + { "required", json::array({ "arg1" }) }, } }, } } }; diff --git a/tests/test-chat-template.cpp b/tests/test-chat-template.cpp index 6a6292cd0151..bcc574afe981 100644 --- a/tests/test-chat-template.cpp +++ b/tests/test-chat-template.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include "json.h" #undef NDEBUG #include @@ -20,7 +20,7 @@ #include "jinja/lexer.h" #include "jinja/caps.h" -using json = nlohmann::ordered_json; +using json = common_json; static int main_automated_tests(void); @@ -304,8 +304,8 @@ void run_single(const std::string& contents, json input, bool use_common, bool d if (input.contains("eos_token")) { eos_token = input["eos_token"].get(); } - nlohmann::ordered_json msgs_json = input["messages"]; - nlohmann::ordered_json tools_json = input["tools"]; + common_json msgs_json = input["messages"]; + common_json tools_json = input["tools"]; auto messages = common_chat_msgs_parse_oaicompat(msgs_json); auto tools = common_chat_tools_parse_oaicompat(tools_json); auto output = format_using_common(contents, bos_token, eos_token, messages, tools); diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index c4670da85302..7918f0ffcf48 100644 --- a/tests/test-chat.cpp +++ b/tests/test-chat.cpp @@ -19,12 +19,12 @@ #include #include #include -#include +#include "json.h" #include #include #include -using json = nlohmann::ordered_json; +using json = common_json; static std::ostream & operator<<(std::ostream & os, const common_chat_msg_diff & diff) { os << "{ content_delta: " << diff.content_delta << "; "; diff --git a/tests/test-grammar-integration.cpp b/tests/test-grammar-integration.cpp index 4d5d13dd0d38..eb4b7c78f50f 100644 --- a/tests/test-grammar-integration.cpp +++ b/tests/test-grammar-integration.cpp @@ -7,13 +7,13 @@ #include "../src/unicode.h" #include "../src/llama-grammar.h" -#include +#include "json.h" #include #include #include -using json = nlohmann::ordered_json; +using json = common_json; static llama_grammar * build_grammar_with_root(const std::string & grammar_str, const char * grammar_root) { return llama_grammar_init_impl(nullptr, grammar_str.c_str(), grammar_root, false, nullptr, 0, nullptr, 0); diff --git a/tests/test-jinja.cpp b/tests/test-jinja.cpp index 1eb2a062b75f..974a3f9dd8df 100644 --- a/tests/test-jinja.cpp +++ b/tests/test-jinja.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include "json.h" #include "subproc.h" #include "jinja/runtime.h" @@ -14,7 +14,7 @@ #include "testing.h" -using json = nlohmann::ordered_json; +using json = common_json; static void test_template(testing & t, const std::string & name, const std::string & tmpl, const json & vars, const std::string & expect); @@ -240,7 +240,7 @@ static void test_conditionals(testing & t) { test_template(t, "is undefined key falsy", "{{ 'yes' if not y['x'] else 'no' }}", - {{"y", {{}}}}, + {{"y", json::array({nullptr})}}, "yes" ); @@ -282,7 +282,7 @@ static void test_conditionals(testing & t) { test_template(t, "is non-empty object truthy", "{{ 'yes' if y else 'no' }}", - {{"y", {"x", false}}}, + {{"y", json::array({"x", false})}}, "yes" ); diff --git a/tests/test-json-schema-to-grammar.cpp b/tests/test-json-schema-to-grammar.cpp index 74b57cf1b669..214dbe1993b8 100755 --- a/tests/test-json-schema-to-grammar.cpp +++ b/tests/test-json-schema-to-grammar.cpp @@ -6,7 +6,7 @@ #include "../src/llama-grammar.h" -#include +#include "json.h" #include #include @@ -1442,7 +1442,7 @@ static void test_resolves_to_string() { auto test = [](const std::string & name, const std::string & schema_str, bool expected) { fprintf(stderr, "- %s\n", name.c_str()); common_schema_info info; - auto schema = nlohmann::ordered_json::parse(schema_str); + auto schema = common_json::parse(schema_str); info.resolve_refs(schema); bool result = info.resolves_to_string(schema); if (result != expected) { @@ -1517,7 +1517,7 @@ int main() { test_all("C++", [](const TestCase & tc) { try { - tc.verify(json_schema_to_grammar(nlohmann::ordered_json::parse(tc.schema), true)); + tc.verify(json_schema_to_grammar(common_json::parse(tc.schema), true)); tc.verify_status(SUCCESS); } catch (const std::invalid_argument & ex) { fprintf(stderr, "Error: %s\n", ex.what()); @@ -1531,7 +1531,7 @@ int main() { auto run = [](const TestCase & tc) { fprintf(stderr, "- %s\n", tc.name.c_str()); try { - tc.verify(json_schema_to_grammar(nlohmann::ordered_json::parse(tc.schema), true)); + tc.verify(json_schema_to_grammar(common_json::parse(tc.schema), true)); tc.verify_status(SUCCESS); } catch (const std::invalid_argument & ex) { fprintf(stderr, "Error: %s\n", ex.what()); diff --git a/tests/test-model-resolution.cpp b/tests/test-model-resolution.cpp index 2437eeec608c..5191e77514ab 100644 --- a/tests/test-model-resolution.cpp +++ b/tests/test-model-resolution.cpp @@ -9,7 +9,7 @@ #include "http.h" #include "log.h" -#include +#include "json.h" #include #include @@ -55,7 +55,7 @@ static const char * COMMIT = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; static void serve_repos(httplib::Server & server) { server.Get(R"(/api/models/(.+)/refs)", [](const httplib::Request & req, httplib::Response & res) { if (g_repos.count(req.matches[1])) { - res.set_content(nlohmann::json{{"branches", {{{"name", "main"}, {"targetCommit", COMMIT}}}}}.dump(), + res.set_content(common_json{{"branches", common_json::array({ common_json{{"name", "main"}, {"targetCommit", COMMIT}} })}}.dump(), "application/json"); } else { res.status = 404; @@ -66,7 +66,7 @@ static void serve_repos(httplib::Server & server) { res.status = 404; return; } - auto files = nlohmann::json::array(); + auto files = common_json::array(); size_t i = 0; for (const auto & p : g_repos[req.matches[1]]) { char oid[41]; diff --git a/tools/cli/cli-context.cpp b/tools/cli/cli-context.cpp index 3d801b73d4c1..aa4eb76796d9 100644 --- a/tools/cli/cli-context.cpp +++ b/tools/cli/cli-context.cpp @@ -6,8 +6,7 @@ #include "log.h" #include "console.h" -#define JSON_ASSERT GGML_ASSERT -#include +#include "json.h" #include #include @@ -16,7 +15,7 @@ #include #include -using json = nlohmann::ordered_json; +using json = common_json; struct cli_context_impl { json messages = json::array(); @@ -73,7 +72,7 @@ static std::string format_error_message(const json & err) { // err is the raw response body of a failed request; it may or may not be JSON static std::string format_error_message(const std::string & err) { - json parsed = json::parse(err, nullptr, false); + json parsed = json::parse_no_throw(err); if (!parsed.is_discarded()) { return format_error_message(parsed); } @@ -157,7 +156,7 @@ bool cli_context::init() { if (!list_and_ask_models()) { return false; } - } catch (const json::parse_error & e) { + } catch (const common_json_error & e) { ui::show_error(e.what()); ui::show_message("This might be caused by an incorrect server-base endpoint URL"); return false; @@ -364,7 +363,7 @@ bool cli_context::generate_completion(generated_content & content_out, cli_timin ui::assistant_turn a; std::string err = client.post_sse("/v1/chat/completions", body.dump(), should_stop, [&](const std::string & payload) { - json chunk = json::parse(payload, nullptr, false); + json chunk = json::parse_no_throw(payload); if (chunk.is_discarded()) { return; } diff --git a/tools/parser/debug-template-parser.cpp b/tools/parser/debug-template-parser.cpp index 8a916f79c78e..abe427022332 100644 --- a/tools/parser/debug-template-parser.cpp +++ b/tools/parser/debug-template-parser.cpp @@ -5,7 +5,7 @@ #include "gguf.h" #include "jinja/runtime.h" #include "log.h" -#include "nlohmann/json.hpp" +#include "json.h" #include "peg-parser.h" #include @@ -15,7 +15,7 @@ #include #include -using json = nlohmann::ordered_json; +using json = common_json; enum class output_mode { ANALYSIS, // Only output analysis results (default) diff --git a/tools/parser/template-analysis.cpp b/tools/parser/template-analysis.cpp index bf898a2290f1..11225bd8c000 100644 --- a/tools/parser/template-analysis.cpp +++ b/tools/parser/template-analysis.cpp @@ -11,9 +11,9 @@ #include #include -#include "nlohmann/json.hpp" +#include "json.h" -using json = nlohmann::ordered_json; +using json = common_json; // ANSI color codes - using 256-color palette for brighter colors (all bold) #define ANSI_RESET "\033[0m" diff --git a/tools/server/server-chat.cpp b/tools/server/server-chat.cpp index 0322e54ccea8..a6fe3c6ba619 100644 --- a/tools/server/server-chat.cpp +++ b/tools/server/server-chat.cpp @@ -153,7 +153,7 @@ json server_chat_convert_responses_to_chatcmpl(const json & response_body) { prev_msg["content"] = json::array(); } auto & prev_content = prev_msg["content"]; - prev_content.insert(prev_content.end(), chatcmpl_content.begin(), chatcmpl_content.end()); + prev_content.insert(chatcmpl_content); } else { item.erase("status"); item.erase("type"); diff --git a/tools/server/server-chat.h b/tools/server/server-chat.h index 102eae688a31..86b842650ea3 100644 --- a/tools/server/server-chat.h +++ b/tools/server/server-chat.h @@ -6,9 +6,7 @@ #include "server-common.h" #include "server-http.h" -#include - -using json = nlohmann::ordered_json; +#include "json.h" // Convert OpenAI Responses API format to OpenAI Chat Completions API format json server_chat_convert_responses_to_chatcmpl(const json & body); diff --git a/tools/server/server-common.cpp b/tools/server/server-common.cpp index 585f65e83c65..4f5b8202aca5 100644 --- a/tools/server/server-common.cpp +++ b/tools/server/server-common.cpp @@ -1540,7 +1540,7 @@ std::vector get_token_probabilities(llama_context * ctx, int i } std::string safe_json_to_str(const json & data) { - return data.dump(-1, ' ', false, json::error_handler_t::replace); + return data.dump_safe(); } // TODO: reuse llama_detokenize diff --git a/tools/server/server-common.h b/tools/server/server-common.h index 6488be344c6a..f8ea82ef4cf5 100644 --- a/tools/server/server-common.h +++ b/tools/server/server-common.h @@ -6,8 +6,7 @@ #include "chat.h" #include "mtmd.h" -#define JSON_ASSERT GGML_ASSERT -#include +#include "json.h" #include #include @@ -19,7 +18,7 @@ #include #include -using json = nlohmann::ordered_json; +using json = common_json; #define SLT_DBG(slot, fmt, ...) LOG_DBG("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__) #define SLT_TRC(slot, fmt, ...) LOG_TRC("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__) @@ -42,9 +41,9 @@ static T json_value(const json & body, const std::string & key, const T & defaul // Fallback null to default value if (body.contains(key) && !body.at(key).is_null()) { try { - return body.at(key); - } catch (NLOHMANN_JSON_NAMESPACE::detail::type_error const & err) { - LOG_WRN("Wrong type supplied for parameter '%s'. Expected '%s', using default value: %s\n", key.c_str(), json(default_value).type_name(), err.what()); + return body.at(key).get(); + } catch (const common_json_error & err) { + LOG_WRN("Wrong type supplied for parameter '%s', using default value: %s\n", key.c_str(), err.what()); return default_value; } } else { diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp index 1293c8640267..d7a6b887d868 100644 --- a/tools/server/server-context.cpp +++ b/tools/server/server-context.cpp @@ -35,8 +35,6 @@ #include #endif -using json = nlohmann::ordered_json; - constexpr int HTTP_POLLING_SECONDS = 1; static common_speculative_output_limits server_output_limits(const common_params & params) { @@ -657,14 +655,14 @@ struct server_slot { res["n_prompt_tokens_processed"] = stats.n_prompt_processed; res["n_prompt_tokens_cache"] = stats.n_prompt_cached; res["params"] = ptask->params.to_json(only_metrics); - res["next_token"] = { + res["next_token"] = json::array({ { {"has_next_token", has_next_token}, {"has_new_line", has_new_line}, {"n_remain", n_remaining()}, {"n_decoded", stats.n_gen}, } - }; + }); if (!only_metrics) { res["prompt"] = ptask->tokens.detokenize(ctx_tgt, true); @@ -4220,7 +4218,8 @@ std::unique_ptr server_routes::handle_completions_impl( // tasks.reserve(inputs.size()); // TODO: this is inaccurate due to child tasks // message delimiters for checkpointing - auto delimiters = common_chat_msg_delimiters_parse(json_value(data, "message_delimiters", json::array())); + json delims = json_value(data, "message_delimiters", json::array()); + auto delimiters = common_chat_msg_delimiters_parse(delims); delimiters.tokenize(ctx_server.vocab); for (size_t i = 0; i < inputs.size(); i++) { @@ -4483,8 +4482,8 @@ static json get_res_model_info(const server_context_meta & meta) { static json get_res_models(const server_context_meta & meta) { // note: do NOT use ctx_server here, otherwise it's not possible to use this during sleep - return { - {"models", { + return json{ + {"models", json::array({ { {"name", meta.model_name}, {"model", meta.model_name}, @@ -4493,23 +4492,23 @@ static json get_res_models(const server_context_meta & meta) { {"digest", ""}, // dummy value, llama.cpp does not support managing model file's hash {"type", "model"}, {"description", ""}, - {"tags", {""}}, - {"capabilities", meta.has_mtmd ? json({"completion","multimodal"}) : json({"completion"})}, + {"tags", json::array({""})}, + {"capabilities", meta.has_mtmd ? json::array({"completion","multimodal"}) : json::array({"completion"})}, {"parameters", ""}, {"details", { {"parent_model", ""}, {"format", "gguf"}, {"family", ""}, - {"families", {""}}, + {"families", json::array({""})}, {"parameter_size", ""}, {"quantization_level", ""} }} } - }}, + })}, {"object", "list"}, - {"data", { + {"data", json::array({ get_res_model_info(meta), - }} + })} }; } @@ -5045,7 +5044,7 @@ void server_routes::init_routes() { std::string content; if (body.count("tokens") != 0) { - const llama_tokens tokens = body.at("tokens"); + const llama_tokens tokens = body.at("tokens").get(); content = tokens_to_str(ctx_server.vocab, tokens); } @@ -5352,7 +5351,7 @@ std::unique_ptr server_routes::handle_embeddings_impl(cons int embd_normalize = params.embd_normalize; if (body.count("embd_normalize") != 0) { - embd_normalize = body.at("embd_normalize"); + embd_normalize = body.at("embd_normalize").get(); if (meta->pooling_type == LLAMA_POOLING_TYPE_NONE) { SRV_DBG("embd_normalize is not supported by pooling type %d, ignoring it\n", meta->pooling_type); } diff --git a/tools/server/server-context.h b/tools/server/server-context.h index 764df0e08524..5d464b8e8cb7 100644 --- a/tools/server/server-context.h +++ b/tools/server/server-context.h @@ -4,7 +4,7 @@ #include "server-task.h" #include "server-queue.h" -#include +#include "json.h" #include #include diff --git a/tools/server/server-models.cpp b/tools/server/server-models.cpp index d60545194188..db0fac99527b 100644 --- a/tools/server/server-models.cpp +++ b/tools/server/server-models.cpp @@ -2462,7 +2462,7 @@ server_http_proxy::server_http_proxy( bool has_files = !files.empty(); if (has_files) { - json form_fields = json::parse(body, nullptr, false); + json form_fields = json::parse_no_throw(body); if (!form_fields.is_discarded()) { auto boundary = generate_multipart_boundary(); effective_body = build_multipart_body(form_fields, files, boundary); diff --git a/tools/server/server-schema.cpp b/tools/server/server-schema.cpp index 5d7fa6ae6ec3..64b9251295ce 100644 --- a/tools/server/server-schema.cpp +++ b/tools/server/server-schema.cpp @@ -503,7 +503,7 @@ std::vector> make_llama_cmpl_schema(const common_params & ->set_handler([&](field_eval_context & ctx, const json & data) { const auto & samplers = data.at("samplers"); if (samplers.is_array()) { - ctx.params.sampling.samplers = common_sampler_types_from_names(samplers); + ctx.params.sampling.samplers = common_sampler_types_from_names(samplers.get>()); } else if (samplers.is_string()) { ctx.params.sampling.samplers = common_sampler_types_from_chars(samplers.get()); } @@ -580,8 +580,7 @@ static void handle_with_catch(const char * name, std::function func) { // treat a null value as absent so clients can send null to request the server default static bool has_value(const json & data, const char * n) { - auto it = data.find(n); - return it != data.end() && !it->is_null(); + return data.contains(n) && !data.at(n).is_null(); } template diff --git a/tools/server/server-task.cpp b/tools/server/server-task.cpp index 258cdcf8fb86..0d3beb313cea 100644 --- a/tools/server/server-task.cpp +++ b/tools/server/server-task.cpp @@ -12,8 +12,6 @@ #include -using json = nlohmann::ordered_json; - // // task_params // @@ -304,7 +302,7 @@ json completion_token_output::probs_vector_to_json(const std::vector::lowest() : std::log(x); } @@ -407,7 +405,7 @@ json server_task_result_cmpl_final::to_json_oaicompat() { res["__verbose"] = to_json_non_oaicompat(); } if (stats.is_set()) { - res.push_back({"timings", stats.to_json()}); + res["timings"] = stats.to_json(); } return res; @@ -455,7 +453,7 @@ json server_task_result_cmpl_final::to_json_oaicompat_chat() { res["__verbose"] = to_json_non_oaicompat(); } if (stats.is_set()) { - res.push_back({"timings", stats.to_json()}); + res["timings"] = stats.to_json(); } return res; @@ -516,7 +514,7 @@ json server_task_result_cmpl_final::to_json_oaicompat_chat_stream() { } if (stats.is_set()) { - deltas.back().push_back({"timings", stats.to_json()}); + deltas.back()["timings"] = stats.to_json(); } // extra fields for debugging purposes @@ -709,7 +707,7 @@ json server_task_result_cmpl_final::to_json_oaicompat_resp_stream() { }); if (stats.is_set()) { - server_sent_events.back().at("data").push_back({"timings", stats.to_json()}); + server_sent_events.back().at("data")["timings"] = stats.to_json(); } return server_sent_events; @@ -1061,10 +1059,10 @@ json server_task_result_cmpl_partial::to_json_non_oaicompat() { }; // populate the timings object when needed (usually for the last response or with timings_per_token enabled) if (stats.is_set()) { - res.push_back({"timings", stats.to_json()}); + res["timings"] = stats.to_json(); } if (is_progress) { - res.push_back({"prompt_progress", progress.to_json()}); + res["prompt_progress"] = progress.to_json(); } if (!prob_output.probs.empty()) { res["completion_probabilities"] = completion_token_output::probs_vector_to_json({prob_output}, post_sampling_probs); @@ -1101,10 +1099,10 @@ json server_task_result_cmpl_partial::to_json_oaicompat() { res["__verbose"] = to_json_non_oaicompat(); } if (stats.is_set()) { - res.push_back({"timings", stats.to_json()}); + res["timings"] = stats.to_json(); } if (is_progress) { - res.push_back({"prompt_progress", progress.to_json()}); + res["prompt_progress"] = progress.to_json(); } return res; @@ -1155,10 +1153,10 @@ json server_task_result_cmpl_partial::to_json_oaicompat_chat() { } if (stats.is_set()) { - last_json.push_back({"timings", stats.to_json()}); + last_json["timings"] = stats.to_json(); } if (is_progress) { - last_json.push_back({"prompt_progress", progress.to_json()}); + last_json["prompt_progress"] = progress.to_json(); } } @@ -1305,10 +1303,10 @@ json server_task_result_cmpl_partial::to_json_oaicompat_resp() { if (!events.empty()) { json & data = events.back().at("data"); if (stats.is_set()) { - data.push_back({"timings", stats.to_json()}); + data["timings"] = stats.to_json(); } if (is_progress) { - data.push_back({"prompt_progress", progress.to_json()}); + data["prompt_progress"] = progress.to_json(); } } diff --git a/tools/server/server-task.h b/tools/server/server-task.h index 25ff01512248..9c99143f8e19 100644 --- a/tools/server/server-task.h +++ b/tools/server/server-task.h @@ -11,7 +11,6 @@ // TODO: prevent including the whole server-common.h as we only use server_tokens #include "server-common.h" -using json = nlohmann::ordered_json; enum server_task_type { SERVER_TASK_TYPE_COMPLETION, diff --git a/tools/server/server-tools.cpp b/tools/server/server-tools.cpp index b5c5c078ae33..12e9dbb8cfa9 100644 --- a/tools/server/server-tools.cpp +++ b/tools/server/server-tools.cpp @@ -2156,7 +2156,7 @@ void server_tools::setup(const std::vector & enabled_tools, res->status = 200; res->data = safe_json_to_str(result); } - } catch (const json::exception & e) { + } catch (const common_json_error & e) { res->status = 400; res->data = safe_json_to_str(format_error_response(e.what(), ERROR_TYPE_INVALID_REQUEST)); } catch (const std::invalid_argument & e) {