Skip to content

Instantly share code, notes, and snippets.

@Unbinilium
Last active June 13, 2025 15:29
Show Gist options
  • Save Unbinilium/e3ce76284f2e07f6cf3bbaca09597c13 to your computer and use it in GitHub Desktop.
Save Unbinilium/e3ce76284f2e07f6cf3bbaca09597c13 to your computer and use it in GitHub Desktop.
// JSerializer - An exceptionally fast, stream-oriented, header-only C++ JSON serialization library.
// MIT License Copyright (c) 2025 Unbinilium
#pragma once
#ifndef JSERIALIZER_HPP
#define JSERIALIZER_HPP
#include <array>
#include <cctype>
#include <cerrno>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <new>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#if !defined(__cplusplus) || __cplusplus < 202002L
#error "JSerializer requires C++20 or later"
#endif
#ifdef JS_BUFFER_SIZE_MIN
#warning "JS_BUFFER_SIZE_MIN is already defined, changing this is not recommended."
#else
#define JS_BUFFER_SIZE_MIN 256
#endif
#ifdef JS_FMT_BUFFER_SIZE_MIN
#warning "JS_FMT_BUFFER_SIZE_MIN is already defined, changing this is not recommended."
#else
#define JS_FMT_BUFFER_SIZE_MIN 32
#endif
#ifndef JS_DEFAULT_BUFFER_SIZE
#define JS_DEFAULT_BUFFER_SIZE (1024 * 16)
#endif
#ifndef JS_DEFAULT_FMT_BUFFER_SIZE
#define JS_DEFAULT_FMT_BUFFER_SIZE 64
#endif
#ifndef JS_DEFAULT_FLOATING_POINT_PRECISION
#define JS_DEFAULT_FLOATING_POINT_PRECISION 3
#endif
#ifndef JS_EXPERIMENTAL_SIMD
#if __has_include(<experimental/simd>)
#define JS_EXPERIMENTAL_SIMD 1
#else
#define JS_EXPERIMENTAL_SIMD 0
#endif
#endif
#if JS_EXPERIMENTAL_SIMD
#include <experimental/simd>
#if defined(__cpp_lib_experimental_parallel_simd) && __cpp_lib_experimental_parallel_simd >= 201803L
namespace stdx = std::experimental;
#ifndef JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR
#define JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR 3
#endif
#else
#undef JS_EXPERIMENTAL_SIMD
#define JS_EXPERIMENTAL_SIMD 0
#warning "JS_EXPERIMENTAL_SIMD is set to 0, since std::experimental::simd is not available on current compiler."
#endif
#endif
namespace ubn {
namespace traits {
template<typename, typename = std::void_t<>>
struct is_bounded_char_array: std::false_type
{
};
template<typename T>
struct is_bounded_char_array<T, std::void_t<decltype(std::declval<T>()[0])>>
: std::integral_constant<bool, std::is_same_v<std::remove_extent_t<T>, char>>
{
};
template<typename T>
inline constexpr bool is_bounded_char_array_v = is_bounded_char_array<T>::value;
template<typename, typename = std::void_t<>>
struct is_stl_container: std::false_type
{
};
template<typename T>
struct is_stl_container<T, std::void_t<typename T::iterator>>: std::true_type
{
};
template<typename T>
inline constexpr bool is_stl_container_v = is_stl_container<T>::value;
template<typename, typename = std::void_t<>>
struct is_stl_map_container: std::false_type
{
};
template<typename T>
struct is_stl_map_container<T, std::void_t<typename T::value_type, typename T::key_type, typename T::mapped_type>>
: std::integral_constant<bool,
std::is_same_v<typename T::value_type, std::pair<const typename T::key_type, typename T::mapped_type>>>
{
};
template<typename T>
inline constexpr bool is_stl_map_container_v = is_stl_map_container<T>::value;
template<typename>
struct is_variant: std::false_type
{
};
template<typename... Args>
struct is_variant<std::variant<Args...>>: std::true_type
{
};
template<typename T>
inline constexpr bool is_variant_v = is_variant<T>::value;
template<typename T, typename = std::void_t<>>
struct stl_container_value
{
using type = void;
};
template<typename T>
struct stl_container_value<T, std::void_t<typename T::value_type>>
{
using type = typename T::value_type;
};
template<typename T>
using stl_container_value_t = typename stl_container_value<T>::type;
} // namespace traits
namespace charconv {
namespace digits2 {
inline constexpr std::array<char, 200> make_digits() noexcept
{
alignas(2) std::array<char, 200> data {};
for (size_t i = 0; i < 100; ++i) {
data[i * 2] = static_cast<char>('0' + i / 10);
data[i * 2 + 1] = static_cast<char>('0' + i % 10);
}
return data;
}
alignas(2) inline constexpr auto table = make_digits();
inline constexpr const char* at(size_t index)
{
return &table[index];
}
} // namespace digits2
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint8_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
if (val < 100) {
if (val < 10) {
*buf = static_cast<char>('0' | val);
return buf + 1;
}
std::memcpy(buf, digits2::at(val << 1), 2);
return buf + 2;
}
else {
uint16_t l0_0 = (static_cast<uint16_t>(val) * static_cast<uint16_t>(41)) >> 12;
uint16_t l1_2 = val - (l0_0 * static_cast<uint16_t>(100));
*buf = static_cast<char>('0' | l0_0);
std::memcpy(buf + 1, digits2::at(l1_2 << 1), 2);
return buf + 3;
}
#pragma GCC diagnostic pop
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int8_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
uint8_t uval = static_cast<uint8_t>(val);
size_t sign = val < 0;
*buf = '-';
return itoa(sign ? static_cast<uint8_t>(~uval + 1) : uval, buf + sign);
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint16_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
if (val < 100) {
if (val < 10) {
*buf = static_cast<char>('0' | val);
return buf + 1;
}
std::memcpy(buf, digits2::at(val << 1), 2);
return buf + 2;
}
else if (val < 10'000) {
uint32_t l0_1 = (static_cast<uint32_t>(val) * static_cast<uint32_t>(5243)) >> 19;
uint32_t l2_3 = val - (l0_1 * static_cast<uint32_t>(100));
uint32_t leading_zero = l0_1 < 10;
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
buf -= leading_zero;
#pragma GCC diagnostic pop
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2);
return buf + 4;
}
else {
uint32_t l0_1 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(107375)) >> 30);
uint32_t l2_5 = val - (l0_1 * static_cast<uint32_t>(10'000));
uint32_t l2_3 = (l2_5 * static_cast<uint32_t>(5243)) >> 19;
uint32_t l4_5 = l2_5 - (l2_3 * static_cast<uint32_t>(100));
uint32_t leading_zero = l0_1 < 10;
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
buf -= leading_zero;
#pragma GCC diagnostic pop
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2);
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2);
return buf + 6;
}
#pragma GCC diagnostic pop
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int16_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
uint16_t uval = static_cast<uint16_t>(val);
size_t sign = val < 0;
*buf = '-';
return itoa(sign ? static_cast<uint16_t>(~uval + 1) : uval, buf + sign);
}
inline char* itoa_u32_lt_1e2(uint32_t val, char* buf) noexcept
{
if (val < 10) {
*buf = static_cast<char>('0' | val);
return buf + 1;
}
std::memcpy(buf, digits2::at(val << 1), 2);
return buf + 2;
}
inline char* itoa_u32_ge_1e2_lt_1e4(uint32_t val, char* buf) noexcept
{
uint32_t l0_1 = (val * static_cast<uint32_t>(5243)) >> 19;
uint32_t l2_3 = val - (l0_1 * static_cast<uint32_t>(100));
uint32_t leading_zero = l0_1 < 10;
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
buf -= leading_zero;
#pragma GCC diagnostic pop
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2);
return buf + 4;
}
inline char* itoa_u32_ge_1e4_lt_1e6(uint32_t val, char* buf) noexcept
{
uint32_t l0_1 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(429497)) >> 32);
uint32_t l2_5 = val - (l0_1 * static_cast<uint32_t>(10'000));
uint32_t l2_3 = (l2_5 * static_cast<uint32_t>(5243)) >> 19;
uint32_t l4_5 = l2_5 - (l2_3 * static_cast<uint32_t>(100));
uint32_t leading_zero = l0_1 < 10;
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
buf -= leading_zero;
#pragma GCC diagnostic pop
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2);
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2);
return buf + 6;
}
inline char* itoa_u32_ge_1e6_lt_1e8(uint32_t val, char* buf) noexcept
{
uint32_t l0_3 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(109951163)) >> 40);
uint32_t l4_7 = val - (l0_3 * static_cast<uint32_t>(10'000));
uint32_t l0_1 = (l0_3 * static_cast<uint32_t>(5243)) >> 19;
uint32_t l2_3 = l0_3 - (l0_1 * static_cast<uint32_t>(100));
uint32_t l4_5 = (l4_7 * static_cast<uint32_t>(5243)) >> 19;
uint32_t l6_7 = l4_7 - (l4_5 * static_cast<uint32_t>(100));
uint32_t leading_zero = l0_1 < 10;
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
buf -= leading_zero;
#pragma GCC diagnostic pop
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2);
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2);
std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2);
return buf + 8;
}
inline char* itoa_u32_ge_1e8_lt_1ea(uint32_t val, char* buf) noexcept
{
uint32_t l0_5 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(3518437209UL)) >> 45);
uint32_t l0_1 = static_cast<uint32_t>((static_cast<uint64_t>(l0_5) * static_cast<uint64_t>(429497)) >> 32);
uint32_t l2_5 = l0_5 - (l0_1 * static_cast<uint32_t>(10'000));
uint32_t l2_3 = (l2_5 * static_cast<uint32_t>(5243)) >> 19;
uint32_t l4_5 = l2_5 - (l2_3 * static_cast<uint32_t>(100));
uint32_t l6_9 = val - (l0_5 * static_cast<uint32_t>(10'000));
uint32_t l6_7 = (l6_9 * static_cast<uint32_t>(5243)) >> 19;
uint32_t l8_9 = l6_9 - (l6_7 * static_cast<uint32_t>(100));
uint32_t leading_zero = l0_1 < 10;
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
buf -= leading_zero;
#pragma GCC diagnostic pop
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2);
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2);
std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2);
std::memcpy(buf + 8, digits2::at(l8_9 << 1), 2);
return buf + 10;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint32_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
if (val < 100) {
return itoa_u32_lt_1e2(val, buf);
}
else if (val < 10'000) {
return itoa_u32_ge_1e2_lt_1e4(val, buf);
}
else if (val < 1'000'000) {
return itoa_u32_ge_1e4_lt_1e6(val, buf);
}
else if (val < 100'000'000) {
return itoa_u32_ge_1e6_lt_1e8(val, buf);
}
else {
return itoa_u32_ge_1e8_lt_1ea(val, buf);
}
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int32_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
uint32_t uval = static_cast<uint32_t>(val);
size_t sign = val < 0;
*buf = '-';
return itoa(sign ? static_cast<uint32_t>(~uval + 1) : uval, buf + sign);
}
inline char* itoa_u32_lt_1e8(uint32_t val, char* buf) noexcept
{
if (val < 100) {
return itoa_u32_lt_1e2(val, buf);
}
else if (val < 10'000) {
return itoa_u32_ge_1e2_lt_1e4(val, buf);
}
else if (val < 1'000'000) {
return itoa_u32_ge_1e4_lt_1e6(val, buf);
}
else {
return itoa_u32_ge_1e6_lt_1e8(val, buf);
}
}
inline char* itoa_u32_ge_1e4_lt_1e8(uint32_t val, char* buf) noexcept
{
if (val < 1'000'000) {
return itoa_u32_ge_1e4_lt_1e6(val, buf);
}
else {
return itoa_u32_ge_1e6_lt_1e8(val, buf);
}
}
inline char* itoa_u64_ge_1e8_lt_1e16(uint64_t val, char* buf) noexcept
{
uint64_t l0_7 = val / static_cast<uint64_t>(100'000'000);
uint32_t l8_f = static_cast<uint32_t>(val - (l0_7 * static_cast<uint64_t>(100'000'000)));
buf = itoa_u32_lt_1e8(static_cast<uint32_t>(l0_7), buf);
buf = itoa_u32_ge_1e6_lt_1e8(l8_f, buf);
return buf;
}
inline char* itoa_u64_ge_1e16_lt_1e20(uint64_t val, char* buf) noexcept
{
uint64_t l0_b = val / static_cast<uint64_t>(100'000'000);
uint32_t r0_7 = static_cast<uint32_t>(val - (l0_b * static_cast<uint64_t>(100'000'000)));
uint64_t l0_3 = l0_b / static_cast<uint64_t>(10'000);
uint32_t l4_7 = static_cast<uint32_t>(l0_b - (l0_3 * static_cast<uint64_t>(10'000)));
buf = itoa_u32_ge_1e4_lt_1e8(static_cast<uint32_t>(l0_3), buf);
buf = itoa_u32_ge_1e2_lt_1e4(l4_7, buf);
buf = itoa_u32_ge_1e6_lt_1e8(r0_7, buf);
return buf;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint64_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
if (val < 100'000'000) {
return itoa_u32_lt_1e8(static_cast<uint32_t>(val), buf);
}
else if (val < static_cast<uint64_t>(100'000'000'000'000ULL)) {
return itoa_u64_ge_1e8_lt_1e16(val, buf);
}
else {
return itoa_u64_ge_1e16_lt_1e20(val, buf);
}
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int64_t), bool> = true>
inline constexpr char* itoa(T&& val, char* buf) noexcept
{
uint64_t uval = static_cast<uint64_t>(val);
size_t sign = val < 0;
*buf = '-';
return itoa(sign ? static_cast<uint64_t>(~uval + 1) : uval, buf + sign);
}
} // namespace charconv
class JSerializer final
{
public:
using WriteCallback = int (*)(const void*, size_t) noexcept;
using FlushCallback = int (*)(int) noexcept;
struct StdoutStream final
{
static inline constexpr int write(const void* data, size_t size) noexcept
{
if (!data || size == 0) [[unlikely]] {
return 0;
}
return static_cast<int>(std::fwrite(data, 1, size, stdout));
}
static inline int flush(int) noexcept
{
std::fwrite("\n", 1, 1, stdout);
return std::fflush(stdout);
}
};
static inline constexpr const size_t const_buffer_size_min = JS_BUFFER_SIZE_MIN;
static inline constexpr const size_t const_fmt_buffer_size_min = JS_FMT_BUFFER_SIZE_MIN;
#if JS_EXPERIMENTAL_SIMD
static inline constexpr const size_t const_simd_memory_alignment
= stdx::memory_alignment_v<stdx::native_simd<unsigned char>>;
static inline constexpr const size_t const_simd_memory_alignment_mask = const_simd_memory_alignment - 1;
static_assert((const_simd_memory_alignment & const_simd_memory_alignment_mask) == 0,
"SIMD memory alignment must be a power of two");
static inline constexpr const int const_simd_size
= stdx::simd_size_v<unsigned char, stdx::simd_abi::native<unsigned char>>;
#endif
static inline constexpr const size_t default_buffer_size = JS_DEFAULT_BUFFER_SIZE;
static inline constexpr const size_t default_fmt_buffer_size = JS_DEFAULT_FMT_BUFFER_SIZE;
static inline constexpr const int default_floating_point_precision = JS_DEFAULT_FLOATING_POINT_PRECISION;
static_assert(default_buffer_size >= const_buffer_size_min,
"Default buffer size must be at least const_buffer_size_min bytes");
static_assert(const_fmt_buffer_size_min <= default_fmt_buffer_size && default_fmt_buffer_size < default_buffer_size,
"Default format buffer size must be between const_fmt_buffer_size_min and default_buffer_size (exclusive)");
static_assert(default_floating_point_precision < default_fmt_buffer_size,
"Default floating point precision must be less than default_fmt_buffer_size");
#if JS_EXPERIMENTAL_SIMD
static inline constexpr const size_t default_simd_enable_len_factor = JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR;
static inline constexpr const size_t default_simd_enable_len = const_simd_size * default_simd_enable_len_factor;
static_assert(default_simd_enable_len_factor > 1, "Default SIMD enable length factor must be greater than 1");
static_assert(default_simd_enable_len <= default_buffer_size,
"Default SIMD enable length must be less than or equal to default_buffer_size");
static_assert((default_simd_enable_len - const_simd_size) >= const_simd_memory_alignment,
"Default SIMD enable length minus default SIMD size must be greater than or equal to "
"const_simd_memory_alignment");
#endif
class StringWriter;
class ForwardWriter;
class BooleanWriter final
{
protected:
friend class StringWriter;
friend class ForwardWriter;
inline constexpr explicit BooleanWriter(JSerializer& serializer) noexcept : _serializer(serializer) { }
public:
template<typename T>
static inline constexpr bool is_boolean_v = std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, bool>;
~BooleanWriter() noexcept { }
inline constexpr void operator<<(bool value) noexcept
{
if (value) {
_serializer.write("true");
}
else {
_serializer.write("false");
}
}
private:
JSerializer& _serializer;
};
class NumberWriter final
{
protected:
friend class StringWriter;
friend class ForwardWriter;
inline constexpr explicit NumberWriter(JSerializer& serializer) noexcept : _serializer(serializer) { }
public:
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>>
static inline constexpr bool is_number_v
= (std::is_integral_v<U> || std::is_floating_point_v<U>) && !std::is_same_v<U, bool>;
~NumberWriter() noexcept { }
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_integral_v<U>, bool> = true>
inline constexpr void operator<<(T&& value) noexcept
{
if (!_serializer.write(std::forward<T>(value))) [[unlikely]] {
_serializer.write("null");
}
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_integral_v<U>, bool> = true>
inline constexpr void operator+=(T&& value) noexcept
{
operator<<(std::forward<T>(value));
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_floating_point_v<U>, bool> = true>
inline constexpr void operator<<(T&& value) noexcept
{
if (std::isnan(value) || std::isinf(value)) [[unlikely]] {
_serializer.write("null");
return;
}
else if (!_serializer.write(std::forward<T>(value), JSerializer::default_floating_point_precision))
[[unlikely]] {
_serializer.write("null");
}
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_floating_point_v<U>, bool> = true>
inline constexpr void operator+=(T&& value) noexcept
{
if (std::isnan(value) || std::isinf(value)) [[unlikely]] {
_serializer.write("null");
return;
}
else if (!_serializer.write(std::forward<T>(value))) [[unlikely]] {
_serializer.write("null");
}
}
private:
JSerializer& _serializer;
};
class StringWriter final
{
protected:
friend class ForwardWriter;
inline constexpr explicit StringWriter(JSerializer& serializer) noexcept : _serializer(serializer)
{
_serializer.write('"');
}
public:
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>>
static inline constexpr bool is_string_v
= (std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>))
|| traits::is_bounded_char_array_v<U> || std::is_same_v<U, std::string>
|| std::is_same_v<U, std::string_view>;
~StringWriter() noexcept
{
_serializer.write('"');
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool>
= true>
inline constexpr StringWriter& operator+=(T&& str) noexcept
{
_serializer.write(static_cast<const void*>(str), std::char_traits<char>::length(str));
return *this;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true>
inline constexpr StringWriter& operator+=(T&& str) noexcept
{
_serializer.write(static_cast<const void*>(str), sizeof(str) - 1);
return *this;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true>
inline constexpr StringWriter& operator+=(T&& str) noexcept
{
_serializer.write(static_cast<const void*>(str.data()), str.size());
return *this;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool>
= true>
inline constexpr StringWriter& operator<<(T&& str) noexcept
{
_serializer.write(str, std::char_traits<char>::length(str));
return *this;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true>
inline constexpr StringWriter& operator<<(T&& str) noexcept
{
_serializer.write(str, sizeof(str) - 1);
return *this;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true>
inline constexpr StringWriter& operator<<(T&& str) noexcept
{
_serializer.write(str.data(), str.size());
return *this;
}
template<typename T, std::enable_if_t<BooleanWriter::is_boolean_v<T>, bool> = true>
inline constexpr StringWriter& operator<<(T&& value) noexcept
{
BooleanWriter { _serializer } << std::forward<T>(value);
return *this;
}
template<typename T, std::enable_if_t<NumberWriter::is_number_v<T>, bool> = true>
inline constexpr StringWriter& operator<<(T&& value) noexcept
{
NumberWriter { _serializer } << std::forward<T>(value);
return *this;
}
inline constexpr int write(const void* data, size_t size) noexcept
{
if (!data || size == 0) [[unlikely]] {
return 0;
}
const int sta = _serializer.state();
if (sta == 0 && _serializer.write(data, size)) [[likely]] {
return static_cast<int>(size);
}
return -sta;
}
private:
JSerializer& _serializer;
};
class ObjectWriter;
template<typename F, std::enable_if_t<std::is_same_v<F, ForwardWriter>, bool> = true>
class ArrayWriter final
{
protected:
friend class ForwardWriter;
inline constexpr explicit ArrayWriter(JSerializer& serializer) noexcept
: _serializer(serializer), _forward_writer(_serializer), _multiple_items(false)
{
_serializer.write('[');
}
public:
~ArrayWriter() noexcept
{
_serializer.write(']');
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, ObjectWriter> || std::is_same_v<U, ArrayWriter<ForwardWriter>>, bool>
= true>
inline constexpr U writer() noexcept
{
if (_multiple_items) [[likely]] {
_serializer.write(',');
}
else [[unlikely]] {
_multiple_items = true;
}
return U { _serializer };
}
template<typename T>
inline constexpr ArrayWriter& operator<<(T&& value) noexcept
{
if (_multiple_items) [[likely]] {
_serializer.write(',');
}
else [[unlikely]] {
_multiple_items = true;
}
_forward_writer << std::forward<T>(value);
return *this;
}
template<typename T>
inline constexpr ArrayWriter& operator+=(T&& value) noexcept
{
if (_multiple_items) [[likely]] {
_serializer.write(',');
}
else [[unlikely]] {
_multiple_items = true;
}
_forward_writer += std::forward<T>(value);
return *this;
}
private:
JSerializer& _serializer;
F _forward_writer;
bool _multiple_items;
};
class ObjectWriter final
{
protected:
friend class JSerializer;
friend class ForwardWriter;
friend class ArrayWriter<ForwardWriter>;
inline explicit ObjectWriter(JSerializer& serializer, bool is_base_writer = false) noexcept
: _serializer(serializer), _is_base_writer(is_base_writer), _multiple_items(false)
{
_serializer.write('{');
}
public:
~ObjectWriter() noexcept
{
_serializer.write('}');
if (_is_base_writer) [[unlikely]] {
_serializer.flush();
}
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool>
= true>
inline ForwardWriter operator[](T&& key) noexcept
{
write(static_cast<const void*>(key), std::char_traits<char>::length(key));
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true>
inline ForwardWriter operator[](T&& key) noexcept
{
write(static_cast<const void*>(&key), sizeof(key) - 1);
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true>
inline ForwardWriter operator[](T&& key) noexcept
{
write(static_cast<const void*>(key.data()), key.size());
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_integral_v<U>, bool> = true>
inline ForwardWriter operator[](T&& key) noexcept
{
write(std::forward<T>(key));
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool>
= true>
inline ForwardWriter operator()(T&& key) noexcept
{
write(key, std::char_traits<char>::length(key));
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true>
inline ForwardWriter operator()(T&& key) noexcept
{
write(key, sizeof(key) - 1);
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true>
inline ForwardWriter operator()(T&& key) noexcept
{
write(key.data(), key.size());
return ForwardWriter { _serializer };
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true>
inline constexpr ObjectWriter& operator<<(T&& map) noexcept
{
for (const auto& [key, value]: map) {
operator()(key) << value;
}
return *this;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true>
inline constexpr ObjectWriter& operator+=(T&& map) noexcept
{
for (const auto& [key, value]: map) {
operator[](key) += value;
}
return *this;
}
private:
template<typename T>
inline constexpr void write(T&& value, size_t len) noexcept
{
if (!len) [[unlikely]] {
return;
}
if (_multiple_items) [[likely]] {
_serializer.write(',');
}
else [[unlikely]] {
_multiple_items = true;
}
_serializer.write('"');
_serializer.write(std::forward<T>(value), len);
_serializer.write("\":");
}
template<typename T>
inline constexpr void write(T&& value) noexcept
{
if (_multiple_items) [[likely]] {
_serializer.write(',');
}
else [[unlikely]] {
_multiple_items = true;
}
_serializer.write('"');
_serializer.write(std::forward<T>(value));
_serializer.write("\":");
}
JSerializer& _serializer;
const bool _is_base_writer;
bool _multiple_items;
};
class ForwardWriter final
{
protected:
friend class StringWriter;
friend class ObjectWriter;
friend class ArrayWriter<ForwardWriter>;
inline constexpr explicit ForwardWriter(JSerializer& serializer) noexcept : _serializer(serializer) { }
public:
~ForwardWriter() noexcept { }
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, StringWriter> || std::is_same_v<U, ObjectWriter>
|| std::is_same_v<U, ArrayWriter<ForwardWriter>>,
bool>
= true>
inline constexpr U writer() noexcept
{
return U { _serializer };
}
inline ObjectWriter operator()() noexcept
{
return ObjectWriter { _serializer };
}
template<typename T, std::enable_if_t<BooleanWriter::is_boolean_v<T>, bool> = true>
inline constexpr void operator<<(T&& value) noexcept
{
BooleanWriter { _serializer } << std::forward<T>(value);
}
template<typename T, std::enable_if_t<BooleanWriter::is_boolean_v<T>, bool> = true>
inline constexpr void operator+=(T&& value) noexcept
{
BooleanWriter { _serializer } << std::forward<T>(value);
}
template<typename T, std::enable_if_t<NumberWriter::is_number_v<T>, bool> = true>
inline constexpr void operator<<(T&& value) noexcept
{
NumberWriter { _serializer } << std::forward<T>(value);
}
template<typename T, std::enable_if_t<NumberWriter::is_number_v<T>, bool> = true>
inline constexpr void operator+=(T&& value) noexcept
{
NumberWriter { _serializer } += std::forward<T>(value);
}
template<typename T, std::enable_if_t<StringWriter::is_string_v<T>, bool> = true>
inline StringWriter operator<<(T&& value) noexcept
{
StringWriter writer { _serializer };
writer << std::forward<T>(value);
return writer;
}
template<typename T, std::enable_if_t<StringWriter::is_string_v<T>, bool> = true>
inline StringWriter operator+=(T&& value) noexcept
{
StringWriter writer { _serializer };
writer += std::forward<T>(value);
return writer;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_variant_v<U>, bool> = true>
inline constexpr void operator<<(T&& variant) noexcept
{
if (variant.valueless_by_exception()) [[unlikely]] {
_serializer.write("null");
return;
}
std::visit([this](const auto& value) constexpr noexcept { *this << value; }, std::forward<T>(variant));
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_variant_v<U>, bool> = true>
inline constexpr void operator+=(T&& variant) noexcept
{
if (variant.valueless_by_exception()) [[unlikely]] {
_serializer.write("null");
return;
}
std::visit([this](const auto& value) constexpr noexcept { *this += value; }, std::forward<T>(variant));
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_stl_container_v<U> && !StringWriter::is_string_v<T>
&& !traits::is_stl_map_container_v<U>,
bool>
= true>
inline ArrayWriter<ForwardWriter> operator<<(T&& value) noexcept
{
ArrayWriter<ForwardWriter> writer { _serializer };
for (const auto& item: value) {
writer << item;
}
return writer;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_stl_container_v<U> && !StringWriter::is_string_v<T>
&& !traits::is_stl_map_container_v<U>,
bool>
= true>
inline ArrayWriter<ForwardWriter> operator+=(T&& value) noexcept
{
ArrayWriter<ForwardWriter> writer { _serializer };
for (const auto& item: value) {
writer += item;
}
return writer;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true>
inline ObjectWriter operator<<(T&& map) noexcept
{
ObjectWriter writer { _serializer };
for (const auto& [key, value]: map) {
writer(key) << value;
}
return writer;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true>
inline ObjectWriter operator+=(T&& map) noexcept
{
ObjectWriter writer { _serializer };
for (const auto& [key, value]: map) {
writer[key] += value;
}
return writer;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::nullptr_t>, bool> = true>
inline constexpr void operator<<(T&&) noexcept
{
_serializer.write("null");
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::nullptr_t>, bool> = true>
inline constexpr void operator+=(T&&) noexcept
{
_serializer.write("null");
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::monostate>, bool> = true>
inline constexpr void operator<<(T&&) noexcept
{
_serializer.write("null");
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_same_v<U, std::monostate>, bool> = true>
inline constexpr void operator+=(T&&) noexcept
{
_serializer.write("null");
}
private:
JSerializer& _serializer;
};
friend class BooleanWriter;
friend class NumberWriter;
friend class StringWriter;
friend class ArrayWriter<ForwardWriter>;
friend class ObjectWriter;
friend class ForwardWriter;
template<typename T = std::unique_ptr<JSerializer>, typename U = std::remove_cv_t<std::remove_reference_t<T>>>
[[nodiscard]] static constexpr U create(WriteCallback write_callback = StdoutStream::write,
FlushCallback flush_callback = StdoutStream::flush, void* buffer = nullptr, size_t size = 0) noexcept
{
if (!write_callback) [[unlikely]] {
write_callback = StdoutStream::write;
}
if (!flush_callback) [[unlikely]] {
flush_callback = StdoutStream::flush;
}
size = size ? size : default_buffer_size;
if (size < const_buffer_size_min) [[unlikely]] {
return U {};
}
bool internal_buffer = false;
if (!buffer) {
buffer = static_cast<void*>(new (std::nothrow) unsigned char[size]);
if (!buffer) [[unlikely]] {
return U {};
}
internal_buffer = true;
}
U ptr { new (std::nothrow) JSerializer(write_callback, flush_callback, internal_buffer,
static_cast<unsigned char*>(buffer), size) };
if (!ptr) [[unlikely]] {
if (internal_buffer) [[likely]] {
delete[] static_cast<unsigned char*>(buffer);
}
return U {};
}
return ptr;
}
~JSerializer() noexcept
{
if (_internal_buffer && _buffer) [[likely]] {
delete[] _buffer;
}
_buffer = nullptr;
}
JSerializer(const JSerializer&) = delete;
JSerializer& operator=(const JSerializer&) = delete;
JSerializer(JSerializer&&) = delete;
JSerializer& operator=(JSerializer&&) = delete;
[[nodiscard]] inline ObjectWriter writer(WriteCallback write_callback = nullptr,
FlushCallback flush_callback = nullptr) noexcept
{
if (write_callback) {
_write_callback = write_callback;
}
if (flush_callback) {
_flush_callback = flush_callback;
}
return ObjectWriter { *this, true };
}
inline constexpr int state() const noexcept
{
return _error_code;
}
bool reset() noexcept
{
_error_code = 0;
_buffer = _buffer_s;
return _error_code == 0;
}
protected:
explicit JSerializer(WriteCallback write_callback, FlushCallback flush_callback, bool internal_buffer,
unsigned char* buffer, size_t size) noexcept
: _write_callback(write_callback), _flush_callback(flush_callback), _internal_buffer(internal_buffer),
_buffer_size(size), _buffer_s(buffer), _buffer_e(_buffer_s + _buffer_size),
_buffer_e_fmt(_buffer_e - default_fmt_buffer_size), _buffer(_buffer_s), _error_code(0)
{
}
constexpr void error(int error_code) noexcept
{
if (_error_code == 0) {
_error_code = error_code;
}
}
inline constexpr void sync() noexcept
{
const unsigned char* end = _buffer;
const unsigned char* ptr = _buffer_s;
while (ptr < end) [[likely]] {
const int ret = _write_callback(ptr, static_cast<size_t>(end - ptr));
if (ret < 0) [[unlikely]] {
error(-ret);
break;
}
ptr += ret;
}
_buffer = _buffer_s;
}
inline constexpr void flush() noexcept
{
if (_buffer > _buffer_s) [[likely]] {
sync();
}
const int ret = _flush_callback(_error_code);
if (ret < 0) [[unlikely]] {
error(-ret);
}
}
inline constexpr void write(const char symbol) noexcept
{
unsigned char* buffer = _buffer;
if (buffer >= _buffer_e) [[unlikely]] {
sync();
buffer = _buffer;
}
*buffer = static_cast<unsigned char>(symbol);
_buffer = buffer + 1;
}
template<size_t N, std::enable_if_t<N != 1, bool> = true>
inline constexpr void write(const char (&symbol)[N]) noexcept
{
constexpr const size_t size = N - 1;
unsigned char* buffer = _buffer;
unsigned char* buffer_e = buffer + size;
if (buffer_e > _buffer_e) [[unlikely]] {
sync();
buffer = _buffer;
buffer_e = buffer + size;
}
std::memcpy(buffer, symbol, size);
_buffer = buffer_e;
}
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>,
std::enable_if_t<std::is_integral_v<U>
&& (sizeof(U) == sizeof(uint8_t) || sizeof(U) == sizeof(uint16_t)
|| sizeof(U) == sizeof(uint32_t) || sizeof(U) == sizeof(uint64_t)),
bool>
= true>
inline constexpr bool write(T&& value) noexcept
{
if (_buffer > _buffer_e_fmt) [[unlikely]] {
sync();
}
char* buffer = reinterpret_cast<char*>(_buffer);
char* ptr = charconv::itoa(std::forward<T>(value), buffer);
_buffer = reinterpret_cast<unsigned char*>(ptr);
return true;
}
template<typename T,
std::enable_if_t<std::is_floating_point_v<std::remove_cv_t<std::remove_reference_t<T>>>, bool> = true>
inline constexpr bool write(T&& value) noexcept
{
if (_buffer > _buffer_e_fmt) [[unlikely]] {
sync();
}
char* buffer = reinterpret_cast<char*>(_buffer);
char* buffer_e = reinterpret_cast<char*>(_buffer_e);
const auto [ptr, ec] = std::to_chars(buffer, buffer_e, value, std::chars_format::fixed);
if (ec != std::errc()) [[unlikely]] {
error(static_cast<int>(ec));
return false;
}
_buffer = reinterpret_cast<unsigned char*>(ptr);
return true;
}
template<typename T,
std::enable_if_t<std::is_floating_point_v<std::remove_cv_t<std::remove_reference_t<T>>>, bool> = true>
inline constexpr bool write(T&& value, int precision) noexcept
{
if (_buffer > _buffer_e_fmt) [[unlikely]] {
sync();
}
char* buffer = reinterpret_cast<char*>(_buffer);
char* buffer_e = reinterpret_cast<char*>(_buffer_e);
const auto [ptr, ec] = std::to_chars(buffer, buffer_e, value, std::chars_format::general, precision);
if (ec != std::errc()) [[unlikely]] {
error(static_cast<int>(ec));
return false;
}
_buffer = reinterpret_cast<unsigned char*>(ptr);
return true;
}
#if JS_EXPERIMENTAL_SIMD
inline constexpr void vwrite(const char* value, size_t len) noexcept
{
const char* value_end = value + len;
const char* value_end_simd = value_end - const_simd_size;
const char* value_pos = value;
while (value < value_end_simd) [[likely]] {
const stdx::fixed_size_simd<unsigned char, const_simd_size> chunk {
reinterpret_cast<const unsigned char*>(value), stdx::vector_aligned
};
const auto escape { (chunk == _simd_escape_quotes) | (chunk == _simd_escape_backslashes)
| (chunk == _simd_escape_cntrl_eqb) | (chunk <= _simd_escape_cntrl_leb) };
if (stdx::any_of(escape)) [[unlikely]] {
const int bad_s = stdx::find_first_set(escape);
const int bad_e = stdx::find_last_set(escape);
const char* value_cur = value + bad_s;
size_t size = static_cast<size_t>(value_cur - value_pos);
if (size) [[likely]] {
write(static_cast<const void*>(value_pos), size);
}
size = static_cast<size_t>((bad_e - bad_s) + 1);
swrite(value_cur, size);
value_pos = value_cur + size;
}
value += const_simd_size;
}
if (value_pos < value_end) [[likely]] {
swrite(value_pos, static_cast<size_t>(value_end - value_pos));
}
}
#endif
inline constexpr void swrite(const char* value, size_t len) noexcept
{
const char* value_end = value + len;
const char* value_pos = value;
while (value < value_end) [[likely]] {
const char c = *value;
if (c == '"') [[unlikely]] {
const size_t size = static_cast<size_t>(value - value_pos);
if (size) [[likely]] {
write(static_cast<const void*>(value_pos), size);
}
value_pos = ++value;
write("\\\"");
continue;
}
else if (c == '\\') [[unlikely]] {
const size_t size = static_cast<size_t>(value - value_pos);
if (size) [[likely]] {
write(static_cast<const void*>(value_pos), size);
}
value_pos = ++value;
write("\\\\");
continue;
}
else if (std::iscntrl(static_cast<unsigned char>(c))) [[unlikely]] {
const size_t size = static_cast<size_t>(value - value_pos);
if (size) [[likely]] {
write(static_cast<const void*>(value_pos), size);
}
value_pos = ++value;
switch (c) {
case '\t':
write("\\t");
continue;
case '\n':
write("\\n");
continue;
case '\f':
write("\\f");
continue;
case '\r':
write("\\r");
continue;
case '\b':
write("\\b");
continue;
default:
const char unicode[6]
= { '\\', 'u', '0', '0', static_cast<char>(_hex_lookup_tbl[c >> 4]),
static_cast<char>(_hex_lookup_tbl[c & 0x0F]) };
write(static_cast<const void*>(unicode), sizeof(unicode));
continue;
}
}
++value;
}
if (value_pos < value_end) [[likely]] {
write(static_cast<const void*>(value_pos), static_cast<size_t>(value_end - value_pos));
}
}
inline constexpr void write(const char* value, size_t len) noexcept
{
#if JS_EXPERIMENTAL_SIMD
if (len < default_simd_enable_len) [[likely]] {
swrite(value, len);
return;
}
const size_t align_shift = reinterpret_cast<std::uintptr_t>(value) & const_simd_memory_alignment_mask;
if (align_shift) {
const size_t align_len = const_simd_memory_alignment - align_shift;
swrite(value, align_len);
value += align_len;
len -= align_len;
}
vwrite(value, len);
#else
swrite(value, len);
#endif
}
inline constexpr bool write(const void* data, size_t size) noexcept
{
unsigned char* buffer = _buffer;
unsigned char* buffer_e = buffer + size;
if (buffer_e <= _buffer_e) {
std::memcpy(buffer, data, size);
_buffer = buffer_e;
return true;
}
sync();
if (size <= _buffer_size) [[likely]] {
buffer = _buffer;
std::memcpy(buffer, data, size);
_buffer = buffer + size;
return true;
}
const unsigned char* data_ptr = static_cast<const unsigned char*>(data);
const unsigned char* data_end = data_ptr + size;
while (data_ptr < data_end) [[likely]] {
const int ret = _write_callback(data_ptr, static_cast<size_t>(data_end - data_ptr));
if (ret < 0) [[unlikely]] {
error(-ret);
return false;
}
data_ptr += ret;
}
return true;
}
private:
WriteCallback _write_callback;
FlushCallback _flush_callback;
const bool _internal_buffer;
const size_t _buffer_size;
unsigned char* _buffer_s;
unsigned char* _buffer_e;
unsigned char* _buffer_e_fmt;
unsigned char* _buffer;
int _error_code;
private:
alignas(std::hardware_destructive_interference_size) static inline constexpr const
char _hex_lookup_tbl[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
#if JS_EXPERIMENTAL_SIMD
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_quotes {
static_cast<unsigned char>('"')
};
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_backslashes {
static_cast<unsigned char>('\\')
};
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_cntrl_leb {
static_cast<unsigned char>(0x1F)
};
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_cntrl_eqb {
static_cast<unsigned char>(0x7F)
};
#endif
};
using StdoutStream = JSerializer::StdoutStream;
using BooleanWriter = JSerializer::BooleanWriter;
using NumberWriter = JSerializer::NumberWriter;
using StringWriter = JSerializer::StringWriter;
using ObjectWriter = JSerializer::ObjectWriter;
using ForwardWriter = JSerializer::ForwardWriter;
using ArrayWriter = JSerializer::ArrayWriter<ForwardWriter>;
#undef JS_BUFFER_SIZE_MIN
#undef JS_FMT_BUFFER_SIZE_MIN
} // namespace ubn
#endif // JSERIALIZER_HPP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment