mirror of
https://github.com/boostorg/json.git
synced 2026-01-19 04:12:14 +00:00
124 lines
5.3 KiB
Plaintext
124 lines
5.3 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
|
|
]
|
|
|
|
[/-----------------------------------------------------------------------------]
|
|
|
|
|
|
[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]
|