JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
enum_adapter.hpp
Go to the documentation of this file.
1/// \file jsonv/serialization/enum_adapter.hpp
2///
3/// Copyright (c) 2015-2026 by Travis Gockel. All rights reserved.
4///
5/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
6/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
7/// version.
8///
9/// \author Travis Gockel (travis@gockelhut.com)
10#pragma once
11
12#include <jsonv/ast.hpp>
13#include <jsonv/config.hpp>
14#include <jsonv/functional.hpp>
15#include <jsonv/reader.hpp>
17
18#include <expected>
19#include <functional>
20#include <initializer_list>
21#include <map>
22#include <string>
23#include <string_view>
24#include <utility>
25
26#include "adapter_for.hpp"
27
28namespace jsonv
29{
30
31namespace detail
32{
33
34/// Where a \c kind::string holding some text sorts against a \c value under \c value_less.
35struct enum_text_order
36{
38 static int compare(const value& a, std::string_view b)
39 {
40 return compare_text(a, b);
41 }
42};
43
44/// Where a \c kind::string holding some text sorts against a \c value under \c value_less_icase.
45struct enum_text_order_icase
46{
48 static int compare(const value& a, std::string_view b)
49 {
50 return compare_text_icase(a, b);
51 }
52};
53
54/// Orders the JSON side of an \c enum_adapter mapping by \c FValueComp, and places the text of a string among it by
55/// \c TTextOrder -- which has to be where \c FValueComp would place a \c value holding that string, or a lookup could
56/// miss what the table holds. That is what lets a string read from JSON text be looked up without building a \c value
57/// to hold it.
58template <typename FValueComp, typename TTextOrder>
59struct enum_value_less
60{
61 using is_transparent = void;
62
64 bool operator()(const value& a, const value& b) const
65 {
66 return FValueComp()(a, b);
67 }
68
70 bool operator()(const value& a, std::string_view b) const
71 {
72 return TTextOrder::compare(a, b) < 0;
73 }
74
76 bool operator()(std::string_view a, const value& b) const
77 {
78 // Reversed rather than negated: a comparison may return INT_MIN, which has no negation.
79 return TTextOrder::compare(b, a) > 0;
80 }
81};
82
83/// How an \c enum_adapter orders its mapping, given the \c FValueComp it was asked for. An ordering the library does not
84/// know can only be asked about a \c value, so it is used as it is, and a string read from JSON text is built into one
85/// to be looked up. A transparent one is no exception: <tt>std::less&lt;&gt;</tt> cannot place a \c std::string_view
86/// before a \c value.
87template <typename FValueComp>
88struct enum_value_order
89{
90 using type = FValueComp;
91
92 static constexpr bool reads_text = false;
93};
94
95template <>
96struct enum_value_order<std::less<value>>
97{
98 using type = enum_value_less<std::less<value>, enum_text_order>;
99
100 static constexpr bool reads_text = true;
101};
102
103template <>
104struct enum_value_order<value_less>
105{
106 using type = enum_value_less<value_less, enum_text_order>;
107
108 static constexpr bool reads_text = true;
109};
110
111template <>
112struct enum_value_order<value_less_icase>
113{
114 using type = enum_value_less<value_less_icase, enum_text_order_icase>;
115
116 static constexpr bool reads_text = true;
117};
118
119}
120
121/// \addtogroup Serialization
122/// \{
123
124/// An adapter for enumeration types. The most common use of this is to map \c enum values in C++ to string values in a
125/// JSON representation (and vice versa).
126///
127/// Extraction reads the \c reader directly. A value the mapping does not hold is refused with the reader still on it,
128/// and the problem names the value and lists every JSON value the mapping accepts, in the mapping's order:
129///
130/// \code
131/// Invalid value for ring: "bogus" (expected one of "earth", "fire", "heart", "useless", "water", "wind")
132/// \endcode
133///
134/// \tparam TEnum The type to map. This is not restricted to C++ enumerations (types defined with the \c enum keyword),
135/// but any type you wish to restrict to a subset of values.
136/// \tparam FEnumComp <tt>bool (*)(TEnum, TEnum)</tt> -- a strict ordering for \c TEnum values.
137/// \tparam FValueComp <tt>bool (*)(value, value)</tt> -- a strict ordering for \c value objects. By default, this is a
138/// case-sensitive comparison, but this can be replaced with anything you desire (for example, use
139/// \c value_less_icase to ignore case in extracting from JSON). Under the library's own orderings
140/// -- <tt>std::less&lt;value&gt;</tt>, \c value_less and \c value_less_icase -- a string read from
141/// JSON text is looked up by its text, with no \c value built to hold it. Any other ordering can
142/// only be asked about a \c value, so one is built for every string extracted from text.
143///
144/// \see enum_adapter_icase
145template <typename TEnum,
146 typename FEnumComp = std::less<TEnum>,
147 typename FValueComp = std::less<value>
148 >
150 public adapter_for<TEnum>
151{
152public:
153 /// Create an adapter with mapping values from the range <tt>[first, last)</tt>.
154 ///
155 /// \tparam TForwardIterator An iterator yielding the type <tt>std::pair&lt;TEnum, jsonv::value&gt;</tt>
156 template <typename TForwardIterator>
157 explicit enum_adapter(std::string enum_name, TForwardIterator first, TForwardIterator last) :
158 _enum_name(std::move(enum_name))
159 {
160 for (auto iter = first; iter != last; ++iter)
161 {
162 _val_to_cpp.insert({ iter->second, iter->first });
163 _cpp_to_val.insert(*iter);
164 }
165 }
166
167 /// Create an adapter with the specified \a mapping values.
168 ///
169 /// \param enum_name A user-friendly name for this enumeration to be used in error messages.
170 /// \param mapping A list of C++ types and values to use in \c to_json and \c extract. It is okay to have a C++
171 /// value with more than one JSON representation. In this case, the \e first JSON representation will
172 /// be used in \c to_json, but \e all JSON representations will be interpreted as the C++ value. It
173 /// is also okay to have the same JSON representation for multiple C++ values. In this case, the
174 /// \e first JSON representation provided for that value will be used in \c extract.
175 ///
176 /// For example:
177 ///
178 /// \code
179 /// enum_adapter<ring>("ring",
180 /// {
181 /// { ring::fire, "fire" },
182 /// { ring::wind, "wind" },
183 /// { ring::earth, "earth" },
184 /// { ring::water, "water" },
185 /// { ring::heart, "heart" }, // "heart" is preferred for to_json
186 /// { ring::heart, "useless" }, // "useless" is interpreted as ring::heart in extract
187 /// }
188 /// );
189 /// \endcode
190 explicit enum_adapter(std::string enum_name, std::initializer_list<std::pair<TEnum, value>> mapping) :
191 enum_adapter(std::move(enum_name), mapping.begin(), mapping.end())
192 { }
193
194protected:
196 virtual std::expected<TEnum, ast_node_type> create(extraction_context& context, reader& from) const override
197 {
198 // A value-backed reader lends the value itself, which is looked up where it sits.
199 if (auto lent = from.current_value())
200 return settle(context, from, *lent);
201
202 if constexpr (value_order::reads_text)
203 {
204 // An unescaped string is looked up by a view of the source, so nothing is built for it. One written with
205 // escapes has no contiguous form to compare against until it is decoded.
206 if (from.good() && from.current_type() == ast_node_type::string_canonical)
207 return settle(context, from, from.current().as<ast_node::string_canonical>().value());
208
209 if (from.good() && from.current_type() == ast_node_type::string_escaped)
210 {
211 const std::string decoded = from.current().as<ast_node::string_escaped>().value();
212 return settle(context, from, std::string_view(decoded));
213 }
214 }
215
216 // Anything else is built as a `value` without moving the reader -- a scalar where it sits, a structure through a
217 // second cursor -- so a miss leaves this one on the value it is about.
218 return settle(context, from, detail::peek_value(context, from));
219 }
220
222 virtual value to_json(const serialization_context&, const TEnum& from) const override
223 {
224 using std::end;
225
226 auto iter = _cpp_to_val.find(from);
227 if (iter != end(_cpp_to_val))
228 return iter->second;
229 else
230 return null;
231 }
232
233private:
234 using value_order = detail::enum_value_order<FValueComp>;
235
236 /// Look up \a key -- a \c value, or the text of a string -- and step \a from past the value it was read from.
237 template <typename TKey>
238 std::expected<TEnum, ast_node_type> settle(extraction_context& context, reader& from, const TKey& key) const
239 {
240 auto iter = _val_to_cpp.find(key);
241 if (iter == _val_to_cpp.end())
242 {
243 // The cursor is still on the value, which is where a problem about it belongs and what whoever recovers
244 // from it expects to step over. Text is described as the string `value` it would have been, so the
245 // message spells it as JSON.
246 return context.problem(context.problem_path(from), describe_miss(value(key)));
247 }
248
249 // Copied while the cursor is still on the value, so a `TEnum` which refuses to be copied fails with the value in
250 // front of it. Moving it out is the last thing which can fail, and by then the value is behind the cursor.
251 TEnum out = iter->second;
252 (void) from.next_value();
253 try
254 {
255 return out;
256 }
257 catch (...)
258 {
259 context.note_value_consumed(from);
260 throw;
261 }
262 }
263
264 /// The message a miss on \a found is reported with: the value as JSON, then every one the mapping would accept.
265 std::string describe_miss(const value& found) const
266 {
267 std::string message = "Invalid value for " + _enum_name + ": " + to_string(found);
268 if (!_val_to_cpp.empty())
269 {
270 const char* separator = " (expected one of ";
271 for (const auto& accepted : _val_to_cpp)
272 {
273 message += separator;
274 message += to_string(accepted.first);
275 separator = ", ";
276 }
277 message += ')';
278 }
279 return message;
280 }
281
282private:
283 std::string _enum_name;
284 std::map<value, TEnum, typename value_order::type> _val_to_cpp;
285 std::map<TEnum, value, FEnumComp> _cpp_to_val;
286};
287
288/// An adapter for enumeration types which ignores the case when extracting from JSON.
289///
290/// \see enum_adapter
291template <typename TEnum, typename FEnumComp = std::less<TEnum>>
293
294/// \}
295
296}
Copyright (c) 2015-2026 by Travis Gockel.
Utilities for directly dealing with a JSON AST.
An adapter for the type T.
const T & as() const
Get the underlying data of this node as T.
Definition ast.hpp:538
Provides extra information to routines used for extraction and serialization.
Definition context.hpp:26
An adapter for enumeration types.
virtual std::expected< TEnum, ast_node_type > create(extraction_context &context, reader &from) const override
Create an instance of T by reading from.
enum_adapter(std::string enum_name, TForwardIterator first, TForwardIterator last)
Create an adapter with mapping values from the range [first, last).
enum_adapter(std::string enum_name, std::initializer_list< std::pair< TEnum, value > > mapping)
Create an adapter with the specified mapping values.
Provides extra information to routines used for extraction, collects the problems they encounter,...
Definition extract.hpp:391
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:104
ast_node_type current_type() const
Get the type of the current AST node, which is always current().type().
const ast_node & current() const
Get the current AST node this reader is pointing at.
optional< const value & > current_value() const noexcept
Get the in-memory value this reader is positioned on, if it has one to lend.
bool good() const
Check if this reader is still good to read from.
Represents a single JSON value, which can be any one of a potential kind, each behaving slightly diff...
Definition value.hpp:113
Copyright (c) 2014-2020 by Travis Gockel.
A collection of function objects a la &lt;functional&gt;.
int compare(const value &a, const value &b, const TCompareTraits &traits)
Compare the values a and b using the comparison traits.
#define JSONV_NODISCARD
Warn if the caller discards the result of this function.
Definition config.hpp:132
JSONV_PUBLIC std::string to_string(const ast_node_type &type)
Get what operator<< writes for type as a std::string.
JSONV_PUBLIC const value null
An instance with kind::null.
STL namespace.
Read a JSON AST.
Conversion between C++ types and JSON values.