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