// Copyright (C) 2020 T. Zachary Laine // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #ifndef JSON_HPP #define JSON_HPP #include "json_fwd.hpp" #include #include #include #include #include #include #include #include namespace json { namespace detail { // Detection idiom. template struct void_ { using type = void; }; template using void_t = typename void_::type; template using remove_cv_ref_t = typename std::remove_cv< typename std::remove_reference::type>::type; struct nonesuch {}; template< typename Default, typename AlwaysVoid, template class Template, typename... Args> struct detector { using value_t = std::false_type; using type = Default; }; template< typename Default, template class Template, typename... Args> struct detector>, Template, Args...> { using value_t = std::true_type; using type = Template; }; template class Template, typename... Args> using is_detected = typename detector::value_t; template class Template, typename... Args> using detected_t = typename detector::type; template< typename Default, template class Template, typename... Args> using detected_or = typename detector::type; // Type traits. template using has_begin = decltype(*std::begin(std::declval())); template using has_end = decltype(*std::end(std::declval())); template using is_range_of = std::integral_constant< bool, ((std::is_same>, U>:: value || std::is_same>, V>:: value) && is_detected::value)>; template using is_range_of_char = std::integral_constant< bool, (std::is_convertible::value && sizeof(BeginValueType) == 1 && is_detected::value)>; template struct is_string : is_range_of_char>> {}; // Hashing. inline std::size_t hash_combine_(std::size_t seed, std::size_t value) noexcept { return seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); } // Why on earth!? It turns out that Clang's unique_ptr is (at least // sometimes) the size of two pointers, which is simply unreasonable. template struct uniq_ptr { using pointer = T *; uniq_ptr() = default; explicit uniq_ptr(T * ptr) : ptr_(ptr) {} uniq_ptr(uniq_ptr && other) : ptr_(other.ptr_) { other.ptr_ = nullptr; } template< typename U, typename Enable = std::enable_if_t::pointer, pointer>>> uniq_ptr(uniq_ptr && other) : ptr_(other.ptr_) { other.ptr_ = nullptr; } uniq_ptr & operator=(uniq_ptr && rhs) { std::swap(ptr_, rhs.ptr_); return *this; } T * operator->() const { return ptr_; } T * get() const { return ptr_; } private: T * ptr_ = nullptr; template friend struct uniq_ptr; }; template auto make_uniq(Args &&... args) { return uniq_ptr(new T{(Args &&) args...}); } struct value_impl_base { virtual ~value_impl_base() = 0; virtual detail::uniq_ptr copy_impl() const = 0; virtual value_kind kind() const noexcept = 0; virtual bool equal_impl(value const & rhs) const noexcept = 0; virtual std::ostream & to_json(std::ostream & os) const noexcept = 0; }; inline value_impl_base::~value_impl_base() {} template struct value_impl; template struct is_object : is_range_of< T, std::pair, std::pair> {}; template struct is_array : is_range_of {}; template struct get_impl; } struct value { value() = default; value(value const & other) { copy_impl(other); } value(value && other) { move_impl(std::move(other)); } value & operator=(value const & other) { storage_ = storage(); copy_impl(other); return *this; } value & operator=(value && other) { storage_ = storage(); move_impl(std::move(other)); return *this; } template value( JSONObject const & o, typename std::enable_if::value>:: type ** enable = nullptr); value(object && o); template value( JSONArray const & a, typename std::enable_if::value>::type ** enable = nullptr); value(array && a); value(double d); template value( String const & str, typename std::enable_if::value>::type ** enable = nullptr); value(std::string && str); value(std::string_view str); value(bool b); value(null_t); template typename std::enable_if::value, value &>:: type operator=(JSONObject const & o); value & operator=(object && o); template typename std::enable_if::value, value &>:: type operator=(JSONArray const & a); value & operator=(array && a); value & operator=(double d); template typename std::enable_if::value, value &>::type operator=(String const & str); value & operator=(std::string && str); value & operator=(std::string_view str); value & operator=(bool b); value & operator=(null_t); value_kind kind() const noexcept { auto k = storage_.local_.kind_; if (k == remote_string_k) return value_kind::string; return static_cast(k); } bool is_null() const noexcept { return kind() == value_kind::null; } bool is_boolean() const noexcept { return kind() == value_kind::boolean; } bool is_number() const noexcept { return kind() == value_kind::number; } bool is_string() const noexcept { return kind() == value_kind::string; } bool is_object() const noexcept { return kind() == value_kind::object; } bool is_array() const noexcept { return kind() == value_kind::array; } bool operator==(value const & rhs) const noexcept { if (rhs.kind() != kind()) return false; switch (storage_.local_.kind_) { case value::null_k: return true; case value::boolean_k: return storage_.local_.bytes_[3] == rhs.storage_.local_.bytes_[3]; case value::number_k: return *double_ptr() == *rhs.double_ptr(); case value::local_string_k: return strcmp( storage_.local_.bytes_.data(), rhs.storage_.local_.bytes_.data()) == 0; default: return storage_.remote_.ptr_->equal_impl(rhs); } return false; } bool operator!=(value const & rhs) const noexcept { return !(*this == rhs); } friend std::ostream & operator<<(std::ostream & os, value const & value); private: void copy_impl(value const & other) { if (other.is_local()) { storage_.local_ = other.storage_.local_; } else { storage_.remote_ = remote{other.storage_.remote_.kind_}; storage_.remote_.ptr_ = other.storage_.remote_.ptr_->copy_impl(); } } void move_impl(value && other) { if (other.is_local()) storage_.local_ = other.storage_.local_; else storage_.remote_ = std::move(other.storage_.remote_); } bool is_local() const noexcept { return storage_.local_.kind_ < object_k; } double const * double_ptr() const noexcept { double const * retval = reinterpret_cast( storage_.local_.bytes_.data() + 7); assert(std::uintptr_t(retval) % alignof(double) == 0); return retval; } double * double_ptr() noexcept { double * retval = reinterpret_cast(storage_.local_.bytes_.data() + 7); assert(std::uintptr_t(retval) % alignof(double) == 0); return retval; } enum value_impl_kind { null_k, boolean_k, number_k, local_string_k, object_k, array_k, remote_string_k }; struct local { uint8_t kind_; std::array bytes_; }; static_assert(sizeof(local) == 16); static_assert(alignof(std::array) == 1); struct remote { uint8_t kind_; detail::uniq_ptr ptr_; }; static_assert(sizeof(remote) <= 16); union storage { storage() : local_{null_k} {} storage & operator=(storage const & other) { destroy(); assert(other.local_.kind_ < object_k); local_ = other.local_; return *this; } ~storage() { destroy(); } void destroy() noexcept { if (local_.kind_ < object_k) local_.~local(); else remote_.~remote(); } local local_; remote remote_; }; storage storage_; template friend struct detail::get_impl; }; static_assert(sizeof(value) == 16); using diagnostic_function = std::function; namespace detail { template<> struct value_impl : value_impl_base { template value_impl(Iter first, Iter last) : value_(first, last) {} value_impl(object && o) : value_(std::move(o)) {} virtual detail::uniq_ptr copy_impl() const override { return detail::uniq_ptr( new value_impl(*this)); } virtual value_kind kind() const noexcept override { return value_kind::object; } virtual bool equal_impl(value const & rhs) const noexcept override { return value_ == get(rhs); } virtual std::ostream & to_json(std::ostream & os) const noexcept override; object value_; }; template<> struct value_impl : value_impl_base { template value_impl(Iter first, Iter last) : value_(first, last) {} value_impl(array && a) : value_(std::move(a)) {} virtual detail::uniq_ptr copy_impl() const override { return detail::uniq_ptr( new value_impl(*this)); } virtual value_kind kind() const noexcept override { return value_kind::array; } virtual bool equal_impl(value const & rhs) const noexcept override { return value_ == get(rhs); } virtual std::ostream & to_json(std::ostream & os) const noexcept override; array value_; }; template std::ostream & write_escaped_cps(std::ostream & os, CPSubRange subr) noexcept { os << std::hex << std::setfill('0'); for (auto cp : subr) { switch (cp) { case 0x0022: os << "\\\""; break; case 0x005c: os << "\\\\"; break; case 0x002f: os << "\\/"; break; case 0x0008: os << "\\b"; break; case 0x000c: os << "\\f"; break; case 0x000a: os << "\\n"; break; case 0x000d: os << "\\r"; break; case 0x0009: os << "\\t"; break; default: if (cp < 0x20) { os << "\\u" << std::setw(4) << (uint32_t)cp; } else { uint16_t const high_surrogate_base = 0xd7c0; uint16_t const low_surrogate_base = 0xdc00; auto const hi = static_cast(cp >> 10) + high_surrogate_base; auto const lo = static_cast(cp & 0x3ff) + low_surrogate_base; os << "\\u" << std::setw(4) << hi; os << "\\u" << std::setw(4) << lo; } break; } } os << std::dec; return os; } inline std::ostream & to_json(std::ostream & os, std::string_view s) noexcept { os << '"'; auto const r = boost::parser::as_utf32(s); char const * last_written_it = &*s.begin(); boost::parser::detail::text::foreach_subrange_if( r.begin(), r.end(), [](char32_t cp) { return cp < 0x0020 || 0xffff < cp; }, [&](auto subr) { os.write( last_written_it, &*subr.begin().base() - last_written_it); detail::write_escaped_cps(os, subr); last_written_it = &*subr.end().base(); }); os.write(last_written_it, &*s.end() - last_written_it); os << '"'; return os; } template<> struct value_impl : value_impl_base { template value_impl(Iter f, Iter l) : value_(f, l) {} value_impl(std::string && s) : value_(std::move(s)) {} value_impl(std::string_view s) : value_(s) {} virtual detail::uniq_ptr copy_impl() const override { return detail::uniq_ptr( new value_impl(*this)); } virtual value_kind kind() const noexcept override { return value_kind::string; } virtual bool equal_impl(value const & rhs) const noexcept override { return value_ == get(rhs); } virtual std::ostream & to_json(std::ostream & os) const noexcept override { return detail::to_json(os, value_); } std::string value_; }; template std::ptrdiff_t fast_distance_or_1000(Iter f, Sentinel l, CategoryTag tag) { return 1000; } template std::ptrdiff_t fast_distance_or_1000(Iter f, Iter l, std::random_access_iterator_tag) { return l - f; } inline std::ostream & value_impl::to_json(std::ostream & os) const noexcept { os << '{'; bool first = true; for (auto const & pair : value_) { if (!first) os << ", "; os << '"' << pair.first << "\": " << pair.second; first = false; } os << '}'; return os; } inline std::ostream & value_impl::to_json(std::ostream & os) const noexcept { os << '['; bool first = true; for (auto const & value : value_) { if (!first) os << ", "; os << value; first = false; } os << ']'; return os; } template<> struct get_impl { static object const & call(value const & v) noexcept { assert(v.is_object()); return static_cast *>( v.storage_.remote_.ptr_.get()) ->value_; } static object & call(value & v) noexcept { assert(v.is_object()); return static_cast *>( v.storage_.remote_.ptr_.get()) ->value_; } }; template<> struct get_impl { static array const & call(value const & v) noexcept { assert(v.is_array()); return static_cast *>( v.storage_.remote_.ptr_.get()) ->value_; } static array & call(value & v) noexcept { assert(v.is_array()); return static_cast *>( v.storage_.remote_.ptr_.get()) ->value_; } }; template<> struct get_impl { static double call(value const & v) noexcept { assert(v.is_number()); return *v.double_ptr(); } }; template<> struct get_impl { static std::string_view call(value const & v) noexcept { assert(v.is_string()); if (v.storage_.local_.kind_ == value::local_string_k) { return std::string_view(v.storage_.local_.bytes_.data()); } else { return static_cast *>( v.storage_.remote_.ptr_.get()) ->value_; } } }; template<> struct get_impl { static bool call(value const & v) noexcept { assert(v.is_boolean()); return v.storage_.local_.bytes_[3]; } }; template<> struct get_impl { static null_t call(value const & v) noexcept { assert(v.is_null()); return {}; } }; } template detail::get_result_t get(value const & v) noexcept { return detail::get_impl::call(v); } template detail::get_result_t get(value & v) noexcept { return detail::get_impl::call(v); } template value::value( JSONObject const & o, typename std::enable_if::value>::type ** enable) { storage_.remote_ = remote{ object_k, detail::make_uniq>(o.begin(), o.end())}; } inline value::value(object && o) { storage_.remote_ = remote{ object_k, detail::make_uniq>(std::move(o))}; } template value::value( JSONArray const & a, typename std::enable_if::value>::type ** enable) { storage_.remote_ = remote{ array_k, detail::make_uniq>(a.begin(), a.end())}; } inline value::value(array && a) { storage_.remote_ = remote{ array_k, detail::make_uniq>(std::move(a))}; } inline value::value(double d) { storage_.local_ = local{number_k}; *double_ptr() = d; } template value::value( String const & str, typename std::enable_if::value>::type ** enable) { auto const f = std::begin(str); auto const l = std::end(str); typename std::iterator_traits::iterator_category tag; if (detail::fast_distance_or_1000(f, l, tag) <= 14) { storage_.local_ = local{local_string_k}; *std::copy(f, l, storage_.local_.bytes_.data()) = 0; } else { storage_.remote_ = remote{remote_string_k}; storage_.remote_.ptr_ = detail::make_uniq>(f, l); } } inline value::value(std::string && s) { auto const f = std::begin(s); auto const l = std::end(s); typename std::iterator_traits::iterator_category tag; if (detail::fast_distance_or_1000(f, l, tag) <= 14) { storage_.local_ = local{local_string_k}; *std::copy(f, l, storage_.local_.bytes_.data()) = 0; } else { storage_.remote_ = remote{remote_string_k}; storage_.remote_.ptr_ = detail::make_uniq>(std::move(s)); } } inline value::value(std::string_view s) { auto const f = std::begin(s); auto const l = std::end(s); if (l - f <= 14) { storage_.local_ = local{local_string_k}; *std::copy(f, l, storage_.local_.bytes_.data()) = 0; } else { storage_.remote_ = remote{remote_string_k}; storage_.remote_.ptr_ = detail::make_uniq>(s); } } inline value::value(bool b) { storage_.local_ = local{boolean_k, {{0, 0, 0, b}}}; } inline value::value(null_t) : value() {} template typename std::enable_if::value, value &>::type value::operator=(JSONObject const & o) { if (is_object()) { get(*this) = object(o.begin(), o.end()); return *this; } return *this = value(o); } inline value & value::operator=(object && o) { if (is_object()) { get(*this) = std::move(o); return *this; } return *this = value(o); } template typename std::enable_if::value, value &>::type value::operator=(JSONArray const & a) { if (is_array()) { get(*this).assign(a.begin(), a.end()); return *this; } return *this = value(a); } inline value & value::operator=(array && a) { if (is_array()) { get(*this) = std::move(a); return *this; } return *this = value(a); } inline value & value::operator=(double d) { return *this = value(d); } template typename std::enable_if::value, value &>::type value::operator=(String const & str) { if (is_string()) { auto const f = std::begin(str); auto const l = std::end(str); typename std::iterator_traits::iterator_category tag; if (is_local() && detail::fast_distance_or_1000(f, l, tag) <= 14) { *std::copy(f, l, storage_.local_.bytes_.data()) = 0; } else { return static_cast *>( storage_.remote_.ptr_.get()) ->value_.assign(f, l); } return *this; } return *this = value(str); } inline value & value::operator=(std::string && s) { if (is_string()) { if (is_local() && s.size() <= 14u) { *std::copy(s.begin(), s.end(), storage_.local_.bytes_.data()) = 0; } else { static_cast *>( storage_.remote_.ptr_.get()) ->value_ = std::move(s); } return *this; } return *this = value(std::move(s)); } inline value & value::operator=(std::string_view s) { if (is_string()) { if (is_local() && s.size() <= 14u) { *std::copy(s.begin(), s.end(), storage_.local_.bytes_.data()) = 0; } else { static_cast *>( storage_.remote_.ptr_.get()) ->value_ = s; } return *this; } return *this = value(s); } inline value & value::operator=(bool b) { return *this = value(b); } inline value & value::operator=(null_t n) { return *this = value(n); } inline std::ostream & operator<<(std::ostream & os, value const & value) { switch (value.storage_.local_.kind_) { case value::null_k: return os << "null"; case value::boolean_k: return os << (value.storage_.local_.bytes_[3] ? "true" : "false"); case value::number_k: return os << *value.double_ptr(); case value::local_string_k: return detail::to_json(os, value.storage_.local_.bytes_.data()); default: return value.storage_.remote_.ptr_->to_json(os); } return os; } inline std::size_t hash_append(std::size_t seed, value const & v) { using detail::hash_combine_; std::size_t const kind_hash = std::hash{}(size_t(v.kind())); switch (v.kind()) { case value_kind::object: return hash_append(kind_hash, get(v)); case value_kind::array: return hash_append(kind_hash, get(v)); case value_kind::number: return hash_combine_( kind_hash, std::hash{}(get(v))); case value_kind::string: return hash_combine_( kind_hash, std::hash{}(get(v))); case value_kind::boolean: return hash_combine_(kind_hash, std::hash{}(get(v))); case value_kind::null: return kind_hash; } assert(!"Unreachable"); return 0; } inline std::size_t hash_append(std::size_t seed, object const & o) { using detail::hash_combine_; std::size_t retval = std::hash{}(size_t(value_kind::object)); for (auto const & pair : o) { retval = hash_combine_(retval, std::hash{}(pair.first)); retval = hash_append(retval, pair.second); } return retval; } inline std::size_t hash_append(std::size_t seed, array const & a) { std::size_t retval = std::hash{}(std::size_t(value_kind::array)); for (auto const & value : a) { retval = hash_append(retval, value); } return retval; } } namespace std { size_t hash::operator()(argument_type const & v) const noexcept { return json::hash_append(0, v); } size_t hash::operator()(argument_type const & o) const noexcept { return json::hash_append(0, o); } size_t hash::operator()(argument_type const & a) const noexcept { return json::hash_append(0, a); } } #endif