Skip to content

Commit 27b0a64

Browse files
authored
Add Decimal support to the JSON class (#2032)
Fixes: #838 See: sourcemeta/jsonschema#235 Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 7ce57be commit 27b0a64

36 files changed

+2379
-61
lines changed

benchmark/json.cc

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,121 @@ static void JSON_Parse_Real(benchmark::State &state) {
207207
}
208208
}
209209

210+
static void JSON_Parse_Decimal(benchmark::State &state) {
211+
const auto document{R"JSON([
212+
123456789012345678901234567890,
213+
987654321098765432109876543210,
214+
111111111111111111111111111111,
215+
999999999999999999999999999999,
216+
555555555555555555555555555555,
217+
1e309,
218+
1e310,
219+
1e320,
220+
1e350,
221+
1e400,
222+
9.99e308,
223+
9.99e309,
224+
9.99e310,
225+
9.99e320,
226+
9.99e350,
227+
123456789012345678901234567890,
228+
987654321098765432109876543210,
229+
111111111111111111111111111111,
230+
999999999999999999999999999999,
231+
555555555555555555555555555555,
232+
1e309,
233+
1e310,
234+
1e320,
235+
1e350,
236+
1e400,
237+
9.99e308,
238+
9.99e309,
239+
9.99e310,
240+
9.99e320,
241+
9.99e350,
242+
123456789012345678901234567890,
243+
987654321098765432109876543210,
244+
111111111111111111111111111111,
245+
999999999999999999999999999999,
246+
555555555555555555555555555555,
247+
1e309,
248+
1e310,
249+
1e320,
250+
1e350,
251+
1e400,
252+
9.99e308,
253+
9.99e309,
254+
9.99e310,
255+
9.99e320,
256+
9.99e350,
257+
123456789012345678901234567890,
258+
987654321098765432109876543210,
259+
111111111111111111111111111111,
260+
999999999999999999999999999999,
261+
555555555555555555555555555555,
262+
1e309,
263+
1e310,
264+
1e320,
265+
1e350,
266+
1e400,
267+
9.99e308,
268+
9.99e309,
269+
9.99e310,
270+
9.99e320,
271+
9.99e350,
272+
123456789012345678901234567890,
273+
987654321098765432109876543210,
274+
111111111111111111111111111111,
275+
999999999999999999999999999999,
276+
555555555555555555555555555555,
277+
1e309,
278+
1e310,
279+
1e320,
280+
1e350,
281+
1e400,
282+
9.99e308,
283+
9.99e309,
284+
9.99e310,
285+
9.99e320,
286+
9.99e350,
287+
123456789012345678901234567890,
288+
987654321098765432109876543210,
289+
111111111111111111111111111111,
290+
999999999999999999999999999999,
291+
555555555555555555555555555555,
292+
1e309,
293+
1e310,
294+
1e320,
295+
1e350,
296+
1e400,
297+
9.99e308,
298+
9.99e309,
299+
9.99e310,
300+
9.99e320,
301+
9.99e350,
302+
123456789012345678901234567890,
303+
987654321098765432109876543210,
304+
111111111111111111111111111111,
305+
999999999999999999999999999999,
306+
555555555555555555555555555555,
307+
1e309,
308+
1e310,
309+
1e320,
310+
1e350,
311+
1e400
312+
])JSON"};
313+
314+
assert(
315+
std::ranges::all_of(sourcemeta::core::parse_json(document).as_array(),
316+
[](const auto &item) { return item.is_decimal(); }));
317+
318+
for (auto _ : state) {
319+
auto result{sourcemeta::core::parse_json(document)};
320+
assert(result.is_array());
321+
benchmark::DoNotOptimize(result);
322+
}
323+
}
324+
210325
static void JSON_Fast_Hash_Helm_Chart_Lock(benchmark::State &state) {
211326
// From `helm-chart-lock`
212327
const auto document{sourcemeta::core::parse_json(R"JSON({
@@ -427,6 +542,7 @@ static void JSON_Object_Defines_Miss_Too_Large(benchmark::State &state) {
427542
BENCHMARK(JSON_Array_Of_Objects_Unique);
428543
BENCHMARK(JSON_Parse_1);
429544
BENCHMARK(JSON_Parse_Real);
545+
BENCHMARK(JSON_Parse_Decimal);
430546
BENCHMARK(JSON_Fast_Hash_Helm_Chart_Lock);
431547
BENCHMARK(JSON_Equality_Helm_Chart_Lock);
432548
BENCHMARK(JSON_String_Equal)->Args({10})->Args({100});

config.cmake.in

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,41 +50,63 @@ foreach(component ${SOURCEMETA_CORE_COMPONENTS})
5050
elseif(component STREQUAL "uri")
5151
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
5252
elseif(component STREQUAL "json")
53+
find_dependency(mpdecimal CONFIG)
54+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
5355
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
5456
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
5557
elseif(component STREQUAL "jsonl")
58+
find_dependency(mpdecimal CONFIG)
59+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
60+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
5661
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
5762
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonl.cmake")
5863
elseif(component STREQUAL "jsonpointer")
5964
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_regex.cmake")
6065
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
66+
find_dependency(mpdecimal CONFIG)
67+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
68+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
6169
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
6270
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
6371
elseif(component STREQUAL "jsonschema")
6472
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
73+
find_dependency(mpdecimal CONFIG)
74+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
75+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
6576
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
6677
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
6778
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake")
6879
elseif(component STREQUAL "yaml")
80+
find_dependency(mpdecimal CONFIG)
81+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
82+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
6983
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
7084
find_dependency(yaml CONFIG)
7185
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
7286
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_yaml.cmake")
7387
elseif(component STREQUAL "alterschema")
7488
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
89+
find_dependency(mpdecimal CONFIG)
90+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
91+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
7592
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
7693
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
7794
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake")
7895
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_alterschema.cmake")
7996
elseif(component STREQUAL "editorschema")
8097
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
98+
find_dependency(mpdecimal CONFIG)
99+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
100+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
81101
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
82102
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
83103
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake")
84104
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_editorschema.cmake")
85105
elseif(component STREQUAL "schemaconfig")
86-
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
87106
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
107+
find_dependency(mpdecimal CONFIG)
108+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
109+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
88110
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
89111
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
90112
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_schemaconfig.cmake")

src/core/json/include/sourcemeta/core/json_auto.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ auto from_json(const JSON &value) -> std::optional<T> {
161161
}
162162
}
163163

164+
/// @ingroup json
165+
template <typename T>
166+
requires std::is_same_v<T, Decimal>
167+
auto from_json(const JSON &value) -> std::optional<T> {
168+
if (value.is_decimal()) {
169+
return value.to_decimal();
170+
} else {
171+
return std::nullopt;
172+
}
173+
}
174+
164175
// TODO: How can we keep this in the hash header that does not yet know about
165176
// JSON?
166177
/// @ingroup json

src/core/json/include/sourcemeta/core/json_error.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,6 @@ class SOURCEMETA_CORE_JSON_EXPORT JSONParseError : public std::exception {
5151
std::string message_{"Failed to parse the JSON document"};
5252
};
5353

54-
/// @ingroup json
55-
/// This class represents a numeric integer limit parsing error
56-
class SOURCEMETA_CORE_JSON_EXPORT JSONParseIntegerLimitError
57-
: public JSONParseError {
58-
public:
59-
/// Create a parsing error
60-
JSONParseIntegerLimitError(const std::uint64_t line,
61-
const std::uint64_t column)
62-
: JSONParseError{
63-
line, column,
64-
"The JSON value is not representable by the IETF RFC 8259 "
65-
"interoperable signed integer range"} {}
66-
};
67-
6854
/// @ingroup json
6955
/// This class represents a parsing error occurring from parsing a file
7056
class SOURCEMETA_CORE_JSON_EXPORT JSONFileParseError : public JSONParseError {

src/core/json/include/sourcemeta/core/json_value.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <sourcemeta/core/json_hash.h>
1010
#include <sourcemeta/core/json_object.h>
1111

12+
#include <sourcemeta/core/numeric.h>
13+
1214
#include <algorithm> // std::any_of
1315
#include <cassert> // assert
1416
#include <cstddef> // std::size_t
@@ -55,7 +57,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON {
5557
Real = 3,
5658
String = 4,
5759
Array = 5,
58-
Object = 6
60+
Object = 6,
61+
Decimal = 7
5962
};
6063

6164
/// An optional callback that can be passed to parsing functions to obtain
@@ -214,6 +217,12 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON {
214217
/// A copy constructor for the object type.
215218
explicit JSON(const Object &value);
216219

220+
/// A copy constructor for the decimal type.
221+
explicit JSON(const Decimal &value);
222+
223+
/// A move constructor for the decimal type.
224+
explicit JSON(Decimal &&value);
225+
217226
/// Misc constructors
218227
JSON(const JSON &);
219228
JSON(JSON &&) noexcept;
@@ -463,6 +472,19 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON {
463472
/// ```
464473
[[nodiscard]] auto is_object() const noexcept -> bool;
465474

475+
/// Check if the input JSON document is an arbitrary precision decimal value.
476+
/// For example:
477+
///
478+
/// ```cpp
479+
/// #include <sourcemeta/core/json.h>
480+
/// #include <cassert>
481+
///
482+
/// const sourcemeta::core::Decimal value{1234567890};
483+
/// const sourcemeta::core::JSON document{value};
484+
/// assert(document.is_decimal());
485+
/// ```
486+
[[nodiscard]] auto is_decimal() const noexcept -> bool;
487+
466488
/// Get the type of the JSON document. For example:
467489
///
468490
/// ```cpp
@@ -519,6 +541,20 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON {
519541
/// ```
520542
[[nodiscard]] auto to_real() const noexcept -> Real;
521543

544+
/// Convert a JSON instance into a decimal value. The result of this method
545+
/// is undefined unless the JSON instance holds a decimal value. For example:
546+
///
547+
/// ```cpp
548+
/// #include <sourcemeta/core/json.h>
549+
/// #include <cassert>
550+
///
551+
/// const sourcemeta::core::Decimal value{1234567890};
552+
/// const sourcemeta::core::JSON document{value};
553+
/// assert(document.is_decimal());
554+
/// assert(document.to_decimal().to_int64() == 1234567890);
555+
/// ```
556+
[[nodiscard]] auto to_decimal() const noexcept -> const Decimal &;
557+
522558
/// Convert a JSON instance into a standard string value. The result of this
523559
/// method is undefined unless the JSON instance holds a string value. For
524560
/// example:
@@ -1713,6 +1749,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON {
17131749
String data_string;
17141750
Array data_array;
17151751
Object data_object;
1752+
Decimal data_decimal;
17161753
};
17171754
#if defined(_MSC_VER)
17181755
#pragma warning(default : 4251)

src/core/json/json.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ auto read_json(const std::filesystem::path &path,
5050
auto stream{read_file<JSON::Char, JSON::CharTraits>(path)};
5151
try {
5252
return parse_json(stream, callback);
53-
} catch (const JSONParseIntegerLimitError &error) {
54-
// For producing better error messages
55-
throw JSONFileParseError(path, error);
5653
} catch (const JSONParseError &error) {
5754
// For producing better error messages
5855
throw JSONFileParseError(path, error);
@@ -94,6 +91,8 @@ auto operator<<(std::basic_ostream<JSON::Char, JSON::CharTraits> &stream,
9491
return stream << "integer";
9592
case sourcemeta::core::JSON::Type::Real:
9693
return stream << "real";
94+
case sourcemeta::core::JSON::Type::Decimal:
95+
return stream << "decimal";
9796
case sourcemeta::core::JSON::Type::String:
9897
return stream << "string";
9998
case sourcemeta::core::JSON::Type::Array:

0 commit comments

Comments
 (0)