[/
    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<std::chrono::system_clock::time_point, ip_address > >`. 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]
