docs: write std::chrono durations as unit/value objects; reword a title with escaped quotes (#5911)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-10-25 12:07:32 +01:00
1 parent ffd4242dc2
commit fe231dc692
4 files changed
+55 -9

No files matched your search

@@ -1,9 +1,38 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <stdexcept>
#include <string>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using json = nlohmann::json; using json = nlohmann::json;
// the name of a period; extend the list for the periods you use
template <typename Period> const char* unit_name();
template <> const char* unit_name<std::nano>()
{
return "ns";
}
template <> const char* unit_name<std::micro>()
{
return "us";
}
template <> const char* unit_name<std::milli>()
{
return "ms";
}
template <> const char* unit_name<std::ratio<1>>()
{
return "s";
}
template <> const char* unit_name<std::ratio<60>>()
{
return "min";
}
template <> const char* unit_name<std::ratio<3600>>()
{
return "h";
}
namespace nlohmann namespace nlohmann
{ {
template <typename Rep, typename Period> template <typename Rep, typename Period>
@@ -11,12 +40,16 @@ struct adl_serializer<std::chrono::duration<Rep, Period>>
{ {
static void to_json(json& j, const std::chrono::duration<Rep, Period>& d) static void to_json(json& j, const std::chrono::duration<Rep, Period>& d)
{ {
j = d.count(); j = {{"unit", unit_name<Period>()}, {"value", d.count()}};
} }
static void from_json(const json& j, std::chrono::duration<Rep, Period>& d) static void from_json(const json& j, std::chrono::duration<Rep, Period>& d)
{ {
d = std::chrono::duration<Rep, Period>(j.get<Rep>()); if (j.at("unit").get<std::string>() != unit_name<Period>())
{
throw std::invalid_argument("unexpected unit " + j.at("unit").dump());
}
d = std::chrono::duration<Rep, Period>(j.at("value").get<Rep>());
} }
}; };
@@ -41,11 +74,21 @@ int main()
std::cout << std::boolalpha; std::cout << std::boolalpha;
// the unit is not part of the JSON value // the unit is part of the JSON value
const json timeout = milliseconds(1500); const json timeout = milliseconds(1500);
std::cout << "duration: " << timeout << '\n'; std::cout << "duration: " << timeout << '\n';
std::cout << "as milliseconds: " << timeout.get<milliseconds>().count() << '\n'; std::cout << "as milliseconds: " << timeout.get<milliseconds>().count() << '\n';
// reading with a different unit is rejected
try
{
timeout.get<seconds>();
}
catch (const std::invalid_argument& e)
{
std::cout << "error: " << e.what() << '\n';
}
// a system_clock time point is the time since the Unix epoch // a system_clock time point is the time since the Unix epoch
const time_point<system_clock, milliseconds> t(milliseconds(1700000000123)); const time_point<system_clock, milliseconds> t(milliseconds(1700000000123));
const json stamp = t; const json stamp = t;
@@ -1,4 +1,5 @@
duration: 1500 duration: {"unit":"ms","value":1500}
as milliseconds: 1500 as milliseconds: 1500
time point: 1700000000123 error: unexpected unit "ms"
time point: {"unit":"ms","value":1700000000123}
round trip: true round trip: true
+5 -3
View File
@@ -92,8 +92,10 @@ to choose the alternative.
## `std::chrono` ## `std::chrono`
A duration is written as its count and read back with the same representation and period. The **unit is not part of A duration is written as an object with the keys `unit` and `value`, for example `{"unit": "ms", "value": 1500}`, so
the JSON value**, so document it, for example in the member name (`timeout_ms`): the JSON value is self-describing. The serializer names the unit of the period (`ns`, `us`, `ms`, `s`, `min`, `h`;
extend the list for other periods), and the deserializer rejects a value whose unit differs from the requested
duration type instead of silently misreading it:
??? example "Example: serialize durations and system_clock time points" ??? example "Example: serialize durations and system_clock time points"
@@ -107,7 +109,7 @@ the JSON value**, so document it, for example in the member name (`timeout_ms`):
--8<-- "examples/type_recipes_chrono.output" --8<-- "examples/type_recipes_chrono.output"
``` ```
A `system_clock::time_point` with millisecond precision is written as the number of milliseconds since the Unix epoch. A `system_clock::time_point` with millisecond precision is written as the duration since the Unix epoch, that is, as an object with `unit` and `value`.
The time point serializer is limited to `system_clock`, The time point serializer is limited to `system_clock`,
because the epoch of other clocks (for example `steady_clock`) has no meaning outside the running program. because the epoch of other clocks (for example `steady_clock`) has no meaning outside the running program.
+1 -1
View File
@@ -195,7 +195,7 @@ This error indicates a syntax error while deserializing a JSON text. The error m
- Make sure the input is correctly read. Try to write the input to standard output to check if, for instance, the input file was successfully opened. - Make sure the input is correctly read. Try to write the input to standard output to check if, for instance, the input file was successfully opened.
- Paste the input to a JSON validator like <https://jsonlint.com> or a tool like [jq](https://jqlang.github.io/jq/). - Paste the input to a JSON validator like <https://jsonlint.com> or a tool like [jq](https://jqlang.github.io/jq/).
??? info "Parsing a file fails at line 1, column 1 (or \"unexpected end of input\")" ??? info "Parsing a file fails at line 1, column 1 (unexpected end of input)"
The messages `attempting to parse an empty input` (since version 3.11.3) and `unexpected end of input` (also the message of The messages `attempting to parse an empty input` (since version 3.11.3) and `unexpected end of input` (also the message of
earlier versions for an empty input) at the very start of the input mean earlier versions for an empty input) at the very start of the input mean