mirror of
https://github.com/boostorg/json.git
synced 2026-01-23 17:42:51 +00:00
151 lines
5.8 KiB
Plaintext
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]
|
|
----
|