common: add json.h abstraction (#27511)

* add common/json

* migrate common

* adapt jinja

* migrate server

* big wip

* migrate tests

* wip

* revert some excessive changes

* wip

* wip 2

* revert redundant changes

* fix server crash

* various fixes

* fix ci

* harden a bit

* clean up

* rm json-shim

* add some comments

* rm redundant decl
This commit is contained in:
Xuan-Son Nguyen
2026-08-22 16:28:28 +02:00
committed by GitHub
parent 2fb989b9e7
commit d9f918d2d0
45 changed files with 987 additions and 217 deletions
+2 -2
View File
@@ -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);
}
+4 -4
View File
@@ -1,7 +1,7 @@
#pragma once
// Common includes for all test files
#include <nlohmann/json.hpp>
#include "json.h"
#include <string>
#include <vector>
@@ -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
+17 -17
View File
@@ -11,9 +11,9 @@
#include <regex>
#include <string>
#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 = */ "<think>",
/* .input = */
@@ -406,7 +406,7 @@ static void test_example_qwen3_coder(testing & t) {
std::set<std::string> required_properties;
if (function.contains("required")) {
function.at("required").get_to(required_properties);
required_properties = function.at("required").get<std::set<std::string>>();
}
std::vector<common_peg_parser> 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<std::tuple<std::string, std::string, nlohmann::json>> tool_calls = {
{ "call_0", "plan_trip", nlohmann::json::parse(R"({
std::vector<std::tuple<std::string, std::string, common_json>> 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" }) },
} },
} }
};
+4 -4
View File
@@ -7,7 +7,7 @@
#include <fstream>
#include <filesystem>
#include <nlohmann/json.hpp>
#include "json.h"
#undef NDEBUG
#include <cassert>
@@ -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<std::string>();
}
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);
+2 -2
View File
@@ -19,12 +19,12 @@
#include <fstream>
#include <functional>
#include <iostream>
#include <nlohmann/json.hpp>
#include "json.h"
#include <set>
#include <stdexcept>
#include <string>
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 << "; ";
+2 -2
View File
@@ -7,13 +7,13 @@
#include "../src/unicode.h"
#include "../src/llama-grammar.h"
#include <nlohmann/json.hpp>
#include "json.h"
#include <cassert>
#include <string>
#include <vector>
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);
+4 -4
View File
@@ -3,7 +3,7 @@
#include <random>
#include <cstdlib>
#include <nlohmann/json.hpp>
#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"
);
+4 -4
View File
@@ -6,7 +6,7 @@
#include "../src/llama-grammar.h"
#include <nlohmann/json.hpp>
#include "json.h"
#include <cassert>
#include <fstream>
@@ -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());
+3 -3
View File
@@ -9,7 +9,7 @@
#include "http.h"
#include "log.h"
#include <nlohmann/json.hpp>
#include "json.h"
#include <algorithm>
#include <cstdio>
@@ -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];