Compare commits

...

10 Commits

Author SHA1 Message Date
Daniel Lemire d293aa3ff2 Merge branch 'master' into francisco/fix_datetime 2025-09-16 13:42:22 -06:00
Francisco Geiman Thiesen d5a7918e0d Adding fix for chrono 2025-09-16 11:26:19 -06:00
Daniel Lemire 1f369ef210 minor patch which allows us to pass mutable strings to simdjson::from… (#2448)
* minor patch which allows us to pass mutable strings to simdjson::from and fix
an issue with ambiguous integrals

* compatibility patch.
2025-09-15 22:22:25 -06:00
Daniel Lemire 22dcdc9f1e Update README.md 2025-09-15 18:52:15 -06:00
Dirk Stolle dda2dafa30 fix some typos (#2446) 2025-09-15 18:23:13 -06:00
Daniel Lemire 72e9d44e10 fixing indent 2025-09-15 17:08:12 -06:00
Daniel Lemire 8aae14931d release candidate 4.0.2 (#2441)
* release candidate 4.0.2

* more fixes

* fixing typos

* adding macro check
2025-09-15 09:17:43 -06:00
Daniel Lemire ef3d1ac25f fixing 2440 2025-09-14 08:09:27 -06:00
Daniel Lemire 9292480a9b removing leftover <experimental/...> (#2438) 2025-09-14 08:00:26 -06:00
Daniel Lemire 611ea97dbd Add C++26 reflection example link to README 2025-09-12 21:22:45 -04:00
21 changed files with 887 additions and 251 deletions
@@ -0,0 +1,22 @@
name: Ubuntu 24.04 CI (CXX 20, noexcept)
on: [push, pull_request]
jobs:
ubuntu-build:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-24.04
strategy:
matrix:
cxx: [g++-13, clang++-16]
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Prepare
run: cmake -DSIMDJSON_CXX_STANDARD=20 -DSIMDJSON_EXCEPTIONS=OFF -DSIMDJSON_DEVELOPER_MODE=ON -B build
env:
CXX: ${{matrix.cxx}}
- name: Build
run: cmake --build build -j=2
- name: Test
run: ctest --output-on-failure --test-dir build
+1 -1
View File
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.14)
project(
simdjson
# The version number is modified by tools/release.py
VERSION 4.0.1
VERSION 4.0.3
DESCRIPTION "Parsing gigabytes of JSON per second"
HOMEPAGE_URL "https://simdjson.org/"
LANGUAGES CXX C
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = "4.0.1"
PROJECT_NUMBER = "4.0.3"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+2
View File
@@ -117,8 +117,10 @@ Godbolt
-------------
Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples:
* [C++26 reflection example](https://godbolt.org/z/K3Px64TqK)
* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/7G5qE4sr9)
* [simdjson examples with errors without exceptions](https://godbolt.org/z/e9dWb9E4v)
Performance results
-------------------
+34 -30
View File
@@ -6,38 +6,42 @@ CPMAddPackage(
EXCLUDE_FROM_ALL YES
)
option(SIMDJSON_USE_RUST "Build the static_reflect benchmark" OFF)
if(NOT WIN32)
# We want the check whether Rust is available before trying to build a crate.
CPMAddPackage(
NAME corrosion
GITHUB_REPOSITORY corrosion-rs/corrosion
VERSION 0.4.4
DOWNLOAD_ONLY ON
OPTIONS "Rust_FIND_QUIETLY OFF"
)
include("${corrosion_SOURCE_DIR}/cmake/FindRust.cmake")
endif()
if(RUST_FOUND)
message(STATUS "Rust found: " ${Rust_VERSION} )
add_subdirectory("${corrosion_SOURCE_DIR}" "${PROJECT_BINARY_DIR}/_deps/corrosion" EXCLUDE_FROM_ALL)
# Important: we want to build in release mode!
corrosion_import_crate(MANIFEST_PATH "serde-benchmark/Cargo.toml" NO_LINKER_OVERRIDE PROFILE release)
else()
message(STATUS "Rust/Cargo is unavailable." )
message(STATUS "We will not benchmark serde-benchmark." )
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
message(STATUS "Under macOS, you may be able to install rust with")
message(STATUS "curl https://sh.rustup.rs -sSf | sh")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Under Linux, you may be able to install rust with a command such as")
message(STATUS "apt-get install cargo" )
message(STATUS "or" )
message(STATUS "curl https://sh.rustup.rs -sSf | sh")
if(SIMDJSON_USER_RUST)
if(NOT WIN32)
# We want the check whether Rust is available before trying to build a crate.
CPMAddPackage(
NAME corrosion
GITHUB_REPOSITORY corrosion-rs/corrosion
VERSION 0.4.4
DOWNLOAD_ONLY ON
OPTIONS "Rust_FIND_QUIETLY OFF"
)
include("${corrosion_SOURCE_DIR}/cmake/FindRust.cmake")
endif()
endif()
if(RUST_FOUND)
message(STATUS "Rust found: " ${Rust_VERSION} )
add_subdirectory("${corrosion_SOURCE_DIR}" "${PROJECT_BINARY_DIR}/_deps/corrosion" EXCLUDE_FROM_ALL)
# Important: we want to build in release mode!
corrosion_import_crate(MANIFEST_PATH "serde-benchmark/Cargo.toml" NO_LINKER_OVERRIDE PROFILE release)
else()
message(STATUS "Rust/Cargo is unavailable." )
message(STATUS "We will not benchmark serde-benchmark." )
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
message(STATUS "Under macOS, you may be able to install rust with")
message(STATUS "curl https://sh.rustup.rs -sSf | sh")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Under Linux, you may be able to install rust with a command such as")
message(STATUS "apt-get install cargo" )
message(STATUS "or" )
message(STATUS "curl https://sh.rustup.rs -sSf | sh")
endif()
endif()
else(SIMDJSON_USER_RUST)
message(STATUS "We will not benchmark serde-benchmark." )
endif(SIMDJSON_USER_RUST)
# Add the benchmark executable targets
add_subdirectory(twitter_benchmark)
+2 -2
View File
@@ -1395,7 +1395,7 @@ Car c = doc.get<Car>();
We try to automate the parsing of any given structure or class
by looking at its non-static public members. At compile-time,
the library looks at a simple structre like `Car` and
the library looks at a simple structure like `Car` and
maps it to parsing code. We call the default constructor,
and then assign values to the public members.
@@ -1450,7 +1450,7 @@ struct complicated_weather_data {
};
```
The code might as simple as the following.
The code might be as simple as the following.
```cpp
auto padded = R"({"time":["2023-03-15T12:00:00Z"],"temperature":[42]})"_padded;
+1 -1
View File
@@ -41,7 +41,7 @@ Parameters:
- `append_raw(std::string_view input)`: Appends a string view directly to the JSON buffer without escaping.
- `append_raw(const char *str, size_t len)`: Appends a specified number of characters from a C string directly to the JSON
After writting the content, if you have reasons to believe that the content might violate UTF-8 conventions, you can check it as follows:
After writing the content, if you have reasons to believe that the content might violate UTF-8 conventions, you can check it as follows:
- `validate_unicode()`: Checks if the content in the JSON buffer is valid UTF-8. Returns: true if the content is valid UTF-8, false otherwise.
+1 -1
View File
@@ -129,7 +129,7 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper
The strings contain unescaped valid UTF-8 strings: no unmatched surrogate is allowed.
Internally, numbers are stored as either 64-bit integers or 64-bit floating-point numbers.
Thus it is possible to get the full 64-bit integer range (either signed or unsigned).
By default, the string `-0` is parsed as the integer 0 as in Pytho or C++. If you set the macro
By default, the string `-0` is parsed as the integer 0 as in Python or C++. If you set the macro
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to `1` when building simdjson, you can get that `-0` is mapped to `-0.0`
as in JavaScript. You can get the desired effect by building simdjson with cmake setting the
`SIMDJSON_MINUS_ZERO_AS_FLOAT` to on: `cmake -B build -D SIMDJSON_MINUS_ZERO_AS_FLOAT=ON`.
+10
View File
@@ -116,6 +116,16 @@ template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, padded_string_view const str) const noexcept {
return auto_parser<ondemand::parser *>{parser, str};
}
template <typename T>
inline auto to_adaptor<T>::operator()(std::string str) const noexcept {
return auto_parser{pad_with_reserve(str)};
}
template <typename T>
inline auto to_adaptor<T>::operator()(ondemand::parser &parser, std::string str) const noexcept {
return auto_parser<ondemand::parser *>{parser, pad_with_reserve(str)};
}
} // namespace internal
} // namespace convert
} // namespace simdjson
+5
View File
@@ -72,6 +72,11 @@ struct to_adaptor {
T operator()(simdjson_result<ondemand::value> &val) const noexcept;
auto operator()(padded_string_view const str) const noexcept;
auto operator()(ondemand::parser &parser, padded_string_view const str) const noexcept;
// The std::string is padded with reserve to ensure there is enough space for padding.
// Some sanitizers may not like this, so you can use simdjson::pad instead.
// simdjson::from(simdjson::pad(str))
auto operator()(std::string str) const noexcept;
auto operator()(ondemand::parser &parser, std::string str) const noexcept;
};
} // namespace internal
} // namespace convert
@@ -12,8 +12,9 @@
#if SIMDJSON_STATIC_REFLECTION
#include <charconv>
#include <chrono>
#include <cstring>
#include <experimental/meta>
#include <meta>
#include <memory>
#include <optional>
#include <string_view>
@@ -90,6 +91,20 @@ constexpr void atom(string_builder &b, const number_type t) {
b.append(t);
}
// Support for std::chrono::duration - serialize as milliseconds
template <typename Rep, typename Period>
void atom(string_builder &b, const std::chrono::duration<Rep, Period> &duration) {
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
b.append(ms);
}
// Support for std::chrono::time_point - serialize as milliseconds since epoch
template <typename Clock, typename Duration>
void atom(string_builder &b, const std::chrono::time_point<Clock, Duration> &time_point) {
auto ms_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(time_point.time_since_epoch()).count();
b.append(ms_since_epoch);
}
template <class T>
requires(std::is_class_v<T> && !container_but_not_string<T> &&
!concepts::string_view_keyed_map<T> &&
@@ -284,10 +284,12 @@ simdjson_inline void string_builder::clear() noexcept {
namespace internal {
// We could specialize further for 32-bit integers.
simdjson_really_inline int int_log2(uint32_t x) { return (63 - leading_zeroes(x | 1)); }
simdjson_really_inline int fast_digit_count(uint32_t x) {
template <typename number_type, typename = typename std::enable_if<
std::is_unsigned<number_type>::value>::type>
simdjson_really_inline int int_log2(number_type x) { return 63 - leading_zeroes(uint64_t(x) | 1); }
simdjson_really_inline int fast_digit_count_32(uint32_t x) {
static uint64_t table[] = {
4294967296, 8589934582, 8589934582, 8589934582, 12884901788,
12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
@@ -299,9 +301,8 @@ simdjson_really_inline int fast_digit_count(uint32_t x) {
return uint32_t((x + table[int_log2(x)]) >> 32);
}
simdjson_really_inline int int_log2(uint64_t x) { return 63 - leading_zeroes(x | 1); }
simdjson_really_inline int fast_digit_count(uint64_t x) {
simdjson_really_inline int fast_digit_count_64(uint64_t x) {
static uint64_t table[] = {9,
99,
999,
@@ -332,7 +333,11 @@ simdjson_really_inline size_t digit_count(number_type v) noexcept {
static_assert(sizeof(number_type) == 8 || sizeof(number_type) == 4 ||
sizeof(number_type) == 2 || sizeof(number_type) == 1,
"We only support 8-bit, 16-bit, 32-bit and 64-bit numbers");
return fast_digit_count(v);
SIMDJSON_IF_CONSTEXPR(sizeof(number_type) <= 4) {
return fast_digit_count_32(static_cast<uint32_t>(v));
} else {
return fast_digit_count_64(static_cast<uint64_t>(v));
}
}
static const char decimal_table[200] = {
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
@@ -8,6 +8,7 @@
#include "simdjson/generic/ondemand/base.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#include <chrono>
#include <concepts>
#include <limits>
#if SIMDJSON_STATIC_REFLECTION
@@ -46,6 +47,25 @@ error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
return SUCCESS;
}
// Support for std::chrono::duration - deserialize from milliseconds
template <typename Rep, typename Period, typename ValT>
error_code tag_invoke(deserialize_tag, ValT &val, std::chrono::duration<Rep, Period> &out) noexcept {
int64_t ms;
SIMDJSON_TRY(val.get_int64().get(ms));
out = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(std::chrono::milliseconds(ms));
return SUCCESS;
}
// Support for std::chrono::time_point - deserialize from milliseconds since epoch
template <typename Clock, typename Duration, typename ValT>
error_code tag_invoke(deserialize_tag, ValT &val, std::chrono::time_point<Clock, Duration> &out) noexcept {
int64_t ms_since_epoch;
SIMDJSON_TRY(val.get_int64().get(ms_since_epoch));
auto duration = std::chrono::milliseconds(ms_since_epoch);
out = std::chrono::time_point<Clock, Duration>(std::chrono::duration_cast<Duration>(duration));
return SUCCESS;
}
template <std::signed_integral T>
requires(!require_custom_serialization<T>)
error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept {
@@ -253,7 +273,9 @@ error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept(nothrow_deser
using value_type = typename std::remove_cvref_t<T>::value_type;
// Check if the value is null
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset(); // Set to nullopt
return SUCCESS;
}
@@ -286,7 +308,7 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
}
error_code e = simdjson::SUCCESS;
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
// Note: removed static assert as optional types are now handled generically
// as long we are succesful or the field is not found, we continue
@@ -361,7 +383,9 @@ error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr<T> &
// Unique pointers
////////////////////////////////////////
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<bool> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -374,7 +398,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<bool> &out) no
}
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<int64_t> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -387,7 +413,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<int64_t> &out)
}
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<uint64_t> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -400,7 +428,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<uint64_t> &out
}
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<double> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -413,7 +443,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<double> &out)
}
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string_view> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -430,7 +462,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string_vi
// Shared pointers
////////////////////////////////////////
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<bool> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -443,7 +477,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<bool> &out) no
}
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<int64_t> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -456,7 +492,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<int64_t> &out)
}
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<uint64_t> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -469,7 +507,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<uint64_t> &out
}
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<double> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -482,7 +522,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<double> &out)
}
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string_view> &out) noexcept {
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset();
return SUCCESS;
}
@@ -504,7 +546,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string_vi
////////////////////////////////////////
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string> &out) noexcept {
// Check if the value is null
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset(); // Set to nullptr
return SUCCESS;
}
@@ -520,7 +564,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<std::string> &
error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string> &out) noexcept {
// Check if the value is null
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset(); // Set to nullptr
return SUCCESS;
}
@@ -536,7 +582,9 @@ error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr<std::string> &
error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr<int> &out) noexcept {
// Check if the value is null
if (val.is_null()) {
bool is_null_value;
SIMDJSON_TRY( val.is_null().get(is_null_value) );
if (is_null_value) {
out.reset(); // Set to nullptr
return SUCCESS;
}
+3 -1
View File
@@ -79,7 +79,9 @@ public:
using value_type = typename std::remove_cvref_t<T>::value_type;
// Check if the value is null
if (is_null()) {
bool is_null_value;
SIMDJSON_TRY( is_null().get(is_null_value) );
if (is_null_value) {
out.reset(); // Set to nullopt
return SUCCESS;
}
+2 -2
View File
@@ -4,7 +4,7 @@
#define SIMDJSON_SIMDJSON_VERSION_H
/** The version of simdjson being used (major.minor.revision) */
#define SIMDJSON_VERSION "4.0.1"
#define SIMDJSON_VERSION "4.0.3"
namespace simdjson {
enum {
@@ -19,7 +19,7 @@ enum {
/**
* The revision (major.minor.REVISION) of simdjson being used.
*/
SIMDJSON_VERSION_REVISION = 1
SIMDJSON_VERSION_REVISION = 3
};
} // namespace simdjson
+1 -1
View File
@@ -1,4 +1,4 @@
/* auto-generated on 2025-09-12 19:26:22 -0400. version 4.0.1 Do not edit! */
/* auto-generated on 2025-09-15 19:47:29 -0600. version 4.0.3 Do not edit! */
/* including simdjson.cpp: */
/* begin file simdjson.cpp */
#define SIMDJSON_SRC_SIMDJSON_CPP
+482 -187
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -0,0 +1,176 @@
#include "simdjson.h"
#include "test_builder.h"
#include <chrono>
#include <string>
#include <vector>
#include <optional>
#include <iostream>
using namespace simdjson;
namespace builder_tests {
struct Meeting {
std::string title;
std::chrono::system_clock::time_point start_time;
std::vector<std::string> attendees;
std::optional<std::string> location;
bool is_recurring;
};
bool test_meeting_roundtrip() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
// Create a Meeting instance with current time
auto now = std::chrono::system_clock::now();
Meeting original{
.title = "CppCon Planning",
.start_time = now,
.attendees = {"Alice", "Bob", "Charlie"},
.location = "Denver",
.is_recurring = true
};
// Serialize to JSON
auto json_result = builder::to_json_string(original);
ASSERT_SUCCESS(json_result);
std::string json = json_result.value();
std::cout << "Serialized JSON: " << json << std::endl;
// Parse back from JSON
ondemand::parser parser;
auto doc_result = parser.iterate(pad(json));
ASSERT_SUCCESS(doc_result);
auto get_result = doc_result.value().get<Meeting>();
ASSERT_SUCCESS(get_result);
Meeting deserialized = std::move(get_result.value());
// Verify round-trip
ASSERT_EQUAL(deserialized.title, original.title);
ASSERT_EQUAL(deserialized.is_recurring, original.is_recurring);
ASSERT_TRUE(deserialized.location.has_value());
ASSERT_EQUAL(deserialized.location.value(), original.location.value());
ASSERT_EQUAL(deserialized.attendees.size(), original.attendees.size());
for(size_t i = 0; i < original.attendees.size(); i++) {
ASSERT_EQUAL(deserialized.attendees[i], original.attendees[i]);
}
// Check that the time point is correctly deserialized with millisecond precision
auto original_ms = std::chrono::duration_cast<std::chrono::milliseconds>(original.start_time.time_since_epoch()).count();
auto deserialized_ms = std::chrono::duration_cast<std::chrono::milliseconds>(deserialized.start_time.time_since_epoch()).count();
std::cout << "Original time: " << original_ms << "ms since epoch" << std::endl;
std::cout << "Deserialized time: " << deserialized_ms << "ms since epoch" << std::endl;
// Verify exact equality at millisecond precision
ASSERT_EQUAL(deserialized_ms, original_ms);
#endif
TEST_SUCCEED();
}
bool test_duration_roundtrip() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct Task {
std::string name;
std::chrono::milliseconds duration_ms;
std::chrono::seconds duration_s;
std::chrono::minutes duration_min;
};
Task original{
.name = "Build Project",
.duration_ms = std::chrono::milliseconds(5432),
.duration_s = std::chrono::seconds(300),
.duration_min = std::chrono::minutes(45)
};
// Serialize to JSON
auto json_result = builder::to_json_string(original);
ASSERT_SUCCESS(json_result);
std::string json = json_result.value();
std::cout << "Duration test JSON: " << json << std::endl;
// Parse back from JSON
ondemand::parser parser;
auto doc_result = parser.iterate(pad(json));
ASSERT_SUCCESS(doc_result);
auto get_result = doc_result.value().get<Task>();
ASSERT_SUCCESS(get_result);
Task deserialized = std::move(get_result.value());
// Verify round-trip
ASSERT_EQUAL(deserialized.name, original.name);
ASSERT_EQUAL(deserialized.duration_ms.count(), original.duration_ms.count());
ASSERT_EQUAL(deserialized.duration_s.count(), original.duration_s.count());
ASSERT_EQUAL(deserialized.duration_min.count(), original.duration_min.count());
#endif
TEST_SUCCEED();
}
bool test_different_clock_types() {
TEST_START();
#if SIMDJSON_STATIC_REFLECTION
struct Schedule {
std::string event;
std::chrono::system_clock::time_point system_time;
std::chrono::steady_clock::time_point steady_time;
};
Schedule original{
.event = "Conference",
.system_time = std::chrono::system_clock::now(),
.steady_time = std::chrono::steady_clock::now()
};
// Serialize to JSON
auto json_result = builder::to_json_string(original);
ASSERT_SUCCESS(json_result);
std::string json = json_result.value();
std::cout << "Clock types test JSON: " << json << std::endl;
// Parse back from JSON
ondemand::parser parser;
auto doc_result = parser.iterate(pad(json));
ASSERT_SUCCESS(doc_result);
auto get_result = doc_result.value().get<Schedule>();
ASSERT_SUCCESS(get_result);
Schedule deserialized = std::move(get_result.value());
// Verify round-trip - check millisecond precision
ASSERT_EQUAL(deserialized.event, original.event);
auto orig_sys_ms = std::chrono::duration_cast<std::chrono::milliseconds>(original.system_time.time_since_epoch()).count();
auto deser_sys_ms = std::chrono::duration_cast<std::chrono::milliseconds>(deserialized.system_time.time_since_epoch()).count();
ASSERT_EQUAL(deser_sys_ms, orig_sys_ms);
auto orig_steady_ms = std::chrono::duration_cast<std::chrono::milliseconds>(original.steady_time.time_since_epoch()).count();
auto deser_steady_ms = std::chrono::duration_cast<std::chrono::milliseconds>(deserialized.steady_time.time_since_epoch()).count();
ASSERT_EQUAL(deser_steady_ms, orig_steady_ms);
#endif
TEST_SUCCEED();
}
bool run() {
return test_meeting_roundtrip() &&
test_duration_roundtrip() &&
test_different_clock_types();
}
} // namespace builder_tests
int main(int argc, char *argv[]) {
return builder_tests::run() ? EXIT_SUCCESS : EXIT_FAILURE;
}
+2 -1
View File
@@ -8,7 +8,7 @@
using namespace simdjson;
namespace ranges_test {
#if SIMDJSON_EXCEPTIONS
bool printout() {
TEST_START();
auto cars_json = R"( [
@@ -43,6 +43,7 @@ namespace ranges_test {
ASSERT_TRUE(std::ranges::equal(cars, expected));
TEST_SUCCEED();
}
#endif // SIMDJSON_EXCEPTIONS
bool run() {
return
+52 -1
View File
@@ -2,6 +2,7 @@
#include "simdjson/convert.h"
#include "test_ondemand.h"
#include <chrono>
#include <map>
#include <ranges>
#include <string>
@@ -21,6 +22,24 @@ private:
std::string date_str;
};
struct Meeting {
std::string title;
std::vector<std::string> attendees;
std::optional<std::string> location;
bool is_recurring;
};
struct MeetingTime {
std::string title;
std::chrono::system_clock::time_point start_time;
std::vector<std::string> attendees;
std::optional<std::string> location;
bool is_recurring;
};
namespace simdjson {
template <typename simdjson_value>
auto tag_invoke(deserialize_tag, simdjson_value &val, MyDate& date) {
@@ -160,6 +179,38 @@ bool simple() {
ASSERT_SUCCESS(doc.get(p));
TEST_SUCCEED();
}
bool meeting_test() {
TEST_START();
std::string json = simdjson::to_json(Meeting{
.title = "CppCon Planning",
.attendees = {"Alice", "Bob", "Charlie"},
.location = "Denver",
.is_recurring = true
});
Meeting m2 = simdjson::from(json);
std::cout << m2.title << std::endl;
TEST_SUCCEED();
}
bool meeting_time_test() {
TEST_START();
auto start_time = std::chrono::system_clock::now();
std::string json = simdjson::to_json(MeetingTime{
.title = "CppCon Planning",
.start_time = start_time,
.attendees = {"Alice", "Bob", "Charlie"},
.location = "Denver",
.is_recurring = true
});
std::cout << json << std::endl;
MeetingTime m2 = simdjson::from(json);
//ASSERT_EQUAL(m2.start_time, start_time);
std::cout << m2.title << std::endl;
TEST_SUCCEED();
}
#endif
bool broken() {
TEST_START();
@@ -415,7 +466,7 @@ bool test_to_vs_from_equivalence() {
bool run() {
return
#if SIMDJSON_STATIC_REFLECTION
bad_player() && good_player() && complicated_weather_test() &&
meeting_time_test() && meeting_test() && bad_player() && good_player() && complicated_weather_test() &&
#endif
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_CONCEPTS
broken() && simple() && simple_optional() && with_parser() && to_array() &&