diff options
| author | Stefan Boberg <[email protected]> | 2021-09-27 12:34:52 +0200 |
|---|---|---|
| committer | Stefan Boberg <[email protected]> | 2021-09-27 12:34:52 +0200 |
| commit | f0036eada7f6bcf6e08afe3ea8517367ed73450e (patch) | |
| tree | b1ce3466bba36175cad369028fad1b410a34b5ec /zencore/compactbinary.cpp | |
| parent | Fixed httpsys Windows compilation error (diff) | |
| parent | GetWindowsErrorAsString() -> GetSystemErrorAsString() (diff) | |
| download | zen-f0036eada7f6bcf6e08afe3ea8517367ed73450e.tar.xz zen-f0036eada7f6bcf6e08afe3ea8517367ed73450e.zip | |
Merged latest from main
Diffstat (limited to 'zencore/compactbinary.cpp')
| -rw-r--r-- | zencore/compactbinary.cpp | 710 |
1 files changed, 622 insertions, 88 deletions
diff --git a/zencore/compactbinary.cpp b/zencore/compactbinary.cpp index f4908aa9a..3b6d33e41 100644 --- a/zencore/compactbinary.cpp +++ b/zencore/compactbinary.cpp @@ -2,18 +2,35 @@ #include "zencore/compactbinary.h" +#include <zencore/base64.h> #include <zencore/compactbinaryvalidation.h> +#include <zencore/compactbinaryvalue.h> #include <zencore/compress.h> #include <zencore/endian.h> +#include <zencore/fmtutils.h> #include <zencore/stream.h> +#include <zencore/string.h> #include <zencore/testing.h> +#include <zencore/uid.h> +#include <fmt/format.h> #include <string_view> +#if ZEN_WITH_TESTS +# include <json11.hpp> +# include <zencore/compactbinarybuilder.h> +#endif + namespace zen { const int DaysToMonth[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; +double +GetJulianDay(uint64_t Ticks) +{ + return (double)(1721425.5 + Ticks / TimeSpan::TicksPerDay); +} + bool IsLeapYear(int Year) { @@ -63,24 +80,254 @@ TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano) Ticks = TotalTicks; } -////////////////////////////////////////////////////////////////////////// +std::string +TimeSpan::ToString(const char* Format) const +{ + using namespace fmt::literals; -namespace CompactBinaryPrivate { + StringBuilder<128> Result; - static constexpr const uint8_t GEmptyObjectPayload[] = {uint8_t(CbFieldType::Object), 0x00}; - static constexpr const uint8_t GEmptyArrayPayload[] = {uint8_t(CbFieldType::Array), 0x01, 0x00}; + Result.Append((Ticks < 0) ? '-' : '+'); - template<typename T> - static constexpr inline T ReadUnaligned(const void* const Memory) + while (*Format != '\0') { -#if PLATFORM_SUPPORTS_UNALIGNED_LOADS - return *static_cast<const T*>(Memory); -#else - T Value; - memcpy(&Value, Memory, sizeof(Value)); - return Value; -#endif + if ((*Format == '%') && (*++Format != '\0')) + { + switch (*Format) + { + case 'd': + Result.Append("%i"_format(GetDays())); + break; + case 'D': + Result.Append("%08i"_format(GetDays())); + break; + case 'h': + Result.Append("%02i"_format(GetHours())); + break; + case 'm': + Result.Append("%02i"_format(GetMinutes())); + break; + case 's': + Result.Append("%02i"_format(GetSeconds())); + break; + case 'f': + Result.Append("%03i"_format(GetFractionMilli())); + break; + case 'u': + Result.Append("%06i"_format(GetFractionMicro())); + break; + case 't': + Result.Append("%07i"_format(GetFractionTicks())); + break; + case 'n': + Result.Append("%09i"_format(GetFractionNano())); + break; + default: + Result.Append(*Format); + } + } + else + { + Result.Append(*Format); + } + + ++Format; + } + + return Result.ToString(); +} + +std::string +TimeSpan::ToString() const +{ + if (GetDays() == 0) + { + return ToString("%h:%m:%s.%f"); + } + + return ToString("%d.%h:%m:%s.%f"); +} + +int +DateTime::GetYear() const +{ + int Year, Month, Day; + GetDate(Year, Month, Day); + + return Year; +} + +int +DateTime::GetMonth() const +{ + int Year, Month, Day; + GetDate(Year, Month, Day); + + return Month; +} + +int +DateTime::GetDay() const +{ + int Year, Month, Day; + GetDate(Year, Month, Day); + + return Day; +} + +int +DateTime::GetHour() const +{ + return (int)((Ticks / TimeSpan::TicksPerHour) % 24); +} + +int +DateTime::GetHour12() const +{ + int Hour = GetHour(); + + if (Hour < 1) + { + return 12; + } + + if (Hour > 12) + { + return (Hour - 12); + } + + return Hour; +} + +int +DateTime::GetMinute() const +{ + return (int)((Ticks / TimeSpan::TicksPerMinute) % 60); +} + +int +DateTime::GetSecond() const +{ + return (int)((Ticks / TimeSpan::TicksPerSecond) % 60); +} + +int +DateTime::GetMillisecond() const +{ + return (int)((Ticks / TimeSpan::TicksPerMillisecond) % 1000); +} + +void +DateTime::GetDate(int& Year, int& Month, int& Day) const +{ + // Based on FORTRAN code in: + // Fliegel, H. F. and van Flandern, T. C., + // Communications of the ACM, Vol. 11, No. 10 (October 1968). + + int i, j, k, l, n; + + l = int(GetJulianDay(Ticks) + 0.5) + 68569; + n = 4 * l / 146097; + l = l - (146097 * n + 3) / 4; + i = 4000 * (l + 1) / 1461001; + l = l - 1461 * i / 4 + 31; + j = 80 * l / 2447; + k = l - 2447 * j / 80; + l = j / 11; + j = j + 2 - 12 * l; + i = 100 * (n - 49) + i + l; + + Year = i; + Month = j; + Day = k; +} + +std::string +DateTime::ToString(const char* Format) const +{ + using namespace fmt::literals; + + StringBuilder<32> Result; + int Year, Month, Day; + + GetDate(Year, Month, Day); + + if (Format != nullptr) + { + while (*Format != '\0') + { + if ((*Format == '%') && (*(++Format) != '\0')) + { + switch (*Format) + { + // case 'a': Result.Append(IsMorning() ? TEXT("am") : TEXT("pm")); break; + // case 'A': Result.Append(IsMorning() ? TEXT("AM") : TEXT("PM")); break; + case 'd': + Result.Append("%02i"_format(Day)); + break; + // case 'D': Result.Appendf(TEXT("%03i"), GetDayOfYear()); break; + case 'm': + Result.Append("%02i"_format(Month)); + break; + case 'y': + Result.Append("%02i"_format(Year % 100)); + break; + case 'Y': + Result.Append("%04i"_format(Year % 100)); + break; + case 'h': + Result.Append("%02i"_format(GetHour12())); + break; + case 'H': + Result.Append("%02i"_format(GetHour())); + break; + case 'M': + Result.Append("%02i"_format(GetMinute())); + break; + case 'S': + Result.Append("%02i"_format(GetSecond())); + break; + case 's': + Result.Append("%03i"_format(GetMillisecond())); + break; + default: + Result.Append(*Format); + } + } + else + { + Result.Append(*Format); + } + + // move to the next one + Format++; + } } + + return Result.ToString(); +} + +std::string +DateTime::ToIso8601() const +{ + return ToString("%Y-%m-%dT%H:%M:%S.%sZ"); +} + +StringBuilderBase& +Guid::ToString(StringBuilderBase& Sb) const +{ + char Buf[128]; + snprintf(Buf, sizeof Buf, "%08x-%04x-%04x-%04x-%04x%08x", A, B >> 16, B & 0xFFFF, C >> 16, C & 0xFFFF, D); + Sb << Buf; + + return Sb; +} + +////////////////////////////////////////////////////////////////////////// + +namespace CompactBinaryPrivate { + static constexpr const uint8_t GEmptyObjectPayload[] = {uint8_t(CbFieldType::Object), 0x00}; + static constexpr const uint8_t GEmptyArrayPayload[] = {uint8_t(CbFieldType::Array), 0x01, 0x00}; } // namespace CompactBinaryPrivate ////////////////////////////////////////////////////////////////////////// @@ -151,14 +398,10 @@ CbFieldView::AsArrayView() MemoryView CbFieldView::AsBinaryView(const MemoryView Default) { - if (CbFieldTypeOps::IsBinary(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinary(Accessor.GetType())) { - const uint8_t* const PayloadBytes = static_cast<const uint8_t*>(Payload); - uint32_t ValueSizeByteCount; - const uint64_t ValueSize = ReadVarUInt(PayloadBytes, ValueSizeByteCount); - Error = CbFieldError::None; - return MemoryView(PayloadBytes + ValueSizeByteCount, ValueSize); + return Accessor.AsBinary(); } else { @@ -170,20 +413,25 @@ CbFieldView::AsBinaryView(const MemoryView Default) std::string_view CbFieldView::AsString(const std::string_view Default) { - if (CbFieldTypeOps::IsString(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType())) { - const char* const PayloadChars = static_cast<const char*>(Payload); - uint32_t ValueSizeByteCount; - const uint64_t ValueSize = ReadVarUInt(PayloadChars, ValueSizeByteCount); - - if (ValueSize >= (uint64_t(1) << 31)) - { - Error = CbFieldError::RangeError; - return Default; - } + Error = CbFieldError::None; + return Accessor.AsString(); + } + else + { + Error = CbFieldError::TypeError; + return Default; + } +} +std::u8string_view +CbFieldView::AsU8String(const std::u8string_view Default) +{ + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType())) + { Error = CbFieldError::None; - return std::string_view(PayloadChars + ValueSizeByteCount, ValueSize); + return Accessor.AsU8String(); } else { @@ -193,23 +441,11 @@ CbFieldView::AsString(const std::string_view Default) } uint64_t -CbFieldView::AsInteger(const uint64_t Default, const IntegerParams Params) +CbFieldView::AsInteger(const uint64_t Default, const CompactBinaryPrivate::IntegerParams Params) { - if (CbFieldTypeOps::IsInteger(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsInteger(Accessor.GetType())) { - // A shift of a 64-bit value by 64 is undefined so shift by one less because magnitude is never zero. - const uint64_t OutOfRangeMask = uint64_t(-2) << (Params.MagnitudeBits - 1); - const uint64_t IsNegative = uint8_t(Type) & 1; - - uint32_t MagnitudeByteCount; - const uint64_t Magnitude = ReadVarUInt(Payload, MagnitudeByteCount); - const uint64_t Value = Magnitude ^ -int64_t(IsNegative); - - const uint64_t IsInRange = (!(Magnitude & OutOfRangeMask)) & ((!IsNegative) | Params.IsSigned); - Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError; - - const uint64_t UseValueMask = -int64_t(IsInRange); - return (Value & UseValueMask) | (Default & ~UseValueMask); + return Accessor.AsInteger(Params, &Error, Default); } else { @@ -221,25 +457,24 @@ CbFieldView::AsInteger(const uint64_t Default, const IntegerParams Params) float CbFieldView::AsFloat(const float Default) { - switch (CbFieldTypeOps::GetType(Type)) + switch (CbValue Accessor = GetValue(); Accessor.GetType()) { case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: { - const uint64_t IsNegative = uint8_t(Type) & 1; + const uint64_t IsNegative = uint8_t(Accessor.GetType()) & 1; constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*FLT_MANT_DIG*/ 24) - 1); uint32_t MagnitudeByteCount; - const int64_t Magnitude = ReadVarUInt(Payload, MagnitudeByteCount) + IsNegative; + const int64_t Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative; const uint64_t IsInRange = !(Magnitude & OutOfRangeMask); Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError; return IsInRange ? float(IsNegative ? -Magnitude : Magnitude) : Default; } case CbFieldType::Float32: { - Error = CbFieldError::None; - const uint32_t Value = FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<uint32_t>(Payload)); - return reinterpret_cast<const float&>(Value); + Error = CbFieldError::None; + return Accessor.AsFloat32(); } case CbFieldType::Float64: Error = CbFieldError::RangeError; @@ -253,31 +488,29 @@ CbFieldView::AsFloat(const float Default) double CbFieldView::AsDouble(const double Default) { - switch (CbFieldTypeOps::GetType(Type)) + switch (CbValue Accessor = GetValue(); Accessor.GetType()) { case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: { - const uint64_t IsNegative = uint8_t(Type) & 1; + const uint64_t IsNegative = uint8_t(Accessor.GetType()) & 1; constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*DBL_MANT_DIG*/ 53) - 1); uint32_t MagnitudeByteCount; - const int64_t Magnitude = ReadVarUInt(Payload, MagnitudeByteCount) + IsNegative; + const int64_t Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative; const uint64_t IsInRange = !(Magnitude & OutOfRangeMask); Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError; return IsInRange ? double(IsNegative ? -Magnitude : Magnitude) : Default; } case CbFieldType::Float32: { - Error = CbFieldError::None; - const uint32_t Value = FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<uint32_t>(Payload)); - return reinterpret_cast<const float&>(Value); + Error = CbFieldError::None; + return Accessor.AsFloat32(); } case CbFieldType::Float64: { - Error = CbFieldError::None; - const uint64_t Value = FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<uint64_t>(Payload)); - return reinterpret_cast<const double&>(Value); + Error = CbFieldError::None; + return Accessor.AsFloat64(); } default: Error = CbFieldError::TypeError; @@ -288,19 +521,19 @@ CbFieldView::AsDouble(const double Default) bool CbFieldView::AsBool(const bool bDefault) { - const CbFieldType LocalType = Type; - const bool bIsBool = CbFieldTypeOps::IsBool(LocalType); - Error = bIsBool ? CbFieldError::None : CbFieldError::TypeError; - return (uint8_t(bIsBool) & uint8_t(LocalType) & 1) | ((!bIsBool) & bDefault); + CbValue Accessor = GetValue(); + const bool IsBool = CbFieldTypeOps::IsBool(Accessor.GetType()); + Error = IsBool ? CbFieldError::None : CbFieldError::TypeError; + return (uint8_t(IsBool) & Accessor.AsBool()) | ((!IsBool) & bDefault); } IoHash CbFieldView::AsObjectAttachment(const IoHash& Default) { - if (CbFieldTypeOps::IsObjectAttachment(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectAttachment(Accessor.GetType())) { Error = CbFieldError::None; - return IoHash::MakeFrom(Payload); + return Accessor.AsObjectAttachment(); } else { @@ -312,10 +545,10 @@ CbFieldView::AsObjectAttachment(const IoHash& Default) IoHash CbFieldView::AsBinaryAttachment(const IoHash& Default) { - if (CbFieldTypeOps::IsBinaryAttachment(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinaryAttachment(Accessor.GetType())) { Error = CbFieldError::None; - return IoHash::MakeFrom(Payload); + return Accessor.AsBinaryAttachment(); } else { @@ -327,10 +560,10 @@ CbFieldView::AsBinaryAttachment(const IoHash& Default) IoHash CbFieldView::AsAttachment(const IoHash& Default) { - if (CbFieldTypeOps::IsAttachment(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsAttachment(Accessor.GetType())) { Error = CbFieldError::None; - return IoHash::MakeFrom(Payload); + return Accessor.AsAttachment(); } else { @@ -342,10 +575,10 @@ CbFieldView::AsAttachment(const IoHash& Default) IoHash CbFieldView::AsHash(const IoHash& Default) { - if (CbFieldTypeOps::IsHash(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsHash(Accessor.GetType())) { Error = CbFieldError::None; - return IoHash::MakeFrom(Payload); + return Accessor.AsHash(); } else { @@ -363,16 +596,10 @@ CbFieldView::AsUuid() Guid CbFieldView::AsUuid(const Guid& Default) { - if (CbFieldTypeOps::IsUuid(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsUuid(Accessor.GetType())) { Error = CbFieldError::None; - Guid Value; - memcpy(&Value, Payload, sizeof(Guid)); - Value.A = FromNetworkOrder(Value.A); - Value.B = FromNetworkOrder(Value.B); - Value.C = FromNetworkOrder(Value.C); - Value.D = FromNetworkOrder(Value.D); - return Value; + return Accessor.AsUuid(); } else { @@ -390,12 +617,40 @@ CbFieldView::AsObjectId() Oid CbFieldView::AsObjectId(const Oid& Default) { - if (CbFieldTypeOps::IsObjectId(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectId(Accessor.GetType())) + { + Error = CbFieldError::None; + return Accessor.AsObjectId(); + } + else + { + Error = CbFieldError::TypeError; + return Default; + } +} + +CbCustomById +CbFieldView::AsCustomById(CbCustomById Default) +{ + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomById(Accessor.GetType())) + { + Error = CbFieldError::None; + return Accessor.AsCustomById(); + } + else + { + Error = CbFieldError::TypeError; + return Default; + } +} + +CbCustomByName +CbFieldView::AsCustomByName(CbCustomByName Default) +{ + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomByName(Accessor.GetType())) { Error = CbFieldError::None; - Oid Value; - memcpy(&Value, Payload, sizeof(Oid)); - return Value; + return Accessor.AsCustomByName(); } else { @@ -407,10 +662,10 @@ CbFieldView::AsObjectId(const Oid& Default) int64_t CbFieldView::AsDateTimeTicks(const int64_t Default) { - if (CbFieldTypeOps::IsDateTime(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsDateTime(Accessor.GetType())) { Error = CbFieldError::None; - return FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<int64_t>(Payload)); + return Accessor.AsDateTimeTicks(); } else { @@ -434,10 +689,10 @@ CbFieldView::AsDateTime(DateTime Default) int64_t CbFieldView::AsTimeSpanTicks(const int64_t Default) { - if (CbFieldTypeOps::IsTimeSpan(Type)) + if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsTimeSpan(Accessor.GetType())) { Error = CbFieldError::None; - return FromNetworkOrder(CompactBinaryPrivate::ReadUnaligned<int64_t>(Payload)); + return Accessor.AsTimeSpanTicks(); } else { @@ -1146,6 +1401,259 @@ SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object) ////////////////////////////////////////////////////////////////////////// +class CbJsonWriter +{ +public: + explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; } + + void WriteField(CbFieldView Field) + { + using namespace fmt::literals; + using namespace std::literals; + + WriteOptionalComma(); + WriteOptionalNewLine(); + + if (std::u8string_view Name = Field.GetU8Name(); !Name.empty()) + { + AppendQuotedString(Name); + Builder << ": "sv; + } + + switch (CbValue Accessor = Field.GetValue(); Accessor.GetType()) + { + case CbFieldType::Null: + Builder << "null"sv; + break; + case CbFieldType::Object: + case CbFieldType::UniformObject: + { + Builder << '{'; + NewLineAndIndent << '\t'; + NeedsNewLine = true; + for (CbFieldView It : Field) + { + WriteField(It); + } + NewLineAndIndent.RemoveSuffix(1); + if (NeedsComma) + { + WriteOptionalNewLine(); + } + Builder << '}'; + } + break; + case CbFieldType::Array: + case CbFieldType::UniformArray: + { + Builder << '['; + NewLineAndIndent << '\t'; + NeedsNewLine = true; + for (CbFieldView It : Field) + { + WriteField(It); + } + NewLineAndIndent.RemoveSuffix(1); + if (NeedsComma) + { + WriteOptionalNewLine(); + } + Builder << ']'; + } + break; + case CbFieldType::Binary: + AppendBase64String(Accessor.AsBinary()); + break; + case CbFieldType::String: + AppendQuotedString(Accessor.AsU8String()); + break; + case CbFieldType::IntegerPositive: + Builder << Accessor.AsIntegerPositive(); + break; + case CbFieldType::IntegerNegative: + Builder << Accessor.AsIntegerNegative(); + break; + case CbFieldType::Float32: + Builder.Append("%.9g"_format(Accessor.AsFloat32())); + break; + case CbFieldType::Float64: + Builder.Append("%.17g"_format(Accessor.AsFloat64())); + break; + case CbFieldType::BoolFalse: + Builder << "false"sv; + break; + case CbFieldType::BoolTrue: + Builder << "true"sv; + break; + case CbFieldType::ObjectAttachment: + case CbFieldType::BinaryAttachment: + { + Builder << '"'; + Accessor.AsAttachment().ToHexString(Builder); + Builder << '"'; + } + break; + case CbFieldType::Hash: + { + Builder << '"'; + Accessor.AsHash().ToHexString(Builder); + Builder << '"'; + } + break; + case CbFieldType::Uuid: + { + Builder << '"'; + Accessor.AsUuid().ToString(Builder); + Builder << '"'; + } + break; + case CbFieldType::DateTime: + Builder << '"' << DateTime(Accessor.AsDateTimeTicks()).ToIso8601() << '"'; + break; + case CbFieldType::TimeSpan: + { + const TimeSpan Span(Accessor.AsTimeSpanTicks()); + if (Span.GetDays() == 0) + { + Builder << '"' << Span.ToString("%h:%m:%s.%n") << '"'; + } + else + { + Builder << '"' << Span.ToString("%d.%h:%m:%s.%n") << '"'; + } + break; + } + case CbFieldType::ObjectId: + Builder << '"'; + Accessor.AsObjectId().ToString(Builder); + Builder << '"'; + break; + case CbFieldType::CustomById: + { + CbCustomById Custom = Accessor.AsCustomById(); + Builder << "{ \"Id\": "; + Builder << Custom.Id; + Builder << ", \"Data\": "; + AppendBase64String(Custom.Data); + Builder << " }"; + break; + } + case CbFieldType::CustomByName: + { + CbCustomByName Custom = Accessor.AsCustomByName(); + Builder << "{ \"Name\": "; + AppendQuotedString(Custom.Name); + Builder << ", \"Data\": "; + AppendBase64String(Custom.Data); + Builder << " }"; + break; + } + default: + ZEN_ASSERT(false); + break; + } + + NeedsComma = true; + NeedsNewLine = true; + } + +private: + void WriteOptionalComma() + { + if (NeedsComma) + { + NeedsComma = false; + Builder << ','; + } + } + + void WriteOptionalNewLine() + { + if (NeedsNewLine) + { + NeedsNewLine = false; + Builder << NewLineAndIndent; + } + } + + void AppendQuotedString(std::u8string_view Value) + { + using namespace std::literals; + + const AsciiSet EscapeSet( + "\\\"\b\f\n\r\t" + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"); + + Builder << '\"'; + while (!Value.empty()) + { + std::u8string_view Verbatim = AsciiSet::FindPrefixWithout(Value, EscapeSet); + Builder << Verbatim; + + Value = Value.substr(Verbatim.size()); + + std::u8string_view Escape = AsciiSet::FindPrefixWith(Value, EscapeSet); + for (char Char : Escape) + { + switch (Char) + { + case '\\': + Builder << "\\\\"sv; + break; + case '\"': + Builder << "\\\""sv; + break; + case '\b': + Builder << "\\b"sv; + break; + case '\f': + Builder << "\\f"sv; + break; + case '\n': + Builder << "\\n"sv; + break; + case '\r': + Builder << "\\r"sv; + break; + case '\t': + Builder << "\\t"sv; + break; + default: + Builder << Char; + break; + } + } + Value = Value.substr(Escape.size()); + } + Builder << '\"'; + } + + void AppendBase64String(MemoryView Value) + { + Builder << '"'; + ZEN_ASSERT(Value.GetSize() <= 512 * 1024 * 1024); + const uint32_t EncodedSize = Base64::GetEncodedDataSize(uint32_t(Value.GetSize())); + const size_t EncodedIndex = Builder.AddUninitialized(size_t(EncodedSize)); + Base64::Encode(static_cast<const uint8_t*>(Value.GetData()), uint32_t(Value.GetSize()), Builder.Data() + EncodedIndex); + } + +private: + StringBuilderBase& Builder; + StringBuilder<32> NewLineAndIndent; + bool NeedsComma{false}; + bool NeedsNewLine{false}; +}; + +void +CompactBinaryToJson(const CbObjectView& Object, StringBuilderBase& Builder) +{ + CbJsonWriter Writer(Builder); + Writer.WriteField(Object.AsFieldView()); +} + +////////////////////////////////////////////////////////////////////////// + #if ZEN_WITH_TESTS void uson_forcelink() @@ -1298,6 +1806,32 @@ TEST_CASE("uson.null") CHECK(Field.IsNull() == false); } } + +TEST_CASE("uson.json") +{ + SUBCASE("string") + { + CbObjectWriter Writer; + Writer << "KeyOne" + << "ValueOne"; + Writer << "KeyTwo" + << "ValueTwo"; + CbObject Obj = Writer.Save(); + + StringBuilder<128> Sb; + const std::string_view JsonText = Obj.ToJson(Sb).ToView(); + + std::string JsonError; + json11::Json Json = json11::Json::parse(JsonText.data(), JsonError); + + const std::string ValueOne = Json["KeyOne"].string_value(); + const std::string ValueTwo = Json["KeyTwo"].string_value(); + + CHECK(JsonError.empty()); + CHECK(ValueOne == "ValueOne"); + CHECK(ValueTwo == "ValueTwo"); + } +} #endif } // namespace zen |