[/ Copyright (c) 2022 Dmitry Arkhipov (grisumbras@yandex.ru) 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/boostorg/json ] [/-----------------------------------------------------------------------------] [section Contextual conversions] Previously in this section we've been assuming that there is a particular fitting JSON representation for a type. But this is not always the case. Often one needs to represent particular value with JSON of certain format in one situation and with another format in a different situation. This can be achieved with Boost.JSON by providing an extra argument\U00002014context. Let's implement conversion from `user_ns::ip_address` to a JSON string: [doc_context_conversion_1] These `tag_invoke` overloads take an extra `as_string` parameter, which disambiguates this specific representation of `ip_address` from all other potential representations. In order to take advantage of them one needs to pass an `as_string` object to __value_from__ or __value_to__ as the last argument: [doc_context_conversion_2] Note, that if there is no dedicated `tag_invoke` overload for a given type and a given context, the implementation falls back to overloads without context. Thus it is easy to combine contextual conversions with conversions provided by the library: [doc_context_conversion_3] [heading Conversions for third-party types] Normally, you wouldn't be able to provide conversions for types from third-party libraries and standard types, because it would require yout to put `tag_invoke` overloads into namespaces you do not control. But with contexts you can put the overloads into your namespaces. This is because the context will add its associated namespaces into the list of namespaces where `tag_invoke` overloads are searched. As an example, let's implement conversion for [@https://en.cppreference.com/w/cpp/chrono/system_clock `std::chrono::system_clock::time_point`s] using [@https://en.wikipedia.org/wiki/ISO_8601 ISO 8601] format. [doc_context_conversion_4] Reverse conversion is left out for brevity. The new context is used in a similar fashion to `as_string` previously in this section. [doc_context_conversion_5] One particular use case that is enabled by contexts is adaptor libraries that define JSON conversion logic for types from a different library. [heading Passing data to conversion functions] Contexts we used so far were empty classes. But contexts may have data members and member functions just as any class. Implementers of conversion functions can take advantage of that to have conversions configurable at runtime or pass special objects to conversions (e.g. allocators). Let's rewrite conversion for `system_clock::time_point`s to allow any format supported by `std::strftime`. [doc_context_conversion_6] This `tag_invoke` overload lets us change date conversion format at runtime. Also note, that there is no ambiguity between `as_iso_8601` overload and `date_format` overload. You can use both in your program: [doc_context_conversion_7] [heading Combining contexts] Often it is needed to use several conversion contexts together. For example, consider a log of remote users identified by IP addresses accessing a system. We can represent it as `std::vector< std::pair >`. We want to serialize both `ip_address`es and `time_point`s as strings, but for this we need both `as_string` and `as_iso_8601` contexts. To combine several contexts just use `std::tuple`. Conversion functions will select the first element of the tuple for which a `tag_invoke` overload exists and will call that overload. As usual, `tag_invoke` overloads that don't use contexts and library-provided generic conversions are also supported. Thus, here's our example: [doc_context_conversion_8] In this snippet `time_point` is converted using `tag_invoke` overload that takes `as_iso_8601`, `ip_address` is converted using `tag_invoke` overload that takes `as_string`, and `std::vector` is converted using a generic conversion provided by the library. [heading Contexts and composite types] As was shown previously, generic conversions provided by the library forward contexts to conversions of nested objects. And in the case when you want to provide your own conversion function for a composite type enabled by a particular context, you usually also need to do that. Consider this example. As was discussed in a previous section, __is_map_like__ requires that your key type satisfies __is_string_like__. Now, let's say your keys are not string-like, but they do convert to __string__. You can make such maps to also convert to objects using a context. But if you want to also use another context for values, you need a way to pass the full combined context to map elements. So, you want the following test to succeed. [doc_context_conversion_9] For this you will have to use a different overload of `tag_invoke`. This time it has to be a function template, and it should have two parameters for contexts. The first parameter is the specific context that disambiguates that particular overload. The second parameter is the full context passed to __value_to__ or __value_from__. [doc_context_conversion_10] [endsect]