From 5b67454fe0be0482000e6fb53c73d2c84bb7dab6 Mon Sep 17 00:00:00 2001 From: Nicholas Ormrod Date: Tue, 14 Aug 2012 16:11:03 -0700 Subject: [PATCH] Added dynamic::convertTo method Summary: convert a dynamic to a well-typed object Test Plan: run test file Reviewed By: delong.j@fb.com FB internal diff: D517021 --- folly/DynamicConverter.h | 277 ++++++++++++++++++++++++++++ folly/docs/Dynamic.md | 2 + folly/docs/DynamicConverter.md | 72 ++++++++ folly/test/DynamicConverterTest.cpp | 261 ++++++++++++++++++++++++++ 4 files changed, 612 insertions(+) create mode 100644 folly/DynamicConverter.h create mode 100644 folly/docs/DynamicConverter.md create mode 100644 folly/test/DynamicConverterTest.cpp diff --git a/folly/DynamicConverter.h b/folly/DynamicConverter.h new file mode 100644 index 00000000..275d6d93 --- /dev/null +++ b/folly/DynamicConverter.h @@ -0,0 +1,277 @@ +/* + * 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 + +#ifndef DYNAMIC_CONVERTER_H +#define DYNAMIC_CONVERTER_H + +#include "folly/dynamic.h" +namespace folly { + template 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>>(d); + * + * See docs/DynamicConverter.md for supported types and customization + */ + + +#include +#include +#include +#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 struct map_container_has_correct_types + : std::is_same::type, + typename T::mapped_type>, + typename T::value_type> {}; + +template struct class_is_container { + struct dummy {}; + enum { value = has_value_type::value && + has_iterator::value && + std::is_constructible::value }; +}; + +template struct container_is_map + : std::conditional< + has_key_type::value && has_mapped_type::value, + map_container_has_correct_types, + std::false_type + >::type {}; + +template struct is_container + : std::conditional< + std::is_class::value, + class_is_container, + std::false_type + >::type {}; + +template struct is_map_container + : std::conditional< + is_container::value, + container_is_map, + 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 +inline void +derefToCache(std::pair* mem, const dynamic::const_item_iterator& it) { + new (mem) std::pair(convertTo(it->first), convertTo(it->second)); +} + +template +inline void derefToCache(T* mem, const dynamic::const_iterator& it) { + new (mem) T(convertTo(*it)); +} + +template +class Transformer : public boost::iterator_adaptor< + Transformer, + 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 +static inline std::move_iterator> +conversionIterator(const It& it) { + return std::make_move_iterator(Transformer(it)); +} + +} // namespace dynamicconverter_detail + +/////////////////////////////////////////////////////////////////////////////// +// DynamicConverter specializations + +template struct DynamicConverter; + +/** + * Each specialization of DynamicConverter has the function + * 'static T convert(const dynamic& d);' + */ + +// boolean +template <> +struct DynamicConverter { + static bool convert(const dynamic& d) { + return d.asBool(); + } +}; + +// integrals +template +struct DynamicConverter::value && + !std::is_same::value>::type> { + static T convert(const dynamic& d) { + return static_cast(d.asInt()); + } +}; + +// floating point +template +struct DynamicConverter::value>::type> { + static T convert(const dynamic& d) { + return static_cast(d.asDouble()); + } +}; + +// fbstring +template <> +struct DynamicConverter { + static folly::fbstring convert(const dynamic& d) { + return d.asString(); + } +}; + +// std::string +template <> +struct DynamicConverter { + static std::string convert(const dynamic& d) { + return d.asString().toStdString(); + } +}; + +// std::pair +template +struct DynamicConverter> { + static std::pair convert(const dynamic& d) { + if (d.isArray() && d.size() == 2) { + return std::make_pair(convertTo(d[0]), convertTo(d[1])); + } else if (d.isObject() && d.size() == 1) { + auto it = d.items().begin(); + return std::make_pair(convertTo(it->first), convertTo(it->second)); + } else { + throw TypeError("array (size 2) or object (size 1)", d.type()); + } + } +}; + +// map containers +template +struct DynamicConverter::value>::type> { + static C convert(const dynamic& d) { + if (LIKELY(d.isObject())) { + return C(dynamicconverter_detail::conversionIterator + (d.items().begin()), + dynamicconverter_detail::conversionIterator + (d.items().end())); + } else if (d.isArray()) { + return C(dynamicconverter_detail::conversionIterator(d.begin()), + dynamicconverter_detail::conversionIterator(d.end())); + } else { + throw TypeError("object or array", d.type()); + } + } +}; + +// non-map containers +template +struct DynamicConverter::value && + !dynamicconverter_detail::is_map_container::value + >::type + > { + static C convert(const dynamic& d) { + if (LIKELY(d.isArray())) { + return C(dynamicconverter_detail::conversionIterator(d.begin()), + dynamicconverter_detail::conversionIterator(d.end())); + } else { + throw TypeError("array", d.type()); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// convertTo implementation + +template +T convertTo(const dynamic& d) { + return DynamicConverter::convert(d); +} + +} // namespace folly + +#endif // DYNAMIC_CONVERTER_H + diff --git a/folly/docs/Dynamic.md b/folly/docs/Dynamic.md index 9a2beee3..c9a5c365 100644 --- a/folly/docs/Dynamic.md +++ b/folly/docs/Dynamic.md @@ -73,6 +73,8 @@ Explicit type conversions can be requested for some of the basic types: // since it can't fit in a double ``` +For more complicated conversions, see [DynamicConverter](DynamicConverter.md). + ### Iteration and Lookup *** diff --git a/folly/docs/DynamicConverter.md b/folly/docs/DynamicConverter.md new file mode 100644 index 00000000..d0ee7f79 --- /dev/null +++ b/folly/docs/DynamicConverter.md @@ -0,0 +1,72 @@ +`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>>(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 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 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 { + static Token convert(const dynamic& d) { + int k = convertTo(d["KIND"]); + fbstring lex = convertTo(d["LEXEME"]); + return Token(k, lex); + } + }; + } +``` + diff --git a/folly/test/DynamicConverterTest.cpp b/folly/test/DynamicConverterTest.cpp new file mode 100644 index 00000000..5d414d44 --- /dev/null +++ b/folly/test/DynamicConverterTest.cpp @@ -0,0 +1,261 @@ +/* + * 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 + +#include "folly/DynamicConverter.h" +#include +#include +#include "folly/Benchmark.h" + +using namespace folly; +using namespace folly::dynamicconverter_detail; + +TEST(DynamicConverter, template_metaprogramming) { + struct A {}; + + bool c1f = is_container::value; + bool c2f = is_container>::value; + bool c3f = is_container::value; + + bool c1t = is_container>::value; + bool c2t = is_container>::value; + bool c3t = is_container>::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::value; + bool m2f = is_map_container>::value; + bool m3f = is_map_container>::value; + + bool m1t = is_map_container>::value; + bool m2t = is_map_container>::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(d1); + EXPECT_EQ(i1, 12); + + dynamic d2 = 123456789012345; + auto i2 = convertTo(d2); + EXPECT_EQ(i2, 123456789012345); + + dynamic d3 = 123456789012345; + auto i3 = convertTo(d3); + EXPECT_EQ(i3, 121); + + dynamic d4 = 3.141; + auto i4 = convertTo(d4); + EXPECT_EQ((int)(i4*100), 314); + + dynamic d5 = true; + auto i5 = convertTo(d5); + EXPECT_EQ(i5, true); + + dynamic d6 = 15; + const auto i6 = convertTo(d6); + EXPECT_EQ(i6, 15); + + dynamic d7 = "87"; + auto i7 = convertTo(d7); + EXPECT_EQ(i7, 87); + + dynamic d8 = "false"; + auto i8 = convertTo(d8); + EXPECT_EQ(i8, false); +} + +TEST(DynamicConverter, simple_builtins) { + dynamic d1 = "Haskell"; + auto i1 = convertTo(d1); + EXPECT_EQ(i1, "Haskell"); + + dynamic d2 = 13; + auto i2 = convertTo(d2); + EXPECT_EQ(i2, "13"); + + dynamic d3 = { 12, "Scala" }; + auto i3 = convertTo>(d3); + EXPECT_EQ(i3.first, 12); + EXPECT_EQ(i3.second, "Scala"); + + dynamic d4 = dynamic::object("C", "C++"); + auto i4 = convertTo>(d4); + EXPECT_EQ(i4.first, "C"); + EXPECT_EQ(i4.second, "C++"); +} + +TEST(DynamicConverter, simple_fbvector) { + dynamic d1 = { 1, 2, 3 }; + auto i1 = convertTo>(d1); + decltype(i1) i1b = { 1, 2, 3 }; + EXPECT_EQ(i1, i1b); +} + +TEST(DynamicConverter, simple_container) { + dynamic d1 = { 1, 2, 3 }; + auto i1 = convertTo>(d1); + decltype(i1) i1b = { 1, 2, 3 }; + EXPECT_EQ(i1, i1b); + + dynamic d2 = { 1, 3, 5, 2, 4 }; + auto i2 = convertTo>(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>(d1); + decltype(i1) i1b = { { 1, "one" }, { 2, "two" } }; + EXPECT_EQ(i1, i1b); + + dynamic d2 = { { 3, "three" }, { 4, "four" } }; + auto i2 = convertTo>(d2); + decltype(i2) i2b = { { 3, "three" }, { 4, "four" } }; + EXPECT_EQ(i2, i2b); +} + +TEST(DynamicConverter, nested_containers) { + dynamic d1 = { { 1 }, { }, { 2, 3 } }; + auto i1 = convertTo>>(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>>(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 { + static A convert(const dynamic & d) { + return { convertTo(d["i"]) }; + } +}; +} +TEST(DynamicConverter, custom_class) { + dynamic d1 = dynamic::object("i", 17); + auto i1 = convertTo(d1); + EXPECT_EQ(i1.i, 17); + + dynamic d2 = { dynamic::object("i", 18), dynamic::object("i", 19) }; + auto i2 = convertTo>(d2); + decltype(i2) i2b = { { 18 }, { 19 } }; + EXPECT_EQ(i2, i2b); +} + +TEST(DynamicConverter, crazy) { + // we are going to create a vector> + // we will construct some of the maps from dynamic objects, + // some from a vector of KV pairs. + // T will be vector> + + std::set + s1 = { "a", "e", "i", "o", "u" }, + s2 = { "2", "3", "5", "7" }, + s3 = { "Hello", "World" }; + + std::vector> + v1 = {}, + v2 = { s1, s2 }, + v3 = { s3 }; + + std::unordered_map>> + m1 = { { true, v1 }, { false, v2 } }, + m2 = { { true, v3 } }; + + std::vector>>> + 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>>>>(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 { + static Token convert(const dynamic& d) { + int k = convertTo(d["KIND"]); + fbstring lex = convertTo(d["LEXEME"]); + return Token(k, lex); + } +}; +} +TEST(DynamicConverter, example) { + dynamic d1 = dynamic::object("KIND", 2)("LEXEME", "a token"); + auto i1 = convertTo(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(); +} + -- 2.34.1