xref: /third_party/json/tests/src/unit-udt.cpp (revision c5f01b2f)
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