blob: 4c8b910cc466a3af4d03ccb40e455391c2ae1250 [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* 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>
#ifndef DYNAMIC_CONVERTER_H
#define DYNAMIC_CONVERTER_H
#include <folly/dynamic.h>
namespace folly {
template <typename T> T convertTo(const dynamic&);
template <typename T> dynamic toDynamic(const T&);
}
/**
* convertTo returns a well-typed representation of the input dynamic.
*
* Example:
*
* dynamic d = { { 1, 2, 3 }, { 4, 5 } }; // a vector of vector of int
* auto vvi = convertTo<fbvector<fbvector<int>>>(d);
*
* See docs/DynamicConverter.md for supported types and customization
*/
#include <type_traits>
#include <iterator>
#include <boost/iterator/iterator_adaptor.hpp>
#include <boost/mpl/has_xxx.hpp>
#include <folly/Likely.h>
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);
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 = typename
std::conditional<
has_iterator<T>::value,
iterator_class_is_container<T>,
std::false_type
>::type;
template <typename T> struct class_is_range {
enum { value = has_value_type<T>::value &&
has_iterator<T>::value };
};
template <typename T> struct is_container
: std::conditional<
std::is_class<T>::value,
class_is_container<T>,
std::false_type
>::type {};
template <typename T> struct is_range
: std::conditional<
std::is_class<T>::value,
class_is_range<T>,
std::false_type
>::type {};
template <typename T> struct is_map
: std::integral_constant<
bool,
is_range<T>::value && has_mapped_type<T>::value
> {};
} // 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(T* mem, const dynamic::const_item_iterator& it) {
throw TypeError("array", dynamic::Type::OBJECT);
}
static inline void derefToCache(T* mem, const dynamic::const_iterator& it) {
new (mem) T(convertTo<T>(*it));
}
};
template<typename F, typename S>
struct Dereferencer<std::pair<F, S>> {
static inline void
derefToCache(std::pair<F, S>* mem, const dynamic::const_item_iterator& it) {
new (mem) std::pair<F, S>(
convertTo<F>(it->first), convertTo<S>(it->second)
);
}
// Intentional duplication of the code in Dereferencer
template <typename T>
static inline void derefToCache(T* mem, const dynamic::const_iterator& it) {
new (mem) T(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 ttype cache_;
mutable bool valid_;
void increment() {
++this->base_reference();
valid_ = false;
}
ttype& dereference() const {
if (LIKELY(!valid_)) {
cache_.~ttype();
Dereferencer<ttype>::derefToCache(&cache_, this->base_reference());
valid_ = true;
}
return cache_;
}
public:
explicit Transformer(const It& it)
: Transformer::iterator_adaptor_(it), valid_(false) {}
};
// 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());
}
};
// 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().toStdString();
}
};
// 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 TypeError("array (size 2) or object (size 1)", d.type());
}
}
};
// containers
template <typename C>
struct DynamicConverter<C,
typename std::enable_if<
dynamicconverter_detail::is_container<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 TypeError("object or array", d.type());
}
}
};
///////////////////////////////////////////////////////////////////////////////
// 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);
}
};
// maps
template<typename C>
struct DynamicConstructor<C,
typename std::enable_if<
dynamicconverter_detail::is_map<C>::value>::type> {
static dynamic construct(const C& x) {
dynamic d = dynamic::object;
for (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<
!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 = {};
for (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 = {};
d.push_back(toDynamic(x.first));
d.push_back(toDynamic(x.second));
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
#endif // DYNAMIC_CONVERTER_H