Use std::string in folly::dynamic
[folly.git] / folly / dynamic.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <folly/dynamic.h>
18 #include <folly/Hash.h>
19
20 namespace folly {
21
22 //////////////////////////////////////////////////////////////////////
23
24 #define FOLLY_DYNAMIC_DEF_TYPEINFO(T) \
25   constexpr const char* dynamic::TypeInfo<T>::name; \
26   constexpr dynamic::Type dynamic::TypeInfo<T>::type; \
27   //
28
29 FOLLY_DYNAMIC_DEF_TYPEINFO(void*)
30 FOLLY_DYNAMIC_DEF_TYPEINFO(bool)
31 FOLLY_DYNAMIC_DEF_TYPEINFO(std::string)
32 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::Array)
33 FOLLY_DYNAMIC_DEF_TYPEINFO(double)
34 FOLLY_DYNAMIC_DEF_TYPEINFO(int64_t)
35 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::ObjectImpl)
36
37 #undef FOLLY_DYNAMIC_DEF_TYPEINFO
38
39 const char* dynamic::typeName() const {
40   return typeName(type_);
41 }
42
43 TypeError::TypeError(const std::string& expected, dynamic::Type actual)
44   : std::runtime_error(to<std::string>("TypeError: expected dynamic "
45       "type `", expected, '\'', ", but had type `",
46       dynamic::typeName(actual), '\''))
47 {}
48
49 TypeError::TypeError(const std::string& expected,
50     dynamic::Type actual1, dynamic::Type actual2)
51   : std::runtime_error(to<std::string>("TypeError: expected dynamic "
52       "types `", expected, '\'', ", but had types `",
53       dynamic::typeName(actual1), "' and `", dynamic::typeName(actual2),
54       '\''))
55 {}
56
57 TypeError::~TypeError() = default;
58
59 // This is a higher-order preprocessor macro to aid going from runtime
60 // types to the compile time type system.
61 #define FB_DYNAMIC_APPLY(type, apply) \
62   do {                                \
63     switch ((type)) {                 \
64       case NULLT:                     \
65         apply(void*);                 \
66         break;                        \
67       case ARRAY:                     \
68         apply(Array);                 \
69         break;                        \
70       case BOOL:                      \
71         apply(bool);                  \
72         break;                        \
73       case DOUBLE:                    \
74         apply(double);                \
75         break;                        \
76       case INT64:                     \
77         apply(int64_t);               \
78         break;                        \
79       case OBJECT:                    \
80         apply(ObjectImpl);            \
81         break;                        \
82       case STRING:                    \
83         apply(std::string);           \
84         break;                        \
85       default:                        \
86         CHECK(0);                     \
87         abort();                      \
88     }                                 \
89   } while (0)
90
91 bool dynamic::operator<(dynamic const& o) const {
92   if (UNLIKELY(type_ == OBJECT || o.type_ == OBJECT)) {
93     throw TypeError("object", type_);
94   }
95   if (type_ != o.type_) {
96     return type_ < o.type_;
97   }
98
99 #define FB_X(T) return CompareOp<T>::comp(*getAddress<T>(),   \
100                                           *o.getAddress<T>())
101   FB_DYNAMIC_APPLY(type_, FB_X);
102 #undef FB_X
103 }
104
105 bool dynamic::operator==(dynamic const& o) const {
106   if (type() != o.type()) {
107     if (isNumber() && o.isNumber()) {
108       auto& integ = isInt() ? *this : o;
109       auto& doubl = isInt() ? o     : *this;
110       return integ.asInt() == doubl.asDouble();
111     }
112     return false;
113   }
114
115 #define FB_X(T) return *getAddress<T>() == *o.getAddress<T>();
116   FB_DYNAMIC_APPLY(type_, FB_X);
117 #undef FB_X
118 }
119
120 dynamic& dynamic::operator=(dynamic const& o) {
121   if (&o != this) {
122     if (type_ == o.type_) {
123 #define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
124       FB_DYNAMIC_APPLY(type_, FB_X);
125 #undef FB_X
126     } else {
127       destroy();
128 #define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
129       FB_DYNAMIC_APPLY(o.type_, FB_X);
130 #undef FB_X
131       type_ = o.type_;
132     }
133   }
134   return *this;
135 }
136
137 dynamic& dynamic::operator=(dynamic&& o) noexcept {
138   if (&o != this) {
139     if (type_ == o.type_) {
140 #define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
141       FB_DYNAMIC_APPLY(type_, FB_X);
142 #undef FB_X
143     } else {
144       destroy();
145 #define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
146       FB_DYNAMIC_APPLY(o.type_, FB_X);
147 #undef FB_X
148       type_ = o.type_;
149     }
150   }
151   return *this;
152 }
153
154 dynamic& dynamic::operator[](dynamic const& k) & {
155   if (!isObject() && !isArray()) {
156     throw TypeError("object/array", type());
157   }
158   if (isArray()) {
159     return at(k);
160   }
161   auto& obj = get<ObjectImpl>();
162   auto ret = obj.insert({k, nullptr});
163   return ret.first->second;
164 }
165
166 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const& {
167   auto& obj = get<ObjectImpl>();
168   auto it = obj.find(k);
169   return it == obj.end() ? v : it->second;
170 }
171
172 dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) const& {
173   auto& obj = get<ObjectImpl>();
174   auto it = obj.find(k);
175   // Avoid clang bug with ternary
176   if (it == obj.end()) {
177     return std::move(v);
178   } else {
179     return it->second;
180   }
181 }
182
183 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) && {
184   auto& obj = get<ObjectImpl>();
185   auto it = obj.find(k);
186   // Avoid clang bug with ternary
187   if (it == obj.end()) {
188     return v;
189   } else {
190     return std::move(it->second);
191   }
192 }
193
194 dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) && {
195   auto& obj = get<ObjectImpl>();
196   auto it = obj.find(k);
197   return std::move(it == obj.end() ? v : it->second);
198 }
199
200 const dynamic* dynamic::get_ptr(dynamic const& idx) const& {
201   if (auto* parray = get_nothrow<Array>()) {
202     if (!idx.isInt()) {
203       throw TypeError("int64", idx.type());
204     }
205     if (idx < 0 || idx >= parray->size()) {
206       return nullptr;
207     }
208     return &(*parray)[idx.asInt()];
209   } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
210     auto it = pobject->find(idx);
211     if (it == pobject->end()) {
212       return nullptr;
213     }
214     return &it->second;
215   } else {
216     throw TypeError("object/array", type());
217   }
218 }
219
220 dynamic const& dynamic::at(dynamic const& idx) const& {
221   if (auto* parray = get_nothrow<Array>()) {
222     if (!idx.isInt()) {
223       throw TypeError("int64", idx.type());
224     }
225     if (idx < 0 || idx >= parray->size()) {
226       throw std::out_of_range("out of range in dynamic array");
227     }
228     return (*parray)[idx.asInt()];
229   } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
230     auto it = pobject->find(idx);
231     if (it == pobject->end()) {
232       throw std::out_of_range(to<std::string>(
233           "couldn't find key ", idx.asString(), " in dynamic object"));
234     }
235     return it->second;
236   } else {
237     throw TypeError("object/array", type());
238   }
239 }
240
241 std::size_t dynamic::size() const {
242   if (auto* ar = get_nothrow<Array>()) {
243     return ar->size();
244   }
245   if (auto* obj = get_nothrow<ObjectImpl>()) {
246     return obj->size();
247   }
248   if (auto* str = get_nothrow<std::string>()) {
249     return str->size();
250   }
251   throw TypeError("array/object", type());
252 }
253
254 dynamic::const_iterator
255 dynamic::erase(const_iterator first, const_iterator last) {
256   auto& arr = get<Array>();
257   return get<Array>().erase(
258     arr.begin() + (first - arr.begin()),
259     arr.begin() + (last - arr.begin()));
260 }
261
262 std::size_t dynamic::hash() const {
263   switch (type()) {
264   case OBJECT:
265   case ARRAY:
266   case NULLT:
267     throw TypeError("not null/object/array", type());
268   case INT64:
269     return std::hash<int64_t>()(getInt());
270   case DOUBLE:
271     return std::hash<double>()(getDouble());
272   case BOOL:
273     return std::hash<bool>()(getBool());
274   case STRING: {
275     // keep it compatible with FBString
276     const auto& str = getString();
277     return ::folly::hash::fnv32_buf(str.data(), str.size());
278   }
279   default:
280     CHECK(0); abort();
281   }
282 }
283
284 char const* dynamic::typeName(Type t) {
285 #define FB_X(T) return TypeInfo<T>::name
286   FB_DYNAMIC_APPLY(t, FB_X);
287 #undef FB_X
288 }
289
290 void dynamic::destroy() noexcept {
291   // This short-circuit speeds up some microbenchmarks.
292   if (type_ == NULLT) return;
293
294 #define FB_X(T) detail::Destroy::destroy(getAddress<T>())
295   FB_DYNAMIC_APPLY(type_, FB_X);
296 #undef FB_X
297   type_ = NULLT;
298   u_.nul = nullptr;
299 }
300
301 //////////////////////////////////////////////////////////////////////
302
303 }