1 | /* |
2 | * Copyright 2012-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | // @author Nicholas Ormrod <njormrod@fb.com> |
18 | |
19 | #pragma once |
20 | |
21 | #include <iterator> |
22 | #include <type_traits> |
23 | |
24 | #include <boost/iterator/iterator_adaptor.hpp> |
25 | #include <boost/mpl/has_xxx.hpp> |
26 | |
27 | #include <folly/Likely.h> |
28 | #include <folly/Optional.h> |
29 | #include <folly/Traits.h> |
30 | #include <folly/dynamic.h> |
31 | #include <folly/lang/Exception.h> |
32 | |
33 | namespace folly { |
34 | template <typename T> |
35 | T convertTo(const dynamic&); |
36 | template <typename T> |
37 | dynamic toDynamic(const T&); |
38 | } // namespace folly |
39 | |
40 | /** |
41 | * convertTo returns a well-typed representation of the input dynamic. |
42 | * |
43 | * Example: |
44 | * |
45 | * dynamic d = dynamic::array( |
46 | * dynamic::array(1, 2, 3), |
47 | * dynamic::array(4, 5)); // a vector of vector of int |
48 | * auto vvi = convertTo<fbvector<fbvector<int>>>(d); |
49 | * |
50 | * See docs/DynamicConverter.md for supported types and customization |
51 | */ |
52 | |
53 | namespace folly { |
54 | |
55 | /////////////////////////////////////////////////////////////////////////////// |
56 | // traits |
57 | |
58 | namespace dynamicconverter_detail { |
59 | |
60 | BOOST_MPL_HAS_XXX_TRAIT_DEF(value_type) |
61 | BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator) |
62 | BOOST_MPL_HAS_XXX_TRAIT_DEF(mapped_type) |
63 | BOOST_MPL_HAS_XXX_TRAIT_DEF(key_type) |
64 | |
65 | template <typename T> |
66 | struct iterator_class_is_container { |
67 | typedef std::reverse_iterator<typename T::iterator> some_iterator; |
68 | enum { |
69 | value = has_value_type<T>::value && |
70 | std::is_constructible<T, some_iterator, some_iterator>::value |
71 | }; |
72 | }; |
73 | |
74 | template <typename T> |
75 | using class_is_container = |
76 | Conjunction<has_iterator<T>, iterator_class_is_container<T>>; |
77 | |
78 | template <typename T> |
79 | using is_range = StrictConjunction<has_value_type<T>, has_iterator<T>>; |
80 | |
81 | template <typename T> |
82 | using is_container = StrictConjunction<std::is_class<T>, class_is_container<T>>; |
83 | |
84 | template <typename T> |
85 | using is_map = StrictConjunction<is_range<T>, has_mapped_type<T>>; |
86 | |
87 | template <typename T> |
88 | using is_associative = StrictConjunction<is_range<T>, has_key_type<T>>; |
89 | |
90 | } // namespace dynamicconverter_detail |
91 | |
92 | /////////////////////////////////////////////////////////////////////////////// |
93 | // custom iterators |
94 | |
95 | /** |
96 | * We have iterators that dereference to dynamics, but need iterators |
97 | * that dereference to typename T. |
98 | * |
99 | * Implementation details: |
100 | * 1. We cache the value of the dereference operator. This is necessary |
101 | * because boost::iterator_adaptor requires *it to return a |
102 | * reference. |
103 | * 2. For const reasons, we cannot call operator= to refresh the |
104 | * cache: we must call the destructor then placement new. |
105 | */ |
106 | |
107 | namespace dynamicconverter_detail { |
108 | |
109 | template <typename T> |
110 | struct Dereferencer { |
111 | static inline void derefToCache( |
112 | Optional<T>* /* mem */, |
113 | const dynamic::const_item_iterator& /* it */) { |
114 | throw_exception<TypeError>("array" , dynamic::Type::OBJECT); |
115 | } |
116 | |
117 | static inline void derefToCache( |
118 | Optional<T>* mem, |
119 | const dynamic::const_iterator& it) { |
120 | mem->emplace(convertTo<T>(*it)); |
121 | } |
122 | }; |
123 | |
124 | template <typename F, typename S> |
125 | struct Dereferencer<std::pair<F, S>> { |
126 | static inline void derefToCache( |
127 | Optional<std::pair<F, S>>* mem, |
128 | const dynamic::const_item_iterator& it) { |
129 | mem->emplace(convertTo<F>(it->first), convertTo<S>(it->second)); |
130 | } |
131 | |
132 | // Intentional duplication of the code in Dereferencer |
133 | template <typename T> |
134 | static inline void derefToCache( |
135 | Optional<T>* mem, |
136 | const dynamic::const_iterator& it) { |
137 | mem->emplace(convertTo<T>(*it)); |
138 | } |
139 | }; |
140 | |
141 | template <typename T, typename It> |
142 | class Transformer |
143 | : public boost:: |
144 | iterator_adaptor<Transformer<T, It>, It, typename T::value_type> { |
145 | friend class boost::iterator_core_access; |
146 | |
147 | typedef typename T::value_type ttype; |
148 | |
149 | mutable Optional<ttype> cache_; |
150 | |
151 | void increment() { |
152 | ++this->base_reference(); |
153 | cache_ = none; |
154 | } |
155 | |
156 | ttype& dereference() const { |
157 | if (!cache_) { |
158 | Dereferencer<ttype>::derefToCache(&cache_, this->base_reference()); |
159 | } |
160 | return cache_.value(); |
161 | } |
162 | |
163 | public: |
164 | explicit Transformer(const It& it) : Transformer::iterator_adaptor_(it) {} |
165 | }; |
166 | |
167 | // conversion factory |
168 | template <typename T, typename It> |
169 | inline std::move_iterator<Transformer<T, It>> conversionIterator(const It& it) { |
170 | return std::make_move_iterator(Transformer<T, It>(it)); |
171 | } |
172 | |
173 | } // namespace dynamicconverter_detail |
174 | |
175 | /////////////////////////////////////////////////////////////////////////////// |
176 | // DynamicConverter specializations |
177 | |
178 | /** |
179 | * Each specialization of DynamicConverter has the function |
180 | * 'static T convert(const dynamic&);' |
181 | */ |
182 | |
183 | // default - intentionally unimplemented |
184 | template <typename T, typename Enable = void> |
185 | struct DynamicConverter; |
186 | |
187 | // boolean |
188 | template <> |
189 | struct DynamicConverter<bool> { |
190 | static bool convert(const dynamic& d) { |
191 | return d.asBool(); |
192 | } |
193 | }; |
194 | |
195 | // integrals |
196 | template <typename T> |
197 | struct DynamicConverter< |
198 | T, |
199 | typename std::enable_if< |
200 | std::is_integral<T>::value && !std::is_same<T, bool>::value>::type> { |
201 | static T convert(const dynamic& d) { |
202 | return folly::to<T>(d.asInt()); |
203 | } |
204 | }; |
205 | |
206 | // enums |
207 | template <typename T> |
208 | struct DynamicConverter< |
209 | T, |
210 | typename std::enable_if<std::is_enum<T>::value>::type> { |
211 | static T convert(const dynamic& d) { |
212 | using type = typename std::underlying_type<T>::type; |
213 | return static_cast<T>(DynamicConverter<type>::convert(d)); |
214 | } |
215 | }; |
216 | |
217 | // floating point |
218 | template <typename T> |
219 | struct DynamicConverter< |
220 | T, |
221 | typename std::enable_if<std::is_floating_point<T>::value>::type> { |
222 | static T convert(const dynamic& d) { |
223 | return folly::to<T>(d.asDouble()); |
224 | } |
225 | }; |
226 | |
227 | // fbstring |
228 | template <> |
229 | struct DynamicConverter<folly::fbstring> { |
230 | static folly::fbstring convert(const dynamic& d) { |
231 | return d.asString(); |
232 | } |
233 | }; |
234 | |
235 | // std::string |
236 | template <> |
237 | struct DynamicConverter<std::string> { |
238 | static std::string convert(const dynamic& d) { |
239 | return d.asString(); |
240 | } |
241 | }; |
242 | |
243 | // std::pair |
244 | template <typename F, typename S> |
245 | struct DynamicConverter<std::pair<F, S>> { |
246 | static std::pair<F, S> convert(const dynamic& d) { |
247 | if (d.isArray() && d.size() == 2) { |
248 | return std::make_pair(convertTo<F>(d[0]), convertTo<S>(d[1])); |
249 | } else if (d.isObject() && d.size() == 1) { |
250 | auto it = d.items().begin(); |
251 | return std::make_pair(convertTo<F>(it->first), convertTo<S>(it->second)); |
252 | } else { |
253 | throw_exception<TypeError>("array (size 2) or object (size 1)" , d.type()); |
254 | } |
255 | } |
256 | }; |
257 | |
258 | // non-associative containers |
259 | template <typename C> |
260 | struct DynamicConverter< |
261 | C, |
262 | typename std::enable_if< |
263 | dynamicconverter_detail::is_container<C>::value && |
264 | !dynamicconverter_detail::is_associative<C>::value>::type> { |
265 | static C convert(const dynamic& d) { |
266 | if (d.isArray()) { |
267 | return C( |
268 | dynamicconverter_detail::conversionIterator<C>(d.begin()), |
269 | dynamicconverter_detail::conversionIterator<C>(d.end())); |
270 | } else if (d.isObject()) { |
271 | return C( |
272 | dynamicconverter_detail::conversionIterator<C>(d.items().begin()), |
273 | dynamicconverter_detail::conversionIterator<C>(d.items().end())); |
274 | } else { |
275 | throw_exception<TypeError>("object or array" , d.type()); |
276 | } |
277 | } |
278 | }; |
279 | |
280 | // associative containers |
281 | template <typename C> |
282 | struct DynamicConverter< |
283 | C, |
284 | typename std::enable_if< |
285 | dynamicconverter_detail::is_container<C>::value && |
286 | dynamicconverter_detail::is_associative<C>::value>::type> { |
287 | static C convert(const dynamic& d) { |
288 | C ret; // avoid direct initialization due to unordered_map's constructor |
289 | // causing memory corruption if the iterator throws an exception |
290 | if (d.isArray()) { |
291 | ret.insert( |
292 | dynamicconverter_detail::conversionIterator<C>(d.begin()), |
293 | dynamicconverter_detail::conversionIterator<C>(d.end())); |
294 | } else if (d.isObject()) { |
295 | ret.insert( |
296 | dynamicconverter_detail::conversionIterator<C>(d.items().begin()), |
297 | dynamicconverter_detail::conversionIterator<C>(d.items().end())); |
298 | } else { |
299 | throw_exception<TypeError>("object or array" , d.type()); |
300 | } |
301 | return ret; |
302 | } |
303 | }; |
304 | |
305 | /////////////////////////////////////////////////////////////////////////////// |
306 | // DynamicConstructor specializations |
307 | |
308 | /** |
309 | * Each specialization of DynamicConstructor has the function |
310 | * 'static dynamic construct(const C&);' |
311 | */ |
312 | |
313 | // default |
314 | template <typename C, typename Enable = void> |
315 | struct DynamicConstructor { |
316 | static dynamic construct(const C& x) { |
317 | return dynamic(x); |
318 | } |
319 | }; |
320 | |
321 | // identity |
322 | template <typename C> |
323 | struct DynamicConstructor< |
324 | C, |
325 | typename std::enable_if<std::is_same<C, dynamic>::value>::type> { |
326 | static dynamic construct(const C& x) { |
327 | return x; |
328 | } |
329 | }; |
330 | |
331 | // maps |
332 | template <typename C> |
333 | struct DynamicConstructor< |
334 | C, |
335 | typename std::enable_if< |
336 | !std::is_same<C, dynamic>::value && |
337 | dynamicconverter_detail::is_map<C>::value>::type> { |
338 | static dynamic construct(const C& x) { |
339 | dynamic d = dynamic::object; |
340 | for (const auto& pair : x) { |
341 | d.insert(toDynamic(pair.first), toDynamic(pair.second)); |
342 | } |
343 | return d; |
344 | } |
345 | }; |
346 | |
347 | // other ranges |
348 | template <typename C> |
349 | struct DynamicConstructor< |
350 | C, |
351 | typename std::enable_if< |
352 | !std::is_same<C, dynamic>::value && |
353 | !dynamicconverter_detail::is_map<C>::value && |
354 | !std::is_constructible<StringPiece, const C&>::value && |
355 | dynamicconverter_detail::is_range<C>::value>::type> { |
356 | static dynamic construct(const C& x) { |
357 | dynamic d = dynamic::array; |
358 | for (const auto& item : x) { |
359 | d.push_back(toDynamic(item)); |
360 | } |
361 | return d; |
362 | } |
363 | }; |
364 | |
365 | // pair |
366 | template <typename A, typename B> |
367 | struct DynamicConstructor<std::pair<A, B>, void> { |
368 | static dynamic construct(const std::pair<A, B>& x) { |
369 | dynamic d = dynamic::array; |
370 | d.push_back(toDynamic(x.first)); |
371 | d.push_back(toDynamic(x.second)); |
372 | return d; |
373 | } |
374 | }; |
375 | |
376 | // vector<bool> |
377 | template <> |
378 | struct DynamicConstructor<std::vector<bool>, void> { |
379 | static dynamic construct(const std::vector<bool>& x) { |
380 | dynamic d = dynamic::array; |
381 | // Intentionally specifying the type as bool here. |
382 | // std::vector<bool>'s iterators return a proxy which is a prvalue |
383 | // and hence cannot bind to an lvalue reference such as auto& |
384 | for (bool item : x) { |
385 | d.push_back(toDynamic(item)); |
386 | } |
387 | return d; |
388 | } |
389 | }; |
390 | |
391 | /////////////////////////////////////////////////////////////////////////////// |
392 | // implementation |
393 | |
394 | template <typename T> |
395 | T convertTo(const dynamic& d) { |
396 | return DynamicConverter<typename std::remove_cv<T>::type>::convert(d); |
397 | } |
398 | |
399 | template <typename T> |
400 | dynamic toDynamic(const T& x) { |
401 | return DynamicConstructor<typename std::remove_cv<T>::type>::construct(x); |
402 | } |
403 | |
404 | } // namespace folly |
405 | |