diff --git a/doc/qbk/02_1_values.qbk b/doc/qbk/02_1_values.qbk index 679d3867..a341a7ed 100644 --- a/doc/qbk/02_1_values.qbk +++ b/doc/qbk/02_1_values.qbk @@ -7,7 +7,7 @@ Official repository: https://github.com/vinniefalco/json ] -[section Values] +[section:values Using Values] JSON documents are stored in memory using instances of __value__: a __SemiRegular__ type which satisfies __DefaultConstructible__ diff --git a/doc/qbk/02_2_strings.qbk b/doc/qbk/02_2_strings.qbk index 9569e03a..a119c585 100644 --- a/doc/qbk/02_2_strings.qbk +++ b/doc/qbk/02_2_strings.qbk @@ -9,7 +9,7 @@ [/-----------------------------------------------------------------------------] -[section Strings] +[section:strings Using Strings] Modifiable sequences of characters are represented using instances of type [link json.ref.boost__json__string `string`]. diff --git a/doc/qbk/02_3_arrays.qbk b/doc/qbk/02_3_arrays.qbk index 70c042f1..7f5c7773 100644 --- a/doc/qbk/02_3_arrays.qbk +++ b/doc/qbk/02_3_arrays.qbk @@ -9,7 +9,7 @@ [/-----------------------------------------------------------------------------] -[section Arrays] +[section:arrays Using Arrays] A __value__ stores an instance of __array__ as the underlying representation for a JSON array. Instances of the __array__ type function identically to diff --git a/doc/qbk/02_4_objects.qbk b/doc/qbk/02_4_objects.qbk index 72bd1f98..689bbc2b 100644 --- a/doc/qbk/02_4_objects.qbk +++ b/doc/qbk/02_4_objects.qbk @@ -9,7 +9,7 @@ [/-----------------------------------------------------------------------------] -[section Objects] +[section:objects Using Objects] A __value__ stores an instance of __object__ as the underlying representation for a JSON object . Instances of the __object__ type are associative diff --git a/doc/qbk/02_5_numbers.qbk b/doc/qbk/02_5_numbers.qbk index 34492347..b3ab6f39 100644 --- a/doc/qbk/02_5_numbers.qbk +++ b/doc/qbk/02_5_numbers.qbk @@ -9,6 +9,6 @@ [/-----------------------------------------------------------------------------] -[section Numbers] +[section:numbers Using Numbers] [endsect] diff --git a/doc/qbk/02_9_exchange.qbk b/doc/qbk/02_9_exchange.qbk index 2048da05..acda5807 100644 --- a/doc/qbk/02_9_exchange.qbk +++ b/doc/qbk/02_9_exchange.qbk @@ -9,7 +9,80 @@ [/-----------------------------------------------------------------------------] -[section Exchange] +[section:exchange Value Exchange] + +While the __value__ container makes it easy to create ad-hoc structures, +often it is necessary to convert between JSON and specific user-defined +types. The library provides a set of supporting features called +"value exchange" to support this use case. Three mechanisms for +handling a user-defined type `T` are possible: + +* Using member functions: + * `void T::to_json( value& ) const` + * `void T::from_json( value const& )` + +* With free functions in the same namespace as `T`: + * `void to_json( T const&, value& )` + * `void from_json( T&, value const& )` + +* Specializing __value_exchange__ for `T`, with static member functions: + * `void value_exchange::to_json( T const&, value& )` and + * `void value_exchange::from_json( T&, value const& )` + +Only one of these mechanisms will be selected. If more than one is +available, the implementation prioritizes the specialization of +__value_exchange__ first, the free functions second, and the member +functions last. + +[heading Member Functions] + +The example code that follows uses the user-defined type `customer`, +declared thusly: + +[snippet_exchange_1] + +Since the author has control of the type, it uses member functions +to enable value exchange. An implementation of `to_json` may look +like this: + +[snippet_exchange_2] + +Turning a JSON value back into a customer is a bit more complicated, +as it must handle the case where the value does not have the necessary +keys, or if the values have the wrong kind. The implementation of +`from_json` throws an exception when the JSON value does not precisely +match the expected schema for customer: + +[snippet_exchange_3] + +Once value exchange is enabled for a type, it may be assigned and +stored: + +[snippet_exchange_4] + +[heading Trait Specialization] + +Sometimes value exchange is needed, but the author cannot modify the +type in question to add member functions. One alternative is to +specialize __value_exchange__ for the type and provide the necessary +conversion functions. Here we enable value exchange for __std_complex__: + +[snippet_exchange_5] + +The implementation for `to_json` is straightforward: + +[snippet_exchange_6] + +As before, the implementation for `from_json` must use exceptions +to indicate that the value does not have the right schema: + +[snippet_exchange_7] + +Conversion to and from __std_complex__ is now possible: + +[snippet_exchange_8] + +[heading Free Functions] [endsect] diff --git a/doc/qbk/02_usage.qbk b/doc/qbk/02_usage.qbk index 7e8b73b0..3f211587 100644 --- a/doc/qbk/02_usage.qbk +++ b/doc/qbk/02_usage.qbk @@ -9,15 +9,15 @@ [section Usage] -* [link json.usage.values Values] -* [link json.usage.strings Strings] -* [link json.usage.arrays Arrays] -* [link json.usage.objects Objects] -* [link json.usage.numbers Numbers] +* [link json.usage.values Using Values] +* [link json.usage.strings Using Strings] +* [link json.usage.arrays Using Arrays] +* [link json.usage.objects Using Objects] +* [link json.usage.numbers Using Numbers] * [link json.usage.storage Storage] * [link json.usage.parsing Parsing] * [link json.usage.serializing Serializing] -* [link json.usage.exchange Exchange] +* [link json.usage.exchange Value Exchange] [note diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index 4558794d..f952e6be 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -62,11 +62,13 @@ [def __storage_ptr__ [link json.ref.boost__json__storage_ptr `storage_ptr`]] [def __string__ [link json.ref.boost__json__string `string`]] [def __to_string__ [link json.ref.boost__json__to_string `to_string`]] +[def __value_exchange__ [link json.ref.boost__json__value_exchange `value_exchange`]] [def __initializer_list__ [@https://en.cppreference.com/w/cpp/utility/initializer_list]] [def __memory_resource__ [@https://en.cppreference.com/w/cpp/memory/memory_resource `std::pmr::memory_resource`]] [def __polymorphic_allocator__ [@https://en.cppreference.com/w/cpp/memory/polymorphic_allocator `std::pmr::polymorphic_allocator`]] [def __shared_ptr__ [@https://en.cppreference.com/w/cpp/memory/shared_ptr `std::shared_ptr`]] +[def __std_complex__ [@https://en.cppreference.com/w/cpp/numeric/complex `std::complex`]] [def __std_string__ [@https://en.cppreference.com/w/cpp/string/basic_string `std::string`]] [def __std_unordered_map__ [@https://en.cppreference.com/w/cpp/container/unordered_map `std::unordered_map`]] [def __std_vector__ [@https://en.cppreference.com/w/cpp/container/vector `std::vector`]] diff --git a/include/boost/json.hpp b/include/boost/json.hpp index d0979b78..bb5893ad 100644 --- a/include/boost/json.hpp +++ b/include/boost/json.hpp @@ -13,8 +13,6 @@ #include #include -#include -#include #include #include #include diff --git a/include/boost/json/assign_string.hpp b/include/boost/json/assign_string.hpp deleted file mode 100644 index 6da9413f..00000000 --- a/include/boost/json/assign_string.hpp +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/vinniefalco/json -// - -#ifndef BOOST_JSON_ASSIGN_STRING_HPP -#define BOOST_JSON_ASSIGN_STRING_HPP - -#include -#include -#include -#include -#include - -namespace boost { -namespace json { - -template -void -from_json( - std::basic_string< - char, - std::char_traits, - Allocator>& t, - value const& v) -{ - auto& s= v.as_string(); - t.assign(s.data(), s.size()); -} - -} // json -} // boost - -#endif diff --git a/include/boost/json/assign_vector.hpp b/include/boost/json/assign_vector.hpp deleted file mode 100644 index ee0521e8..00000000 --- a/include/boost/json/assign_vector.hpp +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/vinniefalco/json -// - -#ifndef BOOST_JSON_ASSIGN_VECTOR_HPP -#define BOOST_JSON_ASSIGN_VECTOR_HPP - -#include -#include -#include -#include -#include - -namespace boost { -namespace json { - -template::value>::type -> -void -from_json( - std::vector& t, - value const& v) -{ - auto& arr = v.as_array(); - t.resize(0); - t.resize(arr.size()); - auto it = t.begin(); - for(auto const& e : arr) - e.store(*it++); -} - -} // json -} // boost - -#endif diff --git a/include/boost/json/detail/config.hpp b/include/boost/json/detail/config.hpp index 3772b9ba..6fab3f1e 100644 --- a/include/boost/json/detail/config.hpp +++ b/include/boost/json/detail/config.hpp @@ -134,30 +134,9 @@ namespace boost { namespace json { #ifndef BOOST_JSON_STANDALONE - -/// The type of string view used by the library using string_view = boost::string_view; - -/// The type of error code used by the library -using error_code = boost::system::error_code; - -/// The type of system error thrown by the library -using system_error = boost::system::system_error; - -/// The type of error category used by the library -using error_category = boost::system::error_category; - -/// The type of error condition used by the library -using error_condition = boost::system::error_condition; - #else - -using error_code = std::error_code; -using error_category = std::error_category; -using error_condition = std::error_condition; using string_view = std::string_view; -using system_error = std::system_error; - #endif namespace detail { @@ -264,7 +243,6 @@ using remove_cr = typename remove_reference::type>::type; } // detail - } // json } // boost diff --git a/include/boost/json/src.hpp b/include/boost/json/src.hpp index 2c5b74a0..7fa3e571 100644 --- a/include/boost/json/src.hpp +++ b/include/boost/json/src.hpp @@ -12,8 +12,8 @@ /* -This file is meant to be included once, in a translation unit of -the program, with the macro BOOST_BEAST_SPLIT_COMPILATION defined. +This file is meant to be included once, +in a translation unit of the program. */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index abc5253a..a2fa58fb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,8 +27,6 @@ add_executable (json-tests parse-vectors.hpp main.cpp array.cpp - assign_string.cpp - assign_vector.cpp basic_parser.cpp error.cpp json.cpp diff --git a/test/Jamfile b/test/Jamfile index 8786eca6..f69c7ca0 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -26,8 +26,6 @@ else local SOURCES = array.cpp - assign_string.cpp - assign_vector.cpp basic_parser.cpp error.cpp json.cpp diff --git a/test/assign_string.cpp b/test/assign_string.cpp deleted file mode 100644 index bbb6705e..00000000 --- a/test/assign_string.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/vinniefalco/json -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace boost { -namespace json { - -class assign_string_test : public beast::unit_test::suite -{ -public: - void - testAssign() - { - #if ! defined(_MSC_VER) || _MSC_VER >= 1910 - value jv = "test"; - std::string s; - try - { - jv.store(s); - BEAST_EXPECT(s == "test"); - } - catch(std::exception const&) - { - BEAST_FAIL(); - } - #else - pass(); - #endif - } - - void - run() override - { - testAssign(); - } -}; - -BEAST_DEFINE_TESTSUITE(boost,json,assign_string); - -} // json -} // boost diff --git a/test/assign_vector.cpp b/test/assign_vector.cpp deleted file mode 100644 index e6188ffa..00000000 --- a/test/assign_vector.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/vinniefalco/json -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace boost { -namespace json { - -class assign_vector_test : public beast::unit_test::suite -{ -public: - void - testAssign() - { - #if ! defined(_MSC_VER) || _MSC_VER >= 1910 - BOOST_JSON_STATIC_ASSERT( - has_from_json::value); - BOOST_JSON_STATIC_ASSERT( - detail::has_adl_from_json>::value); - - value jv(array{}); - { - auto& a = jv.as_array(); - a.push_back(1); - a.push_back(2); - a.push_back(3); - } - std::vector v; - try - { - jv.store(v); - BEAST_EXPECT(v.size() == 3); - BEAST_EXPECT(v[0] == 1); - BEAST_EXPECT(v[1] == 2); - BEAST_EXPECT(v[2] == 3); - } - catch(std::exception const&) - { - BEAST_FAIL(); - } - #else - pass(); - #endif - } - - void - run() override - { - testAssign(); - } -}; - -BEAST_DEFINE_TESTSUITE(boost,json,assign_vector); - -} // json -} // boost diff --git a/test/snippets.cpp b/test/snippets.cpp index 5ad4a0e4..440fec0c 100644 --- a/test/snippets.cpp +++ b/test/snippets.cpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace boost { @@ -308,14 +309,191 @@ usingSerializing() //---------------------------------------------------------- +//[snippet_exchange_1 + +struct customer +{ + std::uint64_t id; + std::string name; + bool delinquent; + + void to_json( value& jv) const; + void from_json( value const& jv ); +}; + +//] + +BOOST_JSON_STATIC_ASSERT( + ::boost::json::detail::has_mf_to_json::value); + +BOOST_JSON_STATIC_ASSERT( + has_to_json::value); + +BOOST_JSON_STATIC_ASSERT( + has_from_json::value); + +//[snippet_exchange_2 + +void customer::to_json( value& jv ) const +{ + // Turn jv into an object + auto& obj = jv.emplace_object(); + + // Each field has its own key/value pair in the object + + obj.emplace( "id", this->id ); + obj.emplace( "name", string_view( this->name )); + obj.emplace( "delinquent", this->delinquent ); +} + +//] + +//[snippet_exchange_3 + +void customer::from_json( value const& jv ) +{ + // as_object() will throw if jv.kind() != kind::object + auto const& obj = jv.as_object(); + + // at() will throw if the key is not found, + // and as_uint64() will throw if the value is + // not an unsigned 64-bit integer. + this->id = obj.at( "id" ).as_uint64(); + + // as_string() will throw if jv.kind() != kind::string + this->name = std::string( string_view( obj.at( "name" ).as_string() ) ); + + // as_bool() will throw if kv.kind() != kind::bool + this->delinquent = obj.at( "delinquent" ).as_bool(); +} + +//] + +void +usingExchange1() +{ + //[snippet_exchange_4 + + customer cust{ 1, "John Doe", false }; + + // Convert customer to value + value jv = cust; + + // Store value in customer + customer cust2; + jv.store( cust2 ); + + //] +} + } // (anon) +} // json +} // boost +//[snippet_exchange_5 + +// Specializations of value_exchange must be +// declared in the namespace ::boost::json. +namespace boost { +namespace json { + +template<> +struct value_exchange< ::std::complex< double > > +{ + static void to_json( ::std::complex< double > const& t, value& jv ); + static void from_json( ::std::complex< double >& t, value const& jv ); +}; + +} // namespace json +} // namespace boost + +//] +namespace boost { +namespace json { + +BOOST_JSON_STATIC_ASSERT( + has_to_json>::value); + +BOOST_JSON_STATIC_ASSERT( + has_from_json>::value); + +//[snippet_exchange_6 + +void +value_exchange< ::std::complex< double > >:: +to_json( ::std::complex< double > const& t, value& jv ) +{ + // Store a complex number as a 2-element array + auto& arr = jv.emplace_array(); + + // Real part first + arr.emplace_back( t.real() ); + + // Imaginary part last + arr.emplace_back( t.imag() ); +} + +//] + +//[snippet_exchange_7] + +void +value_exchange< ::std::complex< double > >:: +from_json( ::std::complex< double >& t, value const& jv ) +{ + // as_array() throws if jv.kind() != kind::array + auto const& arr = jv.as_array(); + + // at() throws if index is out of range + // as_double() throws if kind() != kind::double_ + t.real( arr.at(0).as_double() ); + + // imaginary part last + t.imag( arr.at(1).as_double() ); +} + +//] + +namespace { + +void +usingExchange2() +{ + //[snippet_exchange_8 + + std::complex c = { 3.14159, 2.71727 }; + + // Convert std::complex to value + value jv = c; + + // Store value in std::complex + std::complex c2; + jv.store( c2 ); + + //] +} + +} // (anon) + +//---------------------------------------------------------- + class snippets_test : public beast::unit_test::suite { public: void run() override { + (void)&usingStrings; + (void)&usingArrays; + (void)&usingObjects; + (void)&usingStorage; + (void)&parse_fast; + (void)&do_json; + (void)&do_rpc; + (void)&usingParsing; + (void)&usingSerializing; + (void)&usingExchange1; + (void)&usingExchange2; pass(); } };