2
0
mirror of https://github.com/boostorg/json.git synced 2026-01-23 17:42:51 +00:00
Files
json/doc/pages/conversion/context.adoc
2025-06-18 15:12:04 +03:00

151 lines
5.8 KiB
Plaintext

////
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
////
= 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---context.
Let's implement conversion from `user_ns::ip_address` to a JSON string:
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_1,indent=0]
----
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 <<ref_value_from>> or <<ref_value_to>> as the last
argument:
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_2,indent=0]
----
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:
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_3,indent=0]
----
== 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.
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_4,indent=0]
----
Reverse conversion is left out for brevity.
The new context is used in a similar fashion to `as_string` previously in this
section.
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_5,indent=0]
----
One particular use case that is enabled by contexts is adaptor libraries that
define JSON conversion logic for types from a different library.
== 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`.
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_6,indent=0]
----
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:
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_7,indent=0]
----
== 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:
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_8,indent=0]
----
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.
== 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,
<<ref_is_map_like>> requires that your key type satisfies
<<ref_is_string_like>>. Now, let's say your keys are not string-like, but they
do convert to <<ref_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.
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_9,indent=0]
----
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
<<ref_value_to>> or <<ref_value_from>>.
[source]
----
include::../../../test/snippets.cpp[tag=doc_context_conversion_10,indent=0]
----