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
33namespace folly {
34template <typename T>
35T convertTo(const dynamic&);
36template <typename T>
37dynamic 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
53namespace folly {
54
55///////////////////////////////////////////////////////////////////////////////
56// traits
57
58namespace dynamicconverter_detail {
59
60BOOST_MPL_HAS_XXX_TRAIT_DEF(value_type)
61BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator)
62BOOST_MPL_HAS_XXX_TRAIT_DEF(mapped_type)
63BOOST_MPL_HAS_XXX_TRAIT_DEF(key_type)
64
65template <typename T>
66struct 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
74template <typename T>
75using class_is_container =
76 Conjunction<has_iterator<T>, iterator_class_is_container<T>>;
77
78template <typename T>
79using is_range = StrictConjunction<has_value_type<T>, has_iterator<T>>;
80
81template <typename T>
82using is_container = StrictConjunction<std::is_class<T>, class_is_container<T>>;
83
84template <typename T>
85using is_map = StrictConjunction<is_range<T>, has_mapped_type<T>>;
86
87template <typename T>
88using 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
107namespace dynamicconverter_detail {
108
109template <typename T>
110struct 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
124template <typename F, typename S>
125struct 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
141template <typename T, typename It>
142class 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
168template <typename T, typename It>
169inline 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
184template <typename T, typename Enable = void>
185struct DynamicConverter;
186
187// boolean
188template <>
189struct DynamicConverter<bool> {
190 static bool convert(const dynamic& d) {
191 return d.asBool();
192 }
193};
194
195// integrals
196template <typename T>
197struct 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
207template <typename T>
208struct 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
218template <typename T>
219struct 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
228template <>
229struct DynamicConverter<folly::fbstring> {
230 static folly::fbstring convert(const dynamic& d) {
231 return d.asString();
232 }
233};
234
235// std::string
236template <>
237struct DynamicConverter<std::string> {
238 static std::string convert(const dynamic& d) {
239 return d.asString();
240 }
241};
242
243// std::pair
244template <typename F, typename S>
245struct 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
259template <typename C>
260struct 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
281template <typename C>
282struct 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
314template <typename C, typename Enable = void>
315struct DynamicConstructor {
316 static dynamic construct(const C& x) {
317 return dynamic(x);
318 }
319};
320
321// identity
322template <typename C>
323struct 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
332template <typename C>
333struct 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
348template <typename C>
349struct 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
366template <typename A, typename B>
367struct 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>
377template <>
378struct 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
394template <typename T>
395T convertTo(const dynamic& d) {
396 return DynamicConverter<typename std::remove_cv<T>::type>::convert(d);
397}
398
399template <typename T>
400dynamic toDynamic(const T& x) {
401 return DynamicConstructor<typename std::remove_cv<T>::type>::construct(x);
402}
403
404} // namespace folly
405