Skip to content

Commit fb91cc0

Browse files
authored
Allow from_json to gracefully fail with std::optional (#1791)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 890fbbc commit fb91cc0

File tree

4 files changed

+424
-98
lines changed

4 files changed

+424
-98
lines changed

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

Lines changed: 148 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include <cassert> // assert
88
#include <concepts> // std::same_as, std::constructible_from
99
#include <functional> // std::function
10-
#include <optional> // std::optional
10+
#include <optional> // std::optional, std::nullopt, std::bad_optional_access
1111
#include <tuple> // std::tuple, std::apply, std::tuple_element_t, std::tuple_size, std::tuple_size_v
1212
#include <type_traits> // std::false_type, std::true_type, std::void_t, std::is_enum_v, std::underlying_type_t, std::is_same_v, std::is_base_of_v, std::remove_cvref_t
1313
#include <utility> // std::pair, std:::make_index_sequence, std::index_sequence
@@ -38,7 +38,7 @@ struct json_auto_is_basic_string<std::basic_string<CharT, Traits, Alloc>>
3838
/// @ingroup json
3939
template <typename T>
4040
concept json_auto_has_method_from = requires(const JSON &value) {
41-
{ T::from_json(value) } -> std::same_as<T>;
41+
{ T::from_json(value) } -> std::same_as<std::optional<T>>;
4242
};
4343

4444
/// @ingroup json
@@ -131,24 +131,30 @@ auto to_json(const T &value) -> JSON {
131131
/// If the value has a `.from_json()` static method, always prefer that
132132
template <typename T>
133133
requires(json_auto_has_method_from<T>)
134-
auto from_json(const JSON &value) -> T {
134+
auto from_json(const JSON &value) -> std::optional<T> {
135135
return T::from_json(value);
136136
}
137137

138138
/// @ingroup json
139139
template <typename T>
140140
requires std::is_same_v<T, bool>
141-
auto from_json(const JSON &value) -> T {
142-
assert(value.is_boolean());
143-
return value.to_boolean();
141+
auto from_json(const JSON &value) -> std::optional<T> {
142+
if (value.is_boolean()) {
143+
return value.to_boolean();
144+
} else {
145+
return std::nullopt;
146+
}
144147
}
145148

146149
/// @ingroup json
147150
template <typename T>
148151
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
149-
auto from_json(const JSON &value) -> T {
150-
assert(value.is_integer());
151-
return static_cast<T>(value.to_integer());
152+
auto from_json(const JSON &value) -> std::optional<T> {
153+
if (value.is_integer()) {
154+
return static_cast<T>(value.to_integer());
155+
} else {
156+
return std::nullopt;
157+
}
152158
}
153159

154160
// TODO: How can we keep this in the hash header that does not yet know about
@@ -177,20 +183,27 @@ auto to_json(const T &hash) -> JSON {
177183
/// @ingroup json
178184
template <typename T>
179185
requires std::is_same_v<T, JSON::Object::Container::hash_type>
180-
auto from_json(const JSON &value) -> T {
181-
assert(value.is_array());
182-
assert(value.size() == 4);
186+
auto from_json(const JSON &value) -> std::optional<T> {
187+
if (!value.is_array() || value.size() != 4 || !value.at(0).is_integer() ||
188+
!value.at(1).is_integer() || !value.at(2).is_integer() ||
189+
!value.at(3).is_integer()) {
190+
return std::nullopt;
191+
}
192+
183193
#if defined(__SIZEOF_INT128__)
184-
return {
185-
(static_cast<__uint128_t>(from_json<std::uint64_t>(value.at(0))) << 64) |
186-
from_json<std::uint64_t>(value.at(1)),
187-
(static_cast<__uint128_t>(from_json<std::uint64_t>(value.at(2))) << 64) |
188-
from_json<std::uint64_t>(value.at(3))};
194+
return T{(static_cast<__uint128_t>(
195+
static_cast<std::uint64_t>(value.at(0).to_integer()))
196+
<< 64) |
197+
static_cast<std::uint64_t>(value.at(1).to_integer()),
198+
(static_cast<__uint128_t>(
199+
static_cast<std::uint64_t>(value.at(2).to_integer()))
200+
<< 64) |
201+
static_cast<std::uint64_t>(value.at(3).to_integer())};
189202
#else
190-
return {from_json<std::uint64_t>(value.at(0)),
191-
from_json<std::uint64_t>(value.at(1)),
192-
from_json<std::uint64_t>(value.at(2)),
193-
from_json<std::uint64_t>(value.at(3))};
203+
return T{static_cast<std::uint64_t>(value.at(0).to_integer()),
204+
static_cast<std::uint64_t>(value.at(1).to_integer()),
205+
static_cast<std::uint64_t>(value.at(2).to_integer()),
206+
static_cast<std::uint64_t>(value.at(3).to_integer())};
194207
#endif
195208
}
196209

@@ -204,16 +217,19 @@ auto to_json(const T &value) -> JSON {
204217
/// @ingroup json
205218
template <typename T>
206219
requires std::is_same_v<T, JSON>
207-
auto from_json(const JSON &value) -> T {
220+
auto from_json(const JSON &value) -> std::optional<T> {
208221
return value;
209222
}
210223

211224
/// @ingroup json
212225
template <typename T>
213226
requires json_auto_is_basic_string<T>::value
214-
auto from_json(const JSON &value) -> T {
215-
assert(value.is_string());
216-
return value.to_string();
227+
auto from_json(const JSON &value) -> std::optional<T> {
228+
if (value.is_string()) {
229+
return value.to_string();
230+
} else {
231+
return std::nullopt;
232+
}
217233
}
218234

219235
/// @ingroup json
@@ -226,10 +242,12 @@ auto to_json(const T value) -> JSON {
226242
/// @ingroup json
227243
template <typename T>
228244
requires std::is_enum_v<T>
229-
auto from_json(const JSON &value) -> T {
230-
assert(value.is_integer());
231-
assert(value.is_positive());
232-
return static_cast<T>(value.to_integer());
245+
auto from_json(const JSON &value) -> std::optional<T> {
246+
if (value.is_integer()) {
247+
return static_cast<T>(value.to_integer());
248+
} else {
249+
return std::nullopt;
250+
}
233251
}
234252

235253
/// @ingroup json
@@ -241,11 +259,17 @@ template <typename T> auto to_json(const std::optional<T> &value) -> JSON {
241259
template <typename T>
242260
requires requires { typename T::value_type; } &&
243261
std::is_same_v<T, std::optional<typename T::value_type>>
244-
auto from_json(const JSON &value) -> T {
262+
auto from_json(const JSON &value) -> std::optional<T> {
245263
if (value.is_null()) {
246-
return {};
264+
return std::optional<T>{
265+
std::optional<typename T::value_type>{std::nullopt}};
247266
} else {
248-
return from_json<typename T::value_type>(value);
267+
auto result{from_json<typename T::value_type>(value)};
268+
if (!result.has_value()) {
269+
return std::nullopt;
270+
}
271+
272+
return result;
249273
}
250274
}
251275

@@ -302,45 +326,60 @@ auto to_json(
302326
}
303327

304328
/// @ingroup json
305-
template <json_auto_list_like T> auto from_json(const JSON &value) -> T {
306-
assert(value.is_array());
329+
template <json_auto_list_like T>
330+
auto from_json(const JSON &value) -> std::optional<T> {
331+
if (!value.is_array()) {
332+
return std::nullopt;
333+
}
334+
307335
T result;
308336

309337
if constexpr (requires { result.reserve(value.size()); }) {
310338
result.reserve(value.size());
311339
}
312340

313341
for (const auto &item : value.as_array()) {
314-
if constexpr (requires {
315-
result.insert(from_json<typename T::value_type>(item));
316-
}) {
317-
result.insert(from_json<typename T::value_type>(item));
342+
auto subvalue{from_json<typename T::value_type>(item)};
343+
if (!subvalue.has_value()) {
344+
return std::nullopt;
345+
}
346+
347+
if constexpr (requires { result.insert(subvalue.value()); }) {
348+
result.insert(std::move(subvalue).value());
318349
} else {
319-
result.push_back(from_json<typename T::value_type>(item));
350+
result.push_back(std::move(subvalue).value());
320351
}
321352
}
322353

323354
return result;
324355
}
325356

357+
/// @ingroup json
326358
template <json_auto_list_like T>
327359
auto from_json(
328360
const JSON &value,
329-
const std::function<typename T::value_type(const JSON &)> &callback) -> T {
330-
assert(value.is_array());
361+
const std::function<std::optional<typename T::value_type>(const JSON &)>
362+
&callback) -> std::optional<T> {
363+
if (!value.is_array()) {
364+
return std::nullopt;
365+
}
366+
331367
T result;
332368

333369
if constexpr (requires { result.reserve(value.size()); }) {
334370
result.reserve(value.size());
335371
}
336372

337373
for (const auto &item : value.as_array()) {
338-
if constexpr (requires {
339-
result.insert(from_json<typename T::value_type>(item));
340-
}) {
341-
result.insert(callback(item));
374+
auto subvalue{callback(item)};
375+
if (!subvalue.has_value()) {
376+
return std::nullopt;
377+
}
378+
379+
if constexpr (requires { result.insert(subvalue.value()); }) {
380+
result.insert(std::move(subvalue).value());
342381
} else {
343-
result.push_back(callback(item));
382+
result.push_back(std::move(subvalue).value());
344383
}
345384
}
346385

@@ -379,11 +418,20 @@ auto to_json(
379418
}
380419

381420
/// @ingroup json
382-
template <json_auto_map_like T> auto from_json(const JSON &value) -> T {
383-
assert(value.is_object());
421+
template <json_auto_map_like T>
422+
auto from_json(const JSON &value) -> std::optional<T> {
423+
if (!value.is_object()) {
424+
return std::nullopt;
425+
}
426+
384427
T result;
385428
for (const auto &item : value.as_object()) {
386-
result.emplace(item.first, from_json<typename T::mapped_type>(item.second));
429+
auto subvalue{from_json<typename T::mapped_type>(item.second)};
430+
if (!subvalue.has_value()) {
431+
return std::nullopt;
432+
}
433+
434+
result.emplace(item.first, std::move(subvalue).value());
387435
}
388436

389437
return result;
@@ -393,11 +441,20 @@ template <json_auto_map_like T> auto from_json(const JSON &value) -> T {
393441
template <json_auto_map_like T>
394442
auto from_json(
395443
const JSON &value,
396-
const std::function<typename T::mapped_type(const JSON &)> &callback) -> T {
397-
assert(value.is_object());
444+
const std::function<std::optional<typename T::mapped_type>(const JSON &)>
445+
&callback) -> std::optional<T> {
446+
if (!value.is_object()) {
447+
return std::nullopt;
448+
}
449+
398450
T result;
399451
for (const auto &item : value.as_object()) {
400-
result.emplace(item.first, callback(item.second));
452+
auto subvalue{callback(item.second)};
453+
if (!subvalue.has_value()) {
454+
return std::nullopt;
455+
}
456+
457+
result.emplace(item.first, std::move(subvalue).value());
401458
}
402459

403460
return result;
@@ -424,12 +481,19 @@ auto to_json(const std::pair<L, R> &value) -> JSON {
424481
/// @ingroup json
425482
template <typename T>
426483
requires json_auto_is_pair<T>::value
427-
auto from_json(const JSON &value) -> T {
428-
assert(value.is_array());
429-
assert(value.size() == 2);
484+
auto from_json(const JSON &value) -> std::optional<T> {
485+
if (!value.is_array() || value.size() != 2) {
486+
return std::nullopt;
487+
}
488+
489+
auto first{from_json<typename T::first_type>(value.at(0))};
490+
auto second{from_json<typename T::second_type>(value.at(1))};
491+
if (!first.has_value() || !second.has_value()) {
492+
return std::nullopt;
493+
}
494+
430495
return std::make_pair<typename T::first_type, typename T::second_type>(
431-
from_json<typename T::first_type>(value.at(0)),
432-
from_json<typename T::second_type>(value.at(1)));
496+
std::move(first).value(), std::move(second).value());
433497
}
434498

435499
// Handle 1-element tuples
@@ -442,10 +506,18 @@ template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON {
442506
}
443507

444508
/// @ingroup json
445-
template <json_auto_tuple_mono T> auto from_json(const JSON &value) -> T {
446-
assert(value.is_array());
447-
assert(value.size() == 1);
448-
return {from_json<std::tuple_element_t<0, T>>(value.at(0))};
509+
template <json_auto_tuple_mono T>
510+
auto from_json(const JSON &value) -> std::optional<T> {
511+
if (!value.is_array() || value.size() != 1) {
512+
return std::nullopt;
513+
}
514+
515+
auto first{from_json<std::tuple_element_t<0, T>>(value.at(0))};
516+
if (!first.has_value()) {
517+
return std::nullopt;
518+
}
519+
520+
return {std::move(first).value()};
449521
}
450522

451523
/// @ingroup json
@@ -463,16 +535,25 @@ template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON {
463535
template <typename T, std::size_t... Indices>
464536
auto from_json_tuple_poly(const JSON &value, std::index_sequence<Indices...>)
465537
-> T {
466-
return {from_json<std::tuple_element_t<Indices, T>>(value.at(Indices))...};
538+
return {from_json<std::tuple_element_t<Indices, T>>(value.at(Indices))
539+
.value()...};
467540
}
468541
#endif
469542

470543
/// @ingroup json
471-
template <json_auto_tuple_poly T> auto from_json(const JSON &value) -> T {
472-
assert(value.is_array());
473-
assert(value.size() == std::tuple_size_v<T>);
474-
return from_json_tuple_poly<T>(
475-
value, std::make_index_sequence<std::tuple_size_v<T>>{});
544+
template <json_auto_tuple_poly T>
545+
auto from_json(const JSON &value) -> std::optional<T> {
546+
if (!value.is_array() || value.size() != std::tuple_size_v<T>) {
547+
return std::nullopt;
548+
}
549+
550+
try {
551+
return from_json_tuple_poly<T>(
552+
value, std::make_index_sequence<std::tuple_size_v<T>>{});
553+
// TODO: Maybe there is a better way to catch this without using exceptions?
554+
} catch (const std::bad_optional_access &) {
555+
return std::nullopt;
556+
}
476557
}
477558

478559
} // namespace sourcemeta::core

src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -579,17 +579,24 @@ auto to_json(const T &value) -> JSON {
579579
/// Serialise a WeakPointer as JSON
580580
template <typename T>
581581
requires std::is_same_v<T, WeakPointer>
582-
auto to_json(const T &value) -> JSON {
582+
auto to_json(const T &value) -> std::optional<JSON> {
583583
return JSON{to_string(value)};
584584
}
585585

586586
/// @ingroup jsonpointer
587587
/// Deserialise a Pointer from JSON
588588
template <typename T>
589589
requires std::is_same_v<T, Pointer>
590-
auto from_json(const JSON &value) -> T {
591-
assert(value.is_string());
592-
return to_pointer(value.to_string());
590+
auto from_json(const JSON &value) -> std::optional<T> {
591+
if (!value.is_string()) {
592+
return std::nullopt;
593+
}
594+
595+
try {
596+
return to_pointer(value.to_string());
597+
} catch (const PointerParseError &) {
598+
return std::nullopt;
599+
}
593600
}
594601

595602
} // namespace sourcemeta::core

0 commit comments

Comments
 (0)