Commit 5b67454f authored by Nicholas Ormrod's avatar Nicholas Ormrod Committed by Tudor Bosman

Added dynamic::convertTo<Type> 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
parent 37f0ea54
/*
* 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
...@@ -73,6 +73,8 @@ Explicit type conversions can be requested for some of the basic types: ...@@ -73,6 +73,8 @@ Explicit type conversions can be requested for some of the basic types:
// since it can't fit in a double // since it can't fit in a double
``` ```
For more complicated conversions, see [DynamicConverter](DynamicConverter.md).
### Iteration and Lookup ### Iteration and Lookup
*** ***
......
`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);
}
};
}
```
/*
* 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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment