From b76eaabd9b54d707bf9620bc5dee0140f67c3018 Mon Sep 17 00:00:00 2001 From: Timothy Prepscius Date: Tue, 23 Oct 2018 21:09:16 -0400 Subject: [PATCH] revised adding string_view and stuct { const char * } --- include/nlohmann/detail/input/json_sax.hpp | 8 +- include/nlohmann/detail/input/parser.hpp | 2 + include/nlohmann/detail/json_string_view.hpp | 257 ++++++++++++++++++ include/nlohmann/detail/output/serializer.hpp | 8 +- include/nlohmann/json.hpp | 130 ++++++--- 5 files changed, 365 insertions(+), 40 deletions(-) create mode 100644 include/nlohmann/detail/json_string_view.hpp diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 153f75d2b..f3864bf78 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace nlohmann { @@ -21,6 +22,7 @@ input. template struct json_sax { + using object_t = typename BasicJsonType::object_t; /// type for (signed) integers using number_integer_t = typename BasicJsonType::number_integer_t; /// type for unsigned integers @@ -143,6 +145,7 @@ template class json_sax_dom_parser { public: + using object_t = typename BasicJsonType::object_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -209,7 +212,7 @@ class json_sax_dom_parser bool key(string_t& val) { // add null at given key and store the reference for later - object_element = &(ref_stack.back()->m_value.object->operator[](val)); + object_element = &(ref_stack.back()->m_value.object->operator[](to_map_key(val))); return true; } @@ -318,6 +321,7 @@ template class json_sax_dom_callback_parser { public: + using object_t = typename BasicJsonType::object_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; @@ -402,7 +406,7 @@ class json_sax_dom_callback_parser // add discarded value at given key and store the reference for later if (keep and ref_stack.back()) { - object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); + object_element = &(ref_stack.back()->m_value.object->operator[](to_map_key(val)) = discarded); } return true; diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index ac19bb8c6..f9c55ce53 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace nlohmann { @@ -31,6 +32,7 @@ This class implements a recursive decent parser. template class parser { + using object_t = typename BasicJsonType::object_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; diff --git a/include/nlohmann/detail/json_string_view.hpp b/include/nlohmann/detail/json_string_view.hpp new file mode 100644 index 000000000..d32275ef3 --- /dev/null +++ b/include/nlohmann/detail/json_string_view.hpp @@ -0,0 +1,257 @@ +#pragma once + +// ------------------------------------------------------------------------ +// This header, and the associated changes in json.hpp enable string_view and struct { const char * } as a map_key type. +// +// Why would you want this? In my case, I am using millions of json objects, each one uses the same keys. +// Why use objects at all? This is a question for another time. +// +// In any case, since my keys are known, and finite, I would rather there be a key look-up table, so as to no have a billion copies +// of "transform" lying around. I would rather just have one. +// +// The differences of string_view and const char * are simple. +// string_view uses a strcmp in it's core of the map lookup, so there is no performance increase. +// struct { const char * } uses a pure pointer comparison, so map operations are super fast, and as well, the map should probably be changed +// into a hash map using the pointers. +// +// The draw back of the struct { const char * }, is that, for the full performance, you must cache the correct to_map_key("my_key_value") +// and use that (so it doesn't need to look up the correct pointer first). This is trivial to do however, so, the performance increase +// is great. +// +// ------------------------------------------------------------------------ + + +#ifdef USE_EXPERIMENTAL_STRINGVIEW +#include +#endif + +namespace nlohmann +{ + + +#ifdef USE_EXPERIMENTAL_STRINGVIEW +using json_string_view = std::experimental::string_view; +#else + +// a minimum implementation of string_view +struct json_string_view +{ + const char *data_; + size_t size_; + + json_string_view(const char *data, size_t size) : + data_(data), size_(size) + {} + + json_string_view(const std::string &s) : + data_(s.c_str()), size_(s.size()) + { + } + + size_t size() const + { + return size_; + } + + operator std::string() const + { + return std::string(data_, size_); + } + + const char *begin() const + { + return data_; + } + + const char *end () const + { + return data_ + size_; + } +} ; + +inline +bool operator ==(const json_string_view &l, const json_string_view &r) +{ + if (l.data_ == r.data_ && l.size_ == r.size_) + return true; + + return l.size_ == r.size_ && strncmp(l.data_, r.data_, l.size_) == 0; +} + +inline +bool operator !=(const json_string_view &l, const json_string_view &r) +{ + return !(l == r); +} + +inline +bool operator <(const json_string_view &l, const json_string_view &r) +{ + // will implement, i need to look up proper way to do this + std::string ls(l); + std::string rs(r); + return ls < rs; +} + +#endif + +// ----------------------- + +struct json_const_char_star { + const char *data; +} ; + +inline +bool operator ==(const json_const_char_star &l, const json_const_char_star &r) +{ + return (l.data == r.data); +} + +inline +bool operator !=(const json_const_char_star &l, const json_const_char_star &r) +{ + return !(l == r); +} + +inline +bool operator <(const json_const_char_star &l, const json_const_char_star &r) +{ + // pure pointer compare + return l.data < r.data; +} + +// ------------------- + +// why am I having trouble getting rid of this? it must be too late +typedef const char *__stupid_const_char_typedef; + +template inline R to_map_key(const __stupid_const_char_typedef &s); +template inline R to_map_key(const json_string_view &s); +template inline R to_map_key(const std::string &s); +template inline R to_map_key(const json_const_char_star &s); + +template inline R to_lookup_key(const __stupid_const_char_typedef &s); +template inline R to_lookup_key(const json_string_view &s); +template inline R to_lookup_key(const std::string &s); +template inline R to_lookup_key(const json_const_char_star &s); + +template +inline R to_concatable_string(const T &t) +{ + return R(t); +} + +// ------------------- + +template<> +inline json_string_view to_map_key(const json_string_view &s) +{ + static std::set strings; + static std::set internals; + + auto i = internals.find(s); + if (i == internals.end()) + { + std::string str(s.begin(), s.end()); + strings.insert(str); + + auto sv = json_string_view(*strings.find(str)); + + internals.insert(sv); + return sv; + } + + return *i; +} ; + +template<> +inline json_string_view to_map_key(const std::string &s) +{ + return to_map_key(json_string_view(s)); +} ; + +template<> +inline json_string_view to_map_key(const __stupid_const_char_typedef &s) +{ + return to_map_key(json_string_view(s, strlen(s))); +} + +// ----------------------- + +template<> +inline json_string_view to_lookup_key(const json_string_view &s) +{ + return s; +} + +template<> +inline json_string_view to_lookup_key(const std::string &s) +{ + return json_string_view(s); +} ; + +template<> +inline json_string_view to_lookup_key(const __stupid_const_char_typedef &s) +{ + return json_string_view(s, strlen(s)); +} ; + +// ----------------------- + + +template<> +inline json_const_char_star to_map_key(const std::string &s) +{ + static std::set strings; + + auto i = strings.find(s); + if (i == strings.end()) + { + std::string str(s); + strings.insert(str); + + i = strings.find(str); + } + + return { i->c_str() }; +} ; + +template<> +inline json_const_char_star to_map_key(const __stupid_const_char_typedef &s) +{ + return to_map_key(std::string(s)); +} + +template<> +inline json_const_char_star to_map_key(const json_const_char_star &s) +{ + return s; +} + +template<> +inline json_const_char_star to_lookup_key(const std::string &s) +{ + return to_map_key(s); +} ; + +template<> +inline json_const_char_star to_lookup_key(const __stupid_const_char_typedef &s) +{ + return to_map_key(s); +} + +template<> +inline json_const_char_star to_lookup_key(const json_const_char_star &s) +{ + return s; +} + +template<> +inline +std::string to_concatable_string(const json_const_char_star &t) +{ + return std::string(t.data); +} + + +} // namespace diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index bb74a86e6..37fc9adb4 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -106,7 +106,7 @@ class serializer { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + dump_escaped(to_concatable_string(i->first), ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); @@ -117,7 +117,7 @@ class serializer assert(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + dump_escaped(to_concatable_string(i->first), ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); @@ -134,7 +134,7 @@ class serializer for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + dump_escaped(to_concatable_string(i->first), ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); @@ -144,7 +144,7 @@ class serializer assert(i != val.m_value.object->cend()); assert(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); + dump_escaped(to_concatable_string(i->first), ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 7bcb97287..d93c3304a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -68,6 +68,7 @@ SOFTWARE. #include #include #include +#include #include /*! @@ -162,6 +163,10 @@ Format](http://rfc7159.net/rfc7159) NLOHMANN_BASIC_JSON_TPL_DECLARATION class basic_json { + public: + // these definitions should come + typedef json_string_view map_key_type; + private: template friend struct detail::external_constructor; friend ::nlohmann::json_pointer; @@ -471,7 +476,7 @@ class basic_json 7159](http://rfc7159.net/rfc7159), because any order implements the specified "unordered" nature of JSON objects. */ - using object_t = ObjectTypeemplace( - std::move(*((*element.m_value.array)[0].m_value.string)), + std::move(to_map_key(*((*element.m_value.array)[0].m_value.string))), std::move((*element.m_value.array)[1])); }); } @@ -3006,7 +3011,7 @@ class basic_json JSON_CATCH (std::out_of_range&) { // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + JSON_THROW(out_of_range::create(403, "key '" + to_concatable_string(key) + "' not found")); } } else @@ -3014,6 +3019,13 @@ class basic_json JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } + + template + reference at(const T& key_) + { + auto key = to_lookup_key(key_); + return at(key); + } /*! @brief access specified object element with bounds checking @@ -3045,7 +3057,7 @@ class basic_json `at()`. It also demonstrates the different exceptions that can be thrown., at__object_t_key_type_const} */ - const_reference at(const typename object_t::key_type& key) const + const_reference at(const typename object_t::key_type & key) const { // at only works for objects if (JSON_LIKELY(is_object())) @@ -3057,7 +3069,7 @@ class basic_json JSON_CATCH (std::out_of_range&) { // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + JSON_THROW(out_of_range::create(403, "key '" + to_concatable_string(key) + "' not found")); } } else @@ -3066,6 +3078,13 @@ class basic_json } } + template + const_reference at(const T& key_) const + { + auto key = to_lookup_key(key_); + return at(key); + } + /*! @brief access specified array element @@ -3117,6 +3136,12 @@ class basic_json JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); } + + reference operator[](int idx) + { + return operator[](size_t(idx)); + } + /*! @brief access specified array element @@ -3148,6 +3173,11 @@ class basic_json JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); } + const_reference operator[](int idx) const + { + return operator[](size_t(idx)); + } + /*! @brief access specified object element @@ -3175,7 +3205,7 @@ class basic_json @since version 1.0.0 */ - reference operator[](const typename object_t::key_type& key) + reference operator[](const typename object_t::key_type& key_) { // implicitly convert null value to an empty object if (is_null()) @@ -3188,6 +3218,11 @@ class basic_json // operator[] only works for objects if (JSON_LIKELY(is_object())) { + auto i = m_value.object->find(key_); + if (i != m_value.object->end()) + return i->second; + + auto key = to_map_key(key_); return m_value.object->operator[](key); } @@ -3263,26 +3298,25 @@ class basic_json @since version 1.1.0 */ - template - reference operator[](T* key) + template + reference operator[](const T &key_) { - // implicitly convert null to object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // at only works for objects - if (JSON_LIKELY(is_object())) - { - return m_value.object->operator[](key); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + auto key = to_lookup_key(key_); + return operator[](key); } +// reference operator[](const std::string &key_) +// { +// auto key = to_map_key(key_); +// return operator[](key); +// } +// +// reference operator[](const char *key_) +// { +// auto key = to_map_key(key_); +// return operator[](key); +// } + /*! @brief read-only access specified object element @@ -3313,19 +3347,25 @@ class basic_json @since version 1.1.0 */ - template - const_reference operator[](T* key) const + template + const_reference operator[](const T &key_) const { - // at only works for objects - if (JSON_LIKELY(is_object())) - { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + auto key = to_lookup_key(key_); + return operator[](key); } +// const_reference operator[](const std::string &key_) const +// { +// auto key = to_map_key(key_); +// return operator[](key); +// } +// +// const_reference operator[](const char *key_) const +// { +// auto key = to_map_key(key_); +// return operator[](key); +// } + /*! @brief access specified object element with default value @@ -3802,7 +3842,7 @@ class basic_json @since version 1.0.0 */ - size_type erase(const typename object_t::key_type& key) + size_type erase(const typename object_t::key_type & key) { // this erase only works for objects if (JSON_LIKELY(is_object())) @@ -3813,6 +3853,24 @@ class basic_json JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } + template + size_type erase(const T& key_) + { + auto key = to_map_key(key_); + return erase(key); + } + + void erase(const iterator &key) + { + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + m_value.object->erase(key.m_it.object_iterator); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + /*! @brief remove element from a JSON array given an index @@ -3855,6 +3913,10 @@ class basic_json } } + void erase(int idx) + { + erase(static_cast(idx)); + } /// @}