#include "simdjson.h" #include "test_builder.h" #include #include #include #include #include #include #include using namespace simdjson; namespace builder_tests { #if SIMDJSON_STATIC_REFLECTION // Custom type for testing tag_invoke with extract_into struct Price { double amount; std::string currency; // Custom deserializer that applies currency conversion friend error_code tag_invoke(deserialize_tag, ondemand::value& val, Price& price) noexcept { ondemand::object obj; auto error = val.get_object().get(obj); if (error) return error; // Get the raw amount error = obj["amount"].get(price.amount); if (error) return error; // Get the currency std::string_view currency_sv; error = obj["currency"].get(currency_sv); if (error) return error; price.currency = std::string(currency_sv); // Custom logic: Convert EUR to USD for consistency if (price.currency == "EUR") { price.amount = price.amount * 1.1; // Simplified conversion price.currency = "USD"; } return SUCCESS; } }; struct Product { std::string name; Price price; // Custom deserializable type int stock; }; // Another custom type for testing struct Dimensions { double value; std::string unit; // Custom deserializer that converts to metric friend error_code tag_invoke(deserialize_tag, ondemand::value& val, Dimensions& dim) noexcept { ondemand::object obj; auto error = val.get_object().get(obj); if (error) return error; error = obj["value"].get(dim.value); if (error) return error; std::string_view unit_sv; error = obj["unit"].get(unit_sv); if (error) return error; dim.unit = std::string(unit_sv); // Convert inches to cm if (dim.unit == "inches") { dim.value = dim.value * 2.54; dim.unit = "cm"; } return SUCCESS; } }; struct Package { std::string id; Dimensions weight; Dimensions length; }; #endif bool test_primitive_types() { TEST_START(); #if SIMDJSON_STATIC_REFLECTION struct PrimitiveTypes { bool bool_val; char char_val; int int_val; double double_val; float float_val; }; PrimitiveTypes test{true, 'X', 42, 3.14159, 2.71f}; auto result = builder::to_json_string(test); ASSERT_SUCCESS(result); std::string json = result.value(); ASSERT_TRUE(json.find("\"bool_val\":true") != std::string::npos); ASSERT_TRUE(json.find("\"char_val\":\"X\"") != std::string::npos); ASSERT_TRUE(json.find("\"int_val\":42") != std::string::npos); ASSERT_TRUE(json.find("\"double_val\":3.14159") != std::string::npos); // Test round-trip ondemand::parser parser; auto doc_result = parser.iterate(pad(json)); ASSERT_SUCCESS(doc_result); auto get_result = doc_result.value().get(); ASSERT_SUCCESS(get_result); PrimitiveTypes deserialized = std::move(get_result.value()); ASSERT_EQUAL(deserialized.bool_val, test.bool_val); ASSERT_EQUAL(deserialized.char_val, test.char_val); ASSERT_EQUAL(deserialized.int_val, test.int_val); ASSERT_EQUAL(deserialized.double_val, test.double_val); #endif TEST_SUCCEED(); } bool test_string_types() { TEST_START(); #if SIMDJSON_STATIC_REFLECTION struct StringTypes { std::string string_val; std::string_view string_view_val; }; StringTypes test{"hello world", "test_view"}; auto result = builder::to_json_string(test); ASSERT_SUCCESS(result); std::string json = result.value(); ASSERT_TRUE(json.find("\"string_val\":\"hello world\"") != std::string::npos); ASSERT_TRUE(json.find("\"string_view_val\":\"test_view\"") != std::string::npos); // Test round-trip ondemand::parser parser; auto doc_result = parser.iterate(pad(json)); ASSERT_SUCCESS(doc_result); auto get_result = doc_result.value().get(); ASSERT_SUCCESS(get_result); StringTypes deserialized = std::move(get_result.value()); ASSERT_EQUAL(deserialized.string_val, test.string_val); #endif TEST_SUCCEED(); } bool test_optional_types() { TEST_START(); #if SIMDJSON_STATIC_REFLECTION struct OptionalTypes { std::optional opt_int_with_value; std::optional opt_string_with_value; std::optional opt_int_null; std::optional opt_string_null; }; OptionalTypes test; test.opt_int_with_value = 42; test.opt_string_with_value = "optional_test"; test.opt_int_null = std::nullopt; test.opt_string_null = std::nullopt; auto result = builder::to_json_string(test); ASSERT_SUCCESS(result); std::string json = result.value(); ASSERT_TRUE(json.find("\"opt_int_with_value\":42") != std::string::npos); ASSERT_TRUE(json.find("\"opt_string_with_value\":\"optional_test\"") != std::string::npos); ASSERT_TRUE(json.find("\"opt_int_null\":null") != std::string::npos); ASSERT_TRUE(json.find("\"opt_string_null\":null") != std::string::npos); // Test round-trip ondemand::parser parser; auto doc_result = parser.iterate(pad(json)); ASSERT_SUCCESS(doc_result); auto get_result = doc_result.value().get(); ASSERT_SUCCESS(get_result); OptionalTypes deserialized = std::move(get_result.value()); ASSERT_TRUE(deserialized.opt_int_with_value.has_value()); ASSERT_EQUAL(*deserialized.opt_int_with_value, 42); ASSERT_TRUE(deserialized.opt_string_with_value.has_value()); ASSERT_EQUAL(*deserialized.opt_string_with_value, "optional_test"); ASSERT_FALSE(deserialized.opt_int_null.has_value()); ASSERT_FALSE(deserialized.opt_string_null.has_value()); #endif TEST_SUCCEED(); } bool test_smart_pointer_types() { TEST_START(); #if SIMDJSON_STATIC_REFLECTION struct SmartPointerTypes { std::unique_ptr unique_int_with_value; std::shared_ptr shared_string_with_value; std::unique_ptr unique_bool_with_value; std::unique_ptr unique_int_null; std::shared_ptr shared_string_null; }; SmartPointerTypes test; test.unique_int_with_value = std::make_unique(123); test.shared_string_with_value = std::make_shared("shared_test"); test.unique_bool_with_value = std::make_unique(true); test.unique_int_null = nullptr; test.shared_string_null = nullptr; auto result = builder::to_json_string(test); ASSERT_SUCCESS(result); std::string json = result.value(); ASSERT_TRUE(json.find("\"unique_int_with_value\":123") != std::string::npos); ASSERT_TRUE(json.find("\"shared_string_with_value\":\"shared_test\"") != std::string::npos); ASSERT_TRUE(json.find("\"unique_bool_with_value\":true") != std::string::npos); ASSERT_TRUE(json.find("\"unique_int_null\":null") != std::string::npos); ASSERT_TRUE(json.find("\"shared_string_null\":null") != std::string::npos); // Test round-trip ondemand::parser parser; auto doc_result = parser.iterate(pad(json)); ASSERT_SUCCESS(doc_result); auto get_result = doc_result.value().get(); ASSERT_SUCCESS(get_result); SmartPointerTypes deserialized = std::move(get_result.value()); ASSERT_TRUE(deserialized.unique_int_with_value != nullptr); ASSERT_EQUAL(*deserialized.unique_int_with_value, 123); ASSERT_TRUE(deserialized.shared_string_with_value != nullptr); ASSERT_EQUAL(*deserialized.shared_string_with_value, "shared_test"); ASSERT_TRUE(deserialized.unique_bool_with_value != nullptr); ASSERT_EQUAL(*deserialized.unique_bool_with_value, true); ASSERT_TRUE(deserialized.unique_int_null == nullptr); ASSERT_TRUE(deserialized.shared_string_null == nullptr); #endif TEST_SUCCEED(); } bool test_container_types() { TEST_START(); #if SIMDJSON_STATIC_REFLECTION struct ContainerTypes { std::vector int_vector; std::set string_set; std::map string_map; }; ContainerTypes test; test.int_vector = {1, 2, 3, 4, 5}; test.string_set = {"apple", "banana", "cherry"}; test.string_map = {{"key1", 10}, {"key2", 20}, {"key3", 30}}; auto result = builder::to_json_string(test); ASSERT_SUCCESS(result); std::string json = result.value(); ASSERT_TRUE(json.find("\"int_vector\":[1,2,3,4,5]") != std::string::npos); ASSERT_TRUE(json.find("\"string_set\":[") != std::string::npos); ASSERT_TRUE(json.find("\"string_map\":{") != std::string::npos); // Test round-trip ondemand::parser parser; auto doc_result = parser.iterate(pad(json)); ASSERT_SUCCESS(doc_result); auto get_result = doc_result.value().get(); ASSERT_SUCCESS(get_result); ContainerTypes deserialized = std::move(get_result.value()); ASSERT_EQUAL(deserialized.int_vector.size(), 5); ASSERT_EQUAL(deserialized.string_set.size(), 3); ASSERT_EQUAL(deserialized.string_map.size(), 3); ASSERT_EQUAL(deserialized.int_vector[0], 1); ASSERT_EQUAL(deserialized.int_vector[4], 5); #endif TEST_SUCCEED(); } bool test_extract_into() { TEST_START(); #if SIMDJSON_STATIC_REFLECTION struct Car { std::string make; std::string model; int year; double price; std::optional color; }; ondemand::parser parser; // Test 1: Extract only specific fields { std::string json_str = R"({ "make": "Toyota", "model": "Camry", "year": 2024, "price": 28999.99, "color": "Blue", "engine": "V6", "transmission": "Automatic" })"; auto padded = pad(json_str); Car car{}; auto doc = parser.iterate(padded); ASSERT_SUCCESS(doc); ondemand::object obj; auto obj_result = doc.get_object().get(obj); ASSERT_SUCCESS(obj_result); // Extract only 'make' and 'model' auto error = obj.extract_into<"make", "model">(car); ASSERT_SUCCESS(error); ASSERT_EQUAL(car.make, "Toyota"); ASSERT_EQUAL(car.model, "Camry"); ASSERT_EQUAL(car.year, 0); // Not extracted ASSERT_EQUAL(car.price, 0); // Not extracted } // Test 2: Extract with optional field { std::string json_str = R"({ "make": "Honda", "model": "Accord", "year": 2023, "price": 26999.99, "color": "Red" })"; auto padded = pad(json_str); Car car{}; auto doc = parser.iterate(padded); ASSERT_SUCCESS(doc); ondemand::object obj; auto obj_result = doc.get_object().get(obj); ASSERT_SUCCESS(obj_result); // Extract including optional 'color' auto error = obj.extract_into<"make", "model", "color">(car); ASSERT_SUCCESS(error); ASSERT_EQUAL(car.make, "Honda"); ASSERT_EQUAL(car.model, "Accord"); ASSERT_TRUE(car.color.has_value()); ASSERT_EQUAL(*car.color, "Red"); } // Test 3: Extract with missing optional field { std::string json_str = R"({ "make": "Ford", "model": "F-150", "year": 2024, "price": 35999.99 })"; auto padded = pad(json_str); Car car{}; auto doc = parser.iterate(padded); ASSERT_SUCCESS(doc); ondemand::object obj; auto obj_result = doc.get_object().get(obj); ASSERT_SUCCESS(obj_result); // Try to extract including optional 'color' which doesn't exist auto error = obj.extract_into<"make", "model", "color">(car); ASSERT_SUCCESS(error); ASSERT_EQUAL(car.make, "Ford"); ASSERT_EQUAL(car.model, "F-150"); ASSERT_FALSE(car.color.has_value()); // Should be empty } // Test 4: Extract with custom deserializable type using tag_invoke { // Test using the Price struct defined at namespace level std::string json_str = R"({ "name": "Laptop", "price": { "amount": 1000, "currency": "EUR" }, "stock": 15, "description": "High-end gaming laptop" })"; auto padded = pad(json_str); Product product{}; auto doc = parser.iterate(padded); ASSERT_SUCCESS(doc); ondemand::object obj; auto obj_result = doc.get_object().get(obj); ASSERT_SUCCESS(obj_result); // Extract including price field with custom deserializer auto error = obj.extract_into<"name", "price">(product); ASSERT_SUCCESS(error); ASSERT_EQUAL(product.name, "Laptop"); // Verify custom deserializer was invoked: EUR should be converted to USD ASSERT_EQUAL(product.price.currency, "USD"); // Should be converted from EUR // Verify custom deserializer was invoked: amount should be 1100 (1000 * 1.1) ASSERT_EQUAL(product.price.amount, 1100); // Should be exactly 1100 after conversion ASSERT_EQUAL(product.stock, 0); // Not extracted } // Test 5: Extract with nested custom deserializable types { // Test using the Dimensions struct defined at namespace level std::string json_str = R"({ "id": "PKG123", "weight": { "value": 10, "unit": "pounds" }, "length": { "value": 12, "unit": "inches" }, "fragile": true })"; auto padded = pad(json_str); Package pkg{}; auto doc = parser.iterate(padded); ASSERT_SUCCESS(doc); ondemand::object obj; auto obj_result = doc.get_object().get(obj); ASSERT_SUCCESS(obj_result); // Extract only length (with custom deserializer), skip weight auto error = obj.extract_into<"id", "length">(pkg); ASSERT_SUCCESS(error); ASSERT_EQUAL(pkg.id, "PKG123"); // Verify custom deserializer was invoked: inches should be converted to cm ASSERT_EQUAL(pkg.length.unit, "cm"); // Should be converted from inches // Verify exact conversion: 12 inches * 2.54 = 30.48 cm ASSERT_EQUAL(pkg.length.value, 30.48); // Should be exactly 30.48 after conversion ASSERT_EQUAL(pkg.weight.unit, ""); // Weight not extracted ASSERT_EQUAL(pkg.weight.value, 0); // Weight value should remain at default } #endif TEST_SUCCEED(); } bool run() { return test_primitive_types() && test_string_types() && test_optional_types() && test_smart_pointer_types() && test_container_types() && test_extract_into(); } } // namespace builder_tests int main(int argc, char *argv[]) { return test_main(argc, argv, builder_tests::run); }