416 lines
11 KiB
C++
416 lines
11 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// @author Nicholas Ormrod <njormrod@fb.com>
|
|
|
|
#pragma once
|
|
|
|
#include <iterator>
|
|
#include <type_traits>
|
|
|
|
#include <boost/iterator/iterator_adaptor.hpp>
|
|
#include <boost/mpl/has_xxx.hpp>
|
|
|
|
#include <folly/Likely.h>
|
|
#include <folly/Optional.h>
|
|
#include <folly/Traits.h>
|
|
#include <folly/Utility.h>
|
|
#include <folly/dynamic.h>
|
|
#include <folly/lang/Exception.h>
|
|
|
|
namespace folly {
|
|
template <typename T>
|
|
T convertTo(const dynamic&);
|
|
template <typename T>
|
|
dynamic toDynamic(const T&);
|
|
} // namespace folly
|
|
|
|
/**
|
|
* convertTo returns a well-typed representation of the input dynamic.
|
|
*
|
|
* Example:
|
|
*
|
|
* dynamic d = dynamic::array(
|
|
* dynamic::array(1, 2, 3),
|
|
* dynamic::array(4, 5)); // a vector of vector of int
|
|
* auto vvi = convertTo<fbvector<fbvector<int>>>(d);
|
|
*
|
|
* See docs/DynamicConverter.md for supported types and customization
|
|
*/
|
|
|
|
namespace folly {
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// traits
|
|
|
|
namespace dynamicconverter_detail {
|
|
|
|
BOOST_MPL_HAS_XXX_TRAIT_DEF(value_type)
|
|
BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator)
|
|
BOOST_MPL_HAS_XXX_TRAIT_DEF(mapped_type)
|
|
BOOST_MPL_HAS_XXX_TRAIT_DEF(key_type)
|
|
|
|
template <typename T>
|
|
struct iterator_class_is_container {
|
|
typedef std::reverse_iterator<typename T::iterator> some_iterator;
|
|
enum {
|
|
value = has_value_type<T>::value &&
|
|
std::is_constructible<T, some_iterator, some_iterator>::value
|
|
};
|
|
};
|
|
|
|
template <typename T>
|
|
using class_is_container =
|
|
Conjunction<has_iterator<T>, iterator_class_is_container<T>>;
|
|
|
|
template <typename T>
|
|
using is_range = StrictConjunction<has_value_type<T>, has_iterator<T>>;
|
|
|
|
template <typename T>
|
|
using is_container = StrictConjunction<std::is_class<T>, class_is_container<T>>;
|
|
|
|
template <typename T>
|
|
using is_map = StrictConjunction<is_range<T>, has_mapped_type<T>>;
|
|
|
|
template <typename T>
|
|
using is_associative = StrictConjunction<is_range<T>, has_key_type<T>>;
|
|
|
|
} // namespace dynamicconverter_detail
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// custom iterators
|
|
|
|
/**
|
|
* We have iterators that dereference to dynamics, but need iterators
|
|
* that dereference to typename T.
|
|
*
|
|
* Implementation details:
|
|
* 1. We cache the value of the dereference operator. This is necessary
|
|
* because boost::iterator_adaptor requires *it to return a
|
|
* reference.
|
|
* 2. For const reasons, we cannot call operator= to refresh the
|
|
* cache: we must call the destructor then placement new.
|
|
*/
|
|
|
|
namespace dynamicconverter_detail {
|
|
|
|
template <typename T>
|
|
struct Dereferencer {
|
|
static inline void derefToCache(
|
|
Optional<T>* /* mem */,
|
|
const dynamic::const_item_iterator& /* it */) {
|
|
throw_exception<TypeError>("array", dynamic::Type::OBJECT);
|
|
}
|
|
|
|
static inline void derefToCache(
|
|
Optional<T>* mem,
|
|
const dynamic::const_iterator& it) {
|
|
mem->emplace(convertTo<T>(*it));
|
|
}
|
|
};
|
|
|
|
template <typename F, typename S>
|
|
struct Dereferencer<std::pair<F, S>> {
|
|
static inline void derefToCache(
|
|
Optional<std::pair<F, S>>* mem,
|
|
const dynamic::const_item_iterator& it) {
|
|
mem->emplace(convertTo<F>(it->first), convertTo<S>(it->second));
|
|
}
|
|
|
|
// Intentional duplication of the code in Dereferencer
|
|
template <typename T>
|
|
static inline void derefToCache(
|
|
Optional<T>* mem,
|
|
const dynamic::const_iterator& it) {
|
|
mem->emplace(convertTo<T>(*it));
|
|
}
|
|
};
|
|
|
|
template <typename T, typename It>
|
|
class Transformer
|
|
: public boost::
|
|
iterator_adaptor<Transformer<T, It>, It, typename T::value_type> {
|
|
friend class boost::iterator_core_access;
|
|
|
|
typedef typename T::value_type ttype;
|
|
|
|
mutable Optional<ttype> cache_;
|
|
|
|
void increment() {
|
|
++this->base_reference();
|
|
cache_ = none;
|
|
}
|
|
|
|
ttype& dereference() const {
|
|
if (!cache_) {
|
|
Dereferencer<ttype>::derefToCache(&cache_, this->base_reference());
|
|
}
|
|
return cache_.value();
|
|
}
|
|
|
|
public:
|
|
explicit Transformer(const It& it) : Transformer::iterator_adaptor_(it) {}
|
|
};
|
|
|
|
// conversion factory
|
|
template <typename T, typename It>
|
|
inline std::move_iterator<Transformer<T, It>> conversionIterator(const It& it) {
|
|
return std::make_move_iterator(Transformer<T, It>(it));
|
|
}
|
|
|
|
} // namespace dynamicconverter_detail
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// DynamicConverter specializations
|
|
|
|
/**
|
|
* Each specialization of DynamicConverter has the function
|
|
* 'static T convert(const dynamic&);'
|
|
*/
|
|
|
|
// default - intentionally unimplemented
|
|
template <typename T, typename Enable = void>
|
|
struct DynamicConverter;
|
|
|
|
// boolean
|
|
template <>
|
|
struct DynamicConverter<bool> {
|
|
static bool convert(const dynamic& d) {
|
|
return d.asBool();
|
|
}
|
|
};
|
|
|
|
// integrals
|
|
template <typename T>
|
|
struct DynamicConverter<
|
|
T,
|
|
typename std::enable_if<
|
|
std::is_integral<T>::value && !std::is_same<T, bool>::value>::type> {
|
|
static T convert(const dynamic& d) {
|
|
return folly::to<T>(d.asInt());
|
|
}
|
|
};
|
|
|
|
// enums
|
|
template <typename T>
|
|
struct DynamicConverter<
|
|
T,
|
|
typename std::enable_if<std::is_enum<T>::value>::type> {
|
|
static T convert(const dynamic& d) {
|
|
using type = typename std::underlying_type<T>::type;
|
|
return static_cast<T>(DynamicConverter<type>::convert(d));
|
|
}
|
|
};
|
|
|
|
// floating point
|
|
template <typename T>
|
|
struct DynamicConverter<
|
|
T,
|
|
typename std::enable_if<std::is_floating_point<T>::value>::type> {
|
|
static T convert(const dynamic& d) {
|
|
return folly::to<T>(d.asDouble());
|
|
}
|
|
};
|
|
|
|
// fbstring
|
|
template <>
|
|
struct DynamicConverter<folly::fbstring> {
|
|
static folly::fbstring convert(const dynamic& d) {
|
|
return d.asString();
|
|
}
|
|
};
|
|
|
|
// std::string
|
|
template <>
|
|
struct DynamicConverter<std::string> {
|
|
static std::string convert(const dynamic& d) {
|
|
return d.asString();
|
|
}
|
|
};
|
|
|
|
// std::pair
|
|
template <typename F, typename S>
|
|
struct DynamicConverter<std::pair<F, S>> {
|
|
static std::pair<F, S> convert(const dynamic& d) {
|
|
if (d.isArray() && d.size() == 2) {
|
|
return std::make_pair(convertTo<F>(d[0]), convertTo<S>(d[1]));
|
|
} else if (d.isObject() && d.size() == 1) {
|
|
auto it = d.items().begin();
|
|
return std::make_pair(convertTo<F>(it->first), convertTo<S>(it->second));
|
|
} else {
|
|
throw_exception<TypeError>("array (size 2) or object (size 1)", d.type());
|
|
}
|
|
}
|
|
};
|
|
|
|
// non-associative containers
|
|
template <typename C>
|
|
struct DynamicConverter<
|
|
C,
|
|
typename std::enable_if<
|
|
dynamicconverter_detail::is_container<C>::value &&
|
|
!dynamicconverter_detail::is_associative<C>::value>::type> {
|
|
static C convert(const dynamic& d) {
|
|
if (d.isArray()) {
|
|
return C(
|
|
dynamicconverter_detail::conversionIterator<C>(d.begin()),
|
|
dynamicconverter_detail::conversionIterator<C>(d.end()));
|
|
} else if (d.isObject()) {
|
|
return C(
|
|
dynamicconverter_detail::conversionIterator<C>(d.items().begin()),
|
|
dynamicconverter_detail::conversionIterator<C>(d.items().end()));
|
|
} else {
|
|
throw_exception<TypeError>("object or array", d.type());
|
|
}
|
|
}
|
|
};
|
|
|
|
// associative containers
|
|
template <typename C>
|
|
struct DynamicConverter<
|
|
C,
|
|
typename std::enable_if<
|
|
dynamicconverter_detail::is_container<C>::value &&
|
|
dynamicconverter_detail::is_associative<C>::value>::type> {
|
|
static C convert(const dynamic& d) {
|
|
C ret; // avoid direct initialization due to unordered_map's constructor
|
|
// causing memory corruption if the iterator throws an exception
|
|
if (d.isArray()) {
|
|
ret.insert(
|
|
dynamicconverter_detail::conversionIterator<C>(d.begin()),
|
|
dynamicconverter_detail::conversionIterator<C>(d.end()));
|
|
} else if (d.isObject()) {
|
|
ret.insert(
|
|
dynamicconverter_detail::conversionIterator<C>(d.items().begin()),
|
|
dynamicconverter_detail::conversionIterator<C>(d.items().end()));
|
|
} else {
|
|
throw_exception<TypeError>("object or array", d.type());
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// DynamicConstructor specializations
|
|
|
|
/**
|
|
* Each specialization of DynamicConstructor has the function
|
|
* 'static dynamic construct(const C&);'
|
|
*/
|
|
|
|
// default
|
|
template <typename C, typename Enable = void>
|
|
struct DynamicConstructor {
|
|
static dynamic construct(const C& x) {
|
|
return dynamic(x);
|
|
}
|
|
};
|
|
|
|
// identity
|
|
template <typename C>
|
|
struct DynamicConstructor<
|
|
C,
|
|
typename std::enable_if<std::is_same<C, dynamic>::value>::type> {
|
|
static dynamic construct(const C& x) {
|
|
return x;
|
|
}
|
|
};
|
|
|
|
// enums
|
|
template <typename C>
|
|
struct DynamicConstructor<
|
|
C,
|
|
typename std::enable_if<std::is_enum<C>::value>::type> {
|
|
static dynamic construct(const C& x) {
|
|
return dynamic(to_underlying(x));
|
|
}
|
|
};
|
|
|
|
// maps
|
|
template <typename C>
|
|
struct DynamicConstructor<
|
|
C,
|
|
typename std::enable_if<
|
|
!std::is_same<C, dynamic>::value &&
|
|
dynamicconverter_detail::is_map<C>::value>::type> {
|
|
static dynamic construct(const C& x) {
|
|
dynamic d = dynamic::object;
|
|
for (const auto& pair : x) {
|
|
d.insert(toDynamic(pair.first), toDynamic(pair.second));
|
|
}
|
|
return d;
|
|
}
|
|
};
|
|
|
|
// other ranges
|
|
template <typename C>
|
|
struct DynamicConstructor<
|
|
C,
|
|
typename std::enable_if<
|
|
!std::is_same<C, dynamic>::value &&
|
|
!dynamicconverter_detail::is_map<C>::value &&
|
|
!std::is_constructible<StringPiece, const C&>::value &&
|
|
dynamicconverter_detail::is_range<C>::value>::type> {
|
|
static dynamic construct(const C& x) {
|
|
dynamic d = dynamic::array;
|
|
for (const auto& item : x) {
|
|
d.push_back(toDynamic(item));
|
|
}
|
|
return d;
|
|
}
|
|
};
|
|
|
|
// pair
|
|
template <typename A, typename B>
|
|
struct DynamicConstructor<std::pair<A, B>, void> {
|
|
static dynamic construct(const std::pair<A, B>& x) {
|
|
dynamic d = dynamic::array;
|
|
d.push_back(toDynamic(x.first));
|
|
d.push_back(toDynamic(x.second));
|
|
return d;
|
|
}
|
|
};
|
|
|
|
// vector<bool>
|
|
template <>
|
|
struct DynamicConstructor<std::vector<bool>, void> {
|
|
static dynamic construct(const std::vector<bool>& x) {
|
|
dynamic d = dynamic::array;
|
|
// Intentionally specifying the type as bool here.
|
|
// std::vector<bool>'s iterators return a proxy which is a prvalue
|
|
// and hence cannot bind to an lvalue reference such as auto&
|
|
for (bool item : x) {
|
|
d.push_back(toDynamic(item));
|
|
}
|
|
return d;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// implementation
|
|
|
|
template <typename T>
|
|
T convertTo(const dynamic& d) {
|
|
return DynamicConverter<typename std::remove_cv<T>::type>::convert(d);
|
|
}
|
|
|
|
template <typename T>
|
|
dynamic toDynamic(const T& x) {
|
|
return DynamicConstructor<typename std::remove_cv<T>::type>::construct(x);
|
|
}
|
|
|
|
} // namespace folly
|