--- /dev/null
+/*
+ * Copyright 2012 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&);
+}
+
+/**
+ * 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 <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(key_type);
+BOOST_MPL_HAS_XXX_TRAIT_DEF(mapped_type);
+BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator);
+
+template <typename T> struct map_container_has_correct_types
+ : std::is_same<std::pair<typename std::add_const<typename T::key_type>::type,
+ typename T::mapped_type>,
+ typename T::value_type> {};
+
+template <typename T> struct class_is_container {
+ struct dummy {};
+ enum { value = has_value_type<T>::value &&
+ has_iterator<T>::value &&
+ std::is_constructible<T, dummy, dummy>::value };
+};
+
+template <typename T> struct container_is_map
+ : std::conditional<
+ has_key_type<T>::value && has_mapped_type<T>::value,
+ map_container_has_correct_types<T>,
+ std::false_type
+ >::type {};
+
+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_map_container
+ : std::conditional<
+ is_container<T>::value,
+ container_is_map<T>,
+ std::false_type
+ >::type {};
+
+} // 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 F, typename S>
+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));
+}
+
+template <typename T>
+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();
+ 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>
+static 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
+
+template <typename T, typename Enable = void> struct DynamicConverter;
+
+/**
+ * Each specialization of DynamicConverter has the function
+ * 'static T convert(const dynamic& d);'
+ */
+
+// 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 static_cast<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 static_cast<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());
+ }
+ }
+};
+
+// map containers
+template <typename C>
+struct DynamicConverter<C,
+ typename std::enable_if<
+ dynamicconverter_detail::is_map_container<C>::value>::type> {
+ static C convert(const dynamic& d) {
+ if (LIKELY(d.isObject())) {
+ return C(dynamicconverter_detail::conversionIterator<C>
+ (d.items().begin()),
+ dynamicconverter_detail::conversionIterator<C>
+ (d.items().end()));
+ } else if (d.isArray()) {
+ return C(dynamicconverter_detail::conversionIterator<C>(d.begin()),
+ dynamicconverter_detail::conversionIterator<C>(d.end()));
+ } else {
+ throw TypeError("object or array", d.type());
+ }
+ }
+};
+
+// non-map containers
+template <typename C>
+struct DynamicConverter<C,
+ typename std::enable_if<
+ dynamicconverter_detail::is_container<C>::value &&
+ !dynamicconverter_detail::is_map_container<C>::value
+ >::type
+ > {
+ static C convert(const dynamic& d) {
+ if (LIKELY(d.isArray())) {
+ return C(dynamicconverter_detail::conversionIterator<C>(d.begin()),
+ dynamicconverter_detail::conversionIterator<C>(d.end()));
+ } else {
+ throw TypeError("array", d.type());
+ }
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// convertTo implementation
+
+template <typename T>
+T convertTo(const dynamic& d) {
+ return DynamicConverter<T>::convert(d);
+}
+
+} // namespace folly
+
+#endif // DYNAMIC_CONVERTER_H
+
// since it can't fit in a double
```
+For more complicated conversions, see [DynamicConverter](DynamicConverter.md).
+
### Iteration and Lookup
***
--- /dev/null
+`folly/DynamicConverter.h`
+--------------------------
+
+When dynamic objects contain data of a known type, it is sometimes
+useful to have its well-typed representation. A broad set of
+type-conversions are contained in `DynamicConverter.h`, and
+facilitate the transformation of dynamic objects into their well-typed
+format.
+
+### Usage
+***
+
+Simply pass a dynamic into a templated convertTo:
+
+```
+ dynamic d = { { 1, 2, 3 }, { 4, 5 } }; // a vector of vector of int
+ auto vvi = convertTo<fbvector<fbvector<int>>>(d);
+```
+
+### Supported Types
+***
+
+convertTo naturally supports conversions to
+
+1. arithmetic types (such as int64_t, unsigned short, bool, and double)
+2. fbstring, std::string
+3. containers and map-containers
+
+NOTE:
+
+convertTo<Type> will assume that Type is a container if
+* it has a Type::value_type, and
+* it has a Type::iterator, and
+* it has a constructor that accepts two InputIterators
+
+Additionally, convertTo<Type> will assume that Type is a map if
+* it has a Type::key_type, and
+* it has a Type::mapped_type, and
+* value_type is a pair of const key_type and mapped_type
+
+If Type meets the container criteria, then it will be constructed
+by calling its InputIterator constructor.
+
+### Customization
+***
+
+If you want to use convertTo to convert dynamics into your own custom
+class, then all you have to do is provide a template specialization
+of DynamicConverter with the static method convert. Make sure you put it
+in namespace folly.
+
+Example:
+
+``` Cpp
+ struct Token {
+ int kind_;
+ fbstring lexeme_;
+
+ explicit Token(int kind, const fbstring& lexeme)
+ : kind_(kind), lexeme_(lexeme) {}
+ };
+ namespace folly {
+ template <> struct DynamicConverter<Token> {
+ static Token convert(const dynamic& d) {
+ int k = convertTo<int>(d["KIND"]);
+ fbstring lex = convertTo<fbstring>(d["LEXEME"]);
+ return Token(k, lex);
+ }
+ };
+ }
+```
+
--- /dev/null
+/*
+ * Copyright 2012 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>
+
+#include "folly/DynamicConverter.h"
+#include <gtest/gtest.h>
+#include <gflags/gflags.h>
+#include "folly/Benchmark.h"
+
+using namespace folly;
+using namespace folly::dynamicconverter_detail;
+
+TEST(DynamicConverter, template_metaprogramming) {
+ struct A {};
+
+ bool c1f = is_container<int>::value;
+ bool c2f = is_container<std::pair<int, int>>::value;
+ bool c3f = is_container<A>::value;
+
+ bool c1t = is_container<std::vector<int>>::value;
+ bool c2t = is_container<std::set<int>>::value;
+ bool c3t = is_container<std::map<int, int>>::value;
+
+ EXPECT_EQ(c1f, false);
+ EXPECT_EQ(c2f, false);
+ EXPECT_EQ(c3f, false);
+ EXPECT_EQ(c1t, true);
+ EXPECT_EQ(c2t, true);
+ EXPECT_EQ(c3t, true);
+
+ bool m1f = is_map_container<int>::value;
+ bool m2f = is_map_container<std::vector<int>>::value;
+ bool m3f = is_map_container<std::set<int>>::value;
+
+ bool m1t = is_map_container<std::map<int, int>>::value;
+ bool m2t = is_map_container<std::unordered_map<int, int>>::value;
+
+ EXPECT_EQ(m1f, false);
+ EXPECT_EQ(m2f, false);
+ EXPECT_EQ(m3f, false);
+ EXPECT_EQ(m1t, true);
+ EXPECT_EQ(m2t, true);
+}
+
+TEST(DynamicConverter, arithmetic_types) {
+ dynamic d1 = 12;
+ auto i1 = convertTo<int>(d1);
+ EXPECT_EQ(i1, 12);
+
+ dynamic d2 = 123456789012345;
+ auto i2 = convertTo<int64_t>(d2);
+ EXPECT_EQ(i2, 123456789012345);
+
+ dynamic d3 = 123456789012345;
+ auto i3 = convertTo<uint8_t>(d3);
+ EXPECT_EQ(i3, 121);
+
+ dynamic d4 = 3.141;
+ auto i4 = convertTo<float>(d4);
+ EXPECT_EQ((int)(i4*100), 314);
+
+ dynamic d5 = true;
+ auto i5 = convertTo<bool>(d5);
+ EXPECT_EQ(i5, true);
+
+ dynamic d6 = 15;
+ const auto i6 = convertTo<const int>(d6);
+ EXPECT_EQ(i6, 15);
+
+ dynamic d7 = "87";
+ auto i7 = convertTo<int>(d7);
+ EXPECT_EQ(i7, 87);
+
+ dynamic d8 = "false";
+ auto i8 = convertTo<bool>(d8);
+ EXPECT_EQ(i8, false);
+}
+
+TEST(DynamicConverter, simple_builtins) {
+ dynamic d1 = "Haskell";
+ auto i1 = convertTo<folly::fbstring>(d1);
+ EXPECT_EQ(i1, "Haskell");
+
+ dynamic d2 = 13;
+ auto i2 = convertTo<std::string>(d2);
+ EXPECT_EQ(i2, "13");
+
+ dynamic d3 = { 12, "Scala" };
+ auto i3 = convertTo<std::pair<int, std::string>>(d3);
+ EXPECT_EQ(i3.first, 12);
+ EXPECT_EQ(i3.second, "Scala");
+
+ dynamic d4 = dynamic::object("C", "C++");
+ auto i4 = convertTo<std::pair<std::string, folly::fbstring>>(d4);
+ EXPECT_EQ(i4.first, "C");
+ EXPECT_EQ(i4.second, "C++");
+}
+
+TEST(DynamicConverter, simple_fbvector) {
+ dynamic d1 = { 1, 2, 3 };
+ auto i1 = convertTo<folly::fbvector<int>>(d1);
+ decltype(i1) i1b = { 1, 2, 3 };
+ EXPECT_EQ(i1, i1b);
+}
+
+TEST(DynamicConverter, simple_container) {
+ dynamic d1 = { 1, 2, 3 };
+ auto i1 = convertTo<std::vector<int>>(d1);
+ decltype(i1) i1b = { 1, 2, 3 };
+ EXPECT_EQ(i1, i1b);
+
+ dynamic d2 = { 1, 3, 5, 2, 4 };
+ auto i2 = convertTo<std::set<int>>(d2);
+ decltype(i2) i2b = { 1, 2, 3, 5, 4 };
+ EXPECT_EQ(i2, i2b);
+}
+
+TEST(DynamicConverter, simple_map) {
+ dynamic d1 = dynamic::object(1, "one")(2, "two");
+ auto i1 = convertTo<std::map<int, std::string>>(d1);
+ decltype(i1) i1b = { { 1, "one" }, { 2, "two" } };
+ EXPECT_EQ(i1, i1b);
+
+ dynamic d2 = { { 3, "three" }, { 4, "four" } };
+ auto i2 = convertTo<std::unordered_map<int, std::string>>(d2);
+ decltype(i2) i2b = { { 3, "three" }, { 4, "four" } };
+ EXPECT_EQ(i2, i2b);
+}
+
+TEST(DynamicConverter, nested_containers) {
+ dynamic d1 = { { 1 }, { }, { 2, 3 } };
+ auto i1 = convertTo<folly::fbvector<std::vector<uint8_t>>>(d1);
+ decltype(i1) i1b = { { 1 }, { }, { 2, 3 } };
+ EXPECT_EQ(i1, i1b);
+
+ dynamic h2a = { "3", ".", "1", "4" };
+ dynamic h2b = { "2", ".", "7", "2" };
+ dynamic d2 = dynamic::object(3.14, h2a)(2.72, h2b);
+ auto i2 = convertTo<std::map<double, std::vector<folly::fbstring>>>(d2);
+ decltype(i2) i2b =
+ { { 3.14, { "3", ".", "1", "4" } },
+ { 2.72, { "2", ".", "7", "2" } } };
+ EXPECT_EQ(i2, i2b);
+}
+
+struct A {
+ int i;
+ bool operator==(const A & o) const { return i == o.i; }
+};
+namespace folly {
+template <> struct DynamicConverter<A> {
+ static A convert(const dynamic & d) {
+ return { convertTo<int>(d["i"]) };
+ }
+};
+}
+TEST(DynamicConverter, custom_class) {
+ dynamic d1 = dynamic::object("i", 17);
+ auto i1 = convertTo<A>(d1);
+ EXPECT_EQ(i1.i, 17);
+
+ dynamic d2 = { dynamic::object("i", 18), dynamic::object("i", 19) };
+ auto i2 = convertTo<std::vector<A>>(d2);
+ decltype(i2) i2b = { { 18 }, { 19 } };
+ EXPECT_EQ(i2, i2b);
+}
+
+TEST(DynamicConverter, crazy) {
+ // we are going to create a vector<unordered_map<bool, T>>
+ // we will construct some of the maps from dynamic objects,
+ // some from a vector of KV pairs.
+ // T will be vector<set<string>>
+
+ std::set<std::string>
+ s1 = { "a", "e", "i", "o", "u" },
+ s2 = { "2", "3", "5", "7" },
+ s3 = { "Hello", "World" };
+
+ std::vector<std::set<std::string>>
+ v1 = {},
+ v2 = { s1, s2 },
+ v3 = { s3 };
+
+ std::unordered_map<bool, std::vector<std::set<std::string>>>
+ m1 = { { true, v1 }, { false, v2 } },
+ m2 = { { true, v3 } };
+
+ std::vector<std::unordered_map<bool, std::vector<std::set<std::string>>>>
+ f1 = { m1, m2 };
+
+
+ dynamic
+ ds1 = { "a", "e", "i", "o", "u" },
+ ds2 = { "2", "3", "5", "7" },
+ ds3 = { "Hello", "World" };
+
+ dynamic
+ dv1 = {},
+ dv2 = { ds1, ds2 },
+ dv3 = { ds3 };
+
+ dynamic
+ dm1 = dynamic::object(true, dv1)(false, dv2),
+ dm2 = { { true, dv3 } };
+
+ dynamic
+ df1 = { dm1, dm2 };
+
+
+ auto i = convertTo<std::vector<std::unordered_map<bool, std::vector<
+ std::set<std::string>>>>>(df1); // yes, that is 5 close-chevrons
+
+ EXPECT_EQ(f1, i);
+}
+
+struct Token {
+ int kind_;
+ fbstring lexeme_;
+
+ explicit Token(int kind, const fbstring& lexeme)
+ : kind_(kind), lexeme_(lexeme) {}
+};
+namespace folly {
+template <> struct DynamicConverter<Token> {
+ static Token convert(const dynamic& d) {
+ int k = convertTo<int>(d["KIND"]);
+ fbstring lex = convertTo<fbstring>(d["LEXEME"]);
+ return Token(k, lex);
+ }
+};
+}
+TEST(DynamicConverter, example) {
+ dynamic d1 = dynamic::object("KIND", 2)("LEXEME", "a token");
+ auto i1 = convertTo<Token>(d1);
+ EXPECT_EQ(i1.kind_, 2);
+ EXPECT_EQ(i1.lexeme_, "a token");
+}
+
+int main(int argc, char ** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ google::ParseCommandLineFlags(&argc, &argv, true);
+ if (FLAGS_benchmark) {
+ folly::runBenchmarks();
+ }
+ return RUN_ALL_TESTS();
+}
+