1// __ _____ _____ _____ 2// __| | __| | | | JSON for Modern C++ (supporting code) 3// | | |__ | | | | | | version 3.11.2 4// |_____|_____|_____|_|___| https://github.com/nlohmann/json 5// 6// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me> 7// SPDX-License-Identifier: MIT 8 9#include "doctest_compatibility.h" 10 11// disable -Wnoexcept due to class Evil 12DOCTEST_GCC_SUPPRESS_WARNING_PUSH 13DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") 14 15#include <nlohmann/json.hpp> 16using nlohmann::json; 17#ifdef JSON_TEST_NO_GLOBAL_UDLS 18 using namespace nlohmann::literals; // NOLINT(google-build-using-namespace) 19#endif 20 21#include <map> 22#include <memory> 23#include <string> 24#include <utility> 25 26namespace udt 27{ 28enum class country 29{ 30 china, 31 france, 32 russia 33}; 34 35struct age 36{ 37 int m_val; 38 age(int rhs = 0) : m_val(rhs) {} 39}; 40 41struct name 42{ 43 std::string m_val; 44 name(std::string rhs = "") : m_val(std::move(rhs)) {} 45}; 46 47struct address 48{ 49 std::string m_val; 50 address(std::string rhs = "") : m_val(std::move(rhs)) {} 51}; 52 53struct person 54{ 55 age m_age{}; 56 name m_name{}; 57 country m_country{}; 58 person() = default; 59 person(const age& a, name n, const country& c) : m_age(a), m_name(std::move(n)), m_country(c) {} 60}; 61 62struct contact 63{ 64 person m_person{}; 65 address m_address{}; 66 contact() = default; 67 contact(person p, address a) : m_person(std::move(p)), m_address(std::move(a)) {} 68}; 69 70struct contact_book 71{ 72 name m_book_name{}; 73 std::vector<contact> m_contacts{}; 74 contact_book() = default; 75 contact_book(name n, std::vector<contact> c) : m_book_name(std::move(n)), m_contacts(std::move(c)) {} 76}; 77} // namespace udt 78 79// to_json methods 80namespace udt 81{ 82// templates because of the custom_json tests (see below) 83template <typename BasicJsonType> 84static void to_json(BasicJsonType& j, age a) 85{ 86 j = a.m_val; 87} 88 89template <typename BasicJsonType> 90static void to_json(BasicJsonType& j, const name& n) 91{ 92 j = n.m_val; 93} 94 95template <typename BasicJsonType> 96static void to_json(BasicJsonType& j, country c) 97{ 98 switch (c) 99 { 100 case country::china: 101 j = "中华人民共和国"; 102 return; 103 case country::france: 104 j = "France"; 105 return; 106 case country::russia: 107 j = "Российская Федерация"; 108 return; 109 default: 110 break; 111 } 112} 113 114template <typename BasicJsonType> 115static void to_json(BasicJsonType& j, const person& p) 116{ 117 j = BasicJsonType{{"age", p.m_age}, {"name", p.m_name}, {"country", p.m_country}}; 118} 119 120static void to_json(nlohmann::json& j, const address& a) 121{ 122 j = a.m_val; 123} 124 125static void to_json(nlohmann::json& j, const contact& c) 126{ 127 j = json{{"person", c.m_person}, {"address", c.m_address}}; 128} 129 130static void to_json(nlohmann::json& j, const contact_book& cb) 131{ 132 j = json{{"name", cb.m_book_name}, {"contacts", cb.m_contacts}}; 133} 134 135// operators 136static bool operator==(age lhs, age rhs) 137{ 138 return lhs.m_val == rhs.m_val; 139} 140 141static bool operator==(const address& lhs, const address& rhs) 142{ 143 return lhs.m_val == rhs.m_val; 144} 145 146static bool operator==(const name& lhs, const name& rhs) 147{ 148 return lhs.m_val == rhs.m_val; 149} 150 151static bool operator==(const person& lhs, const person& rhs) 152{ 153 return std::tie(lhs.m_name, lhs.m_age) == std::tie(rhs.m_name, rhs.m_age); 154} 155 156static bool operator==(const contact& lhs, const contact& rhs) 157{ 158 return std::tie(lhs.m_person, lhs.m_address) == 159 std::tie(rhs.m_person, rhs.m_address); 160} 161 162static bool operator==(const contact_book& lhs, const contact_book& rhs) 163{ 164 return std::tie(lhs.m_book_name, lhs.m_contacts) == 165 std::tie(rhs.m_book_name, rhs.m_contacts); 166} 167} // namespace udt 168 169// from_json methods 170namespace udt 171{ 172template <typename BasicJsonType> 173static void from_json(const BasicJsonType& j, age& a) 174{ 175 a.m_val = j.template get<int>(); 176} 177 178template <typename BasicJsonType> 179static void from_json(const BasicJsonType& j, name& n) 180{ 181 n.m_val = j.template get<std::string>(); 182} 183 184template <typename BasicJsonType> 185static void from_json(const BasicJsonType& j, country& c) 186{ 187 const auto str = j.template get<std::string>(); 188 const std::map<std::string, country> m = 189 { 190 {"中华人民共和国", country::china}, 191 {"France", country::france}, 192 {"Российская Федерация", country::russia} 193 }; 194 195 const auto it = m.find(str); 196 // TODO(nlohmann) test exceptions 197 c = it->second; 198} 199 200template <typename BasicJsonType> 201static void from_json(const BasicJsonType& j, person& p) 202{ 203 p.m_age = j["age"].template get<age>(); 204 p.m_name = j["name"].template get<name>(); 205 p.m_country = j["country"].template get<country>(); 206} 207 208static void from_json(const nlohmann::json& j, address& a) 209{ 210 a.m_val = j.get<std::string>(); 211} 212 213static void from_json(const nlohmann::json& j, contact& c) 214{ 215 c.m_person = j["person"].get<person>(); 216 c.m_address = j["address"].get<address>(); 217} 218 219static void from_json(const nlohmann::json& j, contact_book& cb) 220{ 221 cb.m_book_name = j["name"].get<name>(); 222 cb.m_contacts = j["contacts"].get<std::vector<contact>>(); 223} 224} // namespace udt 225 226TEST_CASE("basic usage" * doctest::test_suite("udt")) 227{ 228 229 // a bit narcissistic maybe :) ? 230 const udt::age a 231 { 232 23 233 }; 234 const udt::name n{"theo"}; 235 const udt::country c{udt::country::france}; 236 const udt::person sfinae_addict{a, n, c}; 237 const udt::person senior_programmer{{42}, {"王芳"}, udt::country::china}; 238 const udt::address addr{"Paris"}; 239 const udt::contact cpp_programmer{sfinae_addict, addr}; 240 const udt::contact_book book{{"C++"}, {cpp_programmer, {senior_programmer, addr}}}; 241 242 SECTION("conversion to json via free-functions") 243 { 244 CHECK(json(a) == json(23)); 245 CHECK(json(n) == json("theo")); 246 CHECK(json(c) == json("France")); 247 CHECK(json(sfinae_addict) == R"({"name":"theo", "age":23, "country":"France"})"_json); 248 CHECK(json("Paris") == json(addr)); 249 CHECK(json(cpp_programmer) == 250 R"({"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"})"_json); 251 252 CHECK( 253 json(book) == 254 R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json); 255 256 } 257 258 SECTION("conversion from json via free-functions") 259 { 260 const auto big_json = 261 R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json; 262 SECTION("via explicit calls to get") 263 { 264 const auto parsed_book = big_json.get<udt::contact_book>(); 265 const auto book_name = big_json["name"].get<udt::name>(); 266 const auto contacts = 267 big_json["contacts"].get<std::vector<udt::contact>>(); 268 const auto contact_json = big_json["contacts"].at(0); 269 const auto contact = contact_json.get<udt::contact>(); 270 const auto person = contact_json["person"].get<udt::person>(); 271 const auto address = contact_json["address"].get<udt::address>(); 272 const auto age = contact_json["person"]["age"].get<udt::age>(); 273 const auto country = 274 contact_json["person"]["country"].get<udt::country>(); 275 const auto name = contact_json["person"]["name"].get<udt::name>(); 276 277 CHECK(age == a); 278 CHECK(name == n); 279 CHECK(country == c); 280 CHECK(address == addr); 281 CHECK(person == sfinae_addict); 282 CHECK(contact == cpp_programmer); 283 CHECK(contacts == book.m_contacts); 284 CHECK(book_name == udt::name{"C++"}); 285 CHECK(book == parsed_book); 286 } 287 288 SECTION("via explicit calls to get_to") 289 { 290 udt::person person; 291 udt::name name; 292 293 json person_json = big_json["contacts"][0]["person"]; 294 CHECK(person_json.get_to(person) == sfinae_addict); 295 296 // correct reference gets returned 297 person_json["name"].get_to(name).m_val = "new name"; 298 CHECK(name.m_val == "new name"); 299 } 300 301#if JSON_USE_IMPLICIT_CONVERSIONS 302 SECTION("implicit conversions") 303 { 304 const udt::contact_book parsed_book = big_json; 305 const udt::name book_name = big_json["name"]; 306 const std::vector<udt::contact> contacts = big_json["contacts"]; 307 const auto contact_json = big_json["contacts"].at(0); 308 const udt::contact contact = contact_json; 309 const udt::person person = contact_json["person"]; 310 const udt::address address = contact_json["address"]; 311 const udt::age age = contact_json["person"]["age"]; 312 const udt::country country = contact_json["person"]["country"]; 313 const udt::name name = contact_json["person"]["name"]; 314 315 CHECK(age == a); 316 CHECK(name == n); 317 CHECK(country == c); 318 CHECK(address == addr); 319 CHECK(person == sfinae_addict); 320 CHECK(contact == cpp_programmer); 321 CHECK(contacts == book.m_contacts); 322 CHECK(book_name == udt::name{"C++"}); 323 CHECK(book == parsed_book); 324 } 325#endif 326 } 327} 328 329namespace udt 330{ 331struct legacy_type 332{ 333 std::string number{}; 334 legacy_type() = default; 335 legacy_type(std::string n) : number(std::move(n)) {} 336}; 337} // namespace udt 338 339namespace nlohmann 340{ 341template <typename T> 342struct adl_serializer<std::shared_ptr<T>> 343{ 344 static void to_json(json& j, const std::shared_ptr<T>& opt) 345 { 346 if (opt) 347 { 348 j = *opt; 349 } 350 else 351 { 352 j = nullptr; 353 } 354 } 355 356 static void from_json(const json& j, std::shared_ptr<T>& opt) 357 { 358 if (j.is_null()) 359 { 360 opt = nullptr; 361 } 362 else 363 { 364 opt.reset(new T(j.get<T>())); // NOLINT(cppcoreguidelines-owning-memory) 365 } 366 } 367}; 368 369template <> 370struct adl_serializer<udt::legacy_type> 371{ 372 static void to_json(json& j, const udt::legacy_type& l) 373 { 374 j = std::stoi(l.number); 375 } 376 377 static void from_json(const json& j, udt::legacy_type& l) 378 { 379 l.number = std::to_string(j.get<int>()); 380 } 381}; 382} // namespace nlohmann 383 384TEST_CASE("adl_serializer specialization" * doctest::test_suite("udt")) 385{ 386 SECTION("partial specialization") 387 { 388 SECTION("to_json") 389 { 390 std::shared_ptr<udt::person> optPerson; 391 392 json j = optPerson; 393 CHECK(j.is_null()); 394 395 optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-shared) 396 j = optPerson; 397 CHECK_FALSE(j.is_null()); 398 399 CHECK(j.get<udt::person>() == *optPerson); 400 } 401 402 SECTION("from_json") 403 { 404 auto person = udt::person{{42}, {"John Doe"}, udt::country::russia}; 405 json j = person; 406 407 auto optPerson = j.get<std::shared_ptr<udt::person>>(); 408 REQUIRE(optPerson); 409 CHECK(*optPerson == person); 410 411 j = nullptr; 412 optPerson = j.get<std::shared_ptr<udt::person>>(); 413 CHECK(!optPerson); 414 } 415 } 416 417 SECTION("total specialization") 418 { 419 SECTION("to_json") 420 { 421 udt::legacy_type lt{"4242"}; 422 423 json j = lt; 424 CHECK(j.get<int>() == 4242); 425 } 426 427 SECTION("from_json") 428 { 429 json j = 4242; 430 auto lt = j.get<udt::legacy_type>(); 431 CHECK(lt.number == "4242"); 432 } 433 } 434} 435 436namespace nlohmann 437{ 438template <> 439struct adl_serializer<std::vector<float>> 440{ 441 using type = std::vector<float>; 442 static void to_json(json& j, const type& /*type*/) 443 { 444 j = "hijacked!"; 445 } 446 447 static void from_json(const json& /*unnamed*/, type& opt) 448 { 449 opt = {42.0, 42.0, 42.0}; 450 } 451 452 // preferred version 453 static type from_json(const json& /*unnamed*/) 454 { 455 return {4.0, 5.0, 6.0}; 456 } 457}; 458} // namespace nlohmann 459 460TEST_CASE("even supported types can be specialized" * doctest::test_suite("udt")) 461{ 462 json j = std::vector<float> {1.0, 2.0, 3.0}; 463 CHECK(j.dump() == R"("hijacked!")"); 464 auto f = j.get<std::vector<float>>(); 465 // the single argument from_json method is preferred 466 CHECK((f == std::vector<float> {4.0, 5.0, 6.0})); 467} 468 469namespace nlohmann 470{ 471template <typename T> 472struct adl_serializer<std::unique_ptr<T>> 473{ 474 static void to_json(json& j, const std::unique_ptr<T>& opt) 475 { 476 if (opt) 477 { 478 j = *opt; 479 } 480 else 481 { 482 j = nullptr; 483 } 484 } 485 486 // this is the overload needed for non-copyable types, 487 static std::unique_ptr<T> from_json(const json& j) 488 { 489 if (j.is_null()) 490 { 491 return nullptr; 492 } 493 494 return std::unique_ptr<T>(new T(j.get<T>())); 495 } 496}; 497} // namespace nlohmann 498 499TEST_CASE("Non-copyable types" * doctest::test_suite("udt")) 500{ 501 SECTION("to_json") 502 { 503 std::unique_ptr<udt::person> optPerson; 504 505 json j = optPerson; 506 CHECK(j.is_null()); 507 508 optPerson.reset(new udt::person{{42}, {"John Doe"}, udt::country::russia}); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique) 509 j = optPerson; 510 CHECK_FALSE(j.is_null()); 511 512 CHECK(j.get<udt::person>() == *optPerson); 513 } 514 515 SECTION("from_json") 516 { 517 auto person = udt::person{{42}, {"John Doe"}, udt::country::russia}; 518 json j = person; 519 520 auto optPerson = j.get<std::unique_ptr<udt::person>>(); 521 REQUIRE(optPerson); 522 CHECK(*optPerson == person); 523 524 j = nullptr; 525 optPerson = j.get<std::unique_ptr<udt::person>>(); 526 CHECK(!optPerson); 527 } 528} 529 530// custom serializer - advanced usage 531// pack structs that are pod-types (but not scalar types) 532// relies on adl for any other type 533template <typename T, typename = void> 534struct pod_serializer 535{ 536 // use adl for non-pods, or scalar types 537 template < 538 typename BasicJsonType, typename U = T, 539 typename std::enable_if < 540 !(std::is_pod<U>::value && std::is_class<U>::value), int >::type = 0 > 541 static void from_json(const BasicJsonType& j, U& t) 542 { 543 using nlohmann::from_json; 544 from_json(j, t); 545 } 546 547 // special behaviour for pods 548 template < typename BasicJsonType, typename U = T, 549 typename std::enable_if < 550 std::is_pod<U>::value && std::is_class<U>::value, int >::type = 0 > 551 static void from_json(const BasicJsonType& j, U& t) 552 { 553 std::uint64_t value = 0; 554 // The following block is no longer relevant in this serializer, make another one that shows the issue 555 // the problem arises only when one from_json method is defined without any constraint 556 // 557 // Why cannot we simply use: j.get<std::uint64_t>() ? 558 // Well, with the current experiment, the get method looks for a from_json 559 // function, which we are currently defining! 560 // This would end up in a stack overflow. Calling nlohmann::from_json is a 561 // workaround (is it?). 562 // I shall find a good way to avoid this once all constructors are converted 563 // to free methods 564 // 565 // In short, constructing a json by constructor calls to_json 566 // calling get calls from_json, for now, we cannot do this in custom 567 // serializers 568 nlohmann::from_json(j, value); 569 auto* bytes = static_cast<char*>(static_cast<void*>(&value)); 570 std::memcpy(&t, bytes, sizeof(value)); 571 } 572 573 template < 574 typename BasicJsonType, typename U = T, 575 typename std::enable_if < 576 !(std::is_pod<U>::value && std::is_class<U>::value), int >::type = 0 > 577 static void to_json(BasicJsonType& j, const T& t) 578 { 579 using nlohmann::to_json; 580 to_json(j, t); 581 } 582 583 template < typename BasicJsonType, typename U = T, 584 typename std::enable_if < 585 std::is_pod<U>::value && std::is_class<U>::value, int >::type = 0 > 586 static void to_json(BasicJsonType& j, const T& t) noexcept 587 { 588 const auto* bytes = static_cast< const unsigned char*>(static_cast<const void*>(&t)); 589 std::uint64_t value = 0; 590 std::memcpy(&value, bytes, sizeof(value)); 591 nlohmann::to_json(j, value); 592 } 593}; 594 595namespace udt 596{ 597struct small_pod 598{ 599 int begin; 600 char middle; 601 short end; 602}; 603 604struct non_pod 605{ 606 std::string s{}; 607 non_pod() = default; 608 non_pod(std::string S) : s(std::move(S)) {} 609}; 610 611template <typename BasicJsonType> 612static void to_json(BasicJsonType& j, const non_pod& np) 613{ 614 j = np.s; 615} 616 617template <typename BasicJsonType> 618static void from_json(const BasicJsonType& j, non_pod& np) 619{ 620 np.s = j.template get<std::string>(); 621} 622 623static bool operator==(small_pod lhs, small_pod rhs) noexcept 624{ 625 return std::tie(lhs.begin, lhs.middle, lhs.end) == 626 std::tie(rhs.begin, rhs.middle, rhs.end); 627} 628 629static bool operator==(const non_pod& lhs, const non_pod& rhs) noexcept 630{ 631 return lhs.s == rhs.s; 632} 633 634static std::ostream& operator<<(std::ostream& os, small_pod l) 635{ 636 return os << "begin: " << l.begin << ", middle: " << l.middle << ", end: " << l.end; 637} 638} // namespace udt 639 640TEST_CASE("custom serializer for pods" * doctest::test_suite("udt")) 641{ 642 using custom_json = 643 nlohmann::basic_json<std::map, std::vector, std::string, bool, 644 std::int64_t, std::uint64_t, double, std::allocator, pod_serializer>; 645 646 auto p = udt::small_pod{42, '/', 42}; 647 custom_json j = p; 648 649 auto p2 = j.get<udt::small_pod>(); 650 651 CHECK(p == p2); 652 653 auto np = udt::non_pod{{"non-pod"}}; 654 custom_json j2 = np; 655 auto np2 = j2.get<udt::non_pod>(); 656 CHECK(np == np2); 657} 658 659template <typename T, typename> 660struct another_adl_serializer; 661 662using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, double, std::allocator, another_adl_serializer>; 663 664template <typename T, typename> 665struct another_adl_serializer 666{ 667 static void from_json(const custom_json& j, T& t) 668 { 669 using nlohmann::from_json; 670 from_json(j, t); 671 } 672 673 static void to_json(custom_json& j, const T& t) 674 { 675 using nlohmann::to_json; 676 to_json(j, t); 677 } 678}; 679 680TEST_CASE("custom serializer that does adl by default" * doctest::test_suite("udt")) 681{ 682 auto me = udt::person{{23}, {"theo"}, udt::country::france}; 683 684 json j = me; 685 custom_json cj = me; 686 687 CHECK(j.dump() == cj.dump()); 688 689 CHECK(me == j.get<udt::person>()); 690 CHECK(me == cj.get<udt::person>()); 691} 692 693TEST_CASE("different basic_json types conversions") 694{ 695 SECTION("null") 696 { 697 json j; 698 custom_json cj = j; 699 CHECK(cj == nullptr); 700 } 701 702 SECTION("boolean") 703 { 704 json j = true; 705 custom_json cj = j; 706 CHECK(cj == true); 707 } 708 709 SECTION("discarded") 710 { 711 json j(json::value_t::discarded); 712 custom_json cj; 713 CHECK_NOTHROW(cj = j); 714 CHECK(cj.type() == custom_json::value_t::discarded); 715 } 716 717 SECTION("array") 718 { 719 json j = {1, 2, 3}; 720 custom_json cj = j; 721 CHECK((cj == std::vector<int> {1, 2, 3})); 722 } 723 724 SECTION("integer") 725 { 726 json j = 42; 727 custom_json cj = j; 728 CHECK(cj == 42); 729 } 730 731 SECTION("float") 732 { 733 json j = 42.0; 734 custom_json cj = j; 735 CHECK(cj == 42.0); 736 } 737 738 SECTION("unsigned") 739 { 740 json j = 42u; 741 custom_json cj = j; 742 CHECK(cj == 42u); 743 } 744 745 SECTION("string") 746 { 747 json j = "forty-two"; 748 custom_json cj = j; 749 CHECK(cj == "forty-two"); 750 } 751 752 SECTION("binary") 753 { 754 json j = json::binary({1, 2, 3}, 42); 755 custom_json cj = j; 756 CHECK(cj.get_binary().subtype() == 42); 757 std::vector<std::uint8_t> cv = cj.get_binary(); 758 std::vector<std::uint8_t> v = j.get_binary(); 759 CHECK(cv == v); 760 } 761 762 SECTION("object") 763 { 764 json j = {{"forty", "two"}}; 765 custom_json cj = j; 766 auto m = j.get<std::map<std::string, std::string>>(); 767 CHECK(cj == m); 768 } 769 770 SECTION("get<custom_json>") 771 { 772 json j = 42; 773 custom_json cj = j.get<custom_json>(); 774 CHECK(cj == 42); 775 } 776} 777 778namespace 779{ 780struct incomplete; 781 782// std::is_constructible is broken on macOS' libc++ 783// use the cppreference implementation 784 785template <typename T, typename = void> 786struct is_constructible_patched : std::false_type {}; 787 788template <typename T> 789struct is_constructible_patched<T, decltype(void(json(std::declval<T>())))> : std::true_type {}; 790} // namespace 791 792TEST_CASE("an incomplete type does not trigger a compiler error in non-evaluated context" * doctest::test_suite("udt")) 793{ 794 static_assert(!is_constructible_patched<json, incomplete>::value, ""); 795} 796 797namespace 798{ 799class Evil 800{ 801 public: 802 Evil() = default; 803 template <typename T> 804 Evil(T t) : m_i(sizeof(t)) 805 { 806 static_cast<void>(t); // fix MSVC's C4100 warning 807 } 808 809 int m_i = 0; 810}; 811 812void from_json(const json& /*unused*/, Evil& /*unused*/) {} 813} // namespace 814 815TEST_CASE("Issue #924") 816{ 817 // Prevent get<std::vector<Evil>>() to throw 818 auto j = json::array(); 819 820 CHECK_NOTHROW(j.get<Evil>()); 821 CHECK_NOTHROW(j.get<std::vector<Evil>>()); 822 823 // silence Wunused-template warnings 824 Evil e(1); 825 CHECK(e.m_i >= 0); 826} 827 828TEST_CASE("Issue #1237") 829{ 830 struct non_convertible_type {}; 831 static_assert(!std::is_convertible<json, non_convertible_type>::value, ""); 832} 833 834namespace 835{ 836class no_iterator_type 837{ 838 public: 839 no_iterator_type(std::initializer_list<int> l) 840 : _v(l) 841 {} 842 843 std::vector<int>::const_iterator begin() const 844 { 845 return _v.begin(); 846 } 847 848 std::vector<int>::const_iterator end() const 849 { 850 return _v.end(); 851 } 852 853 private: 854 std::vector<int> _v; 855}; 856} // namespace 857 858TEST_CASE("compatible array type, without iterator type alias") 859{ 860 no_iterator_type vec{1, 2, 3}; 861 json j = vec; 862} 863 864DOCTEST_GCC_SUPPRESS_WARNING_POP 865