aboutsummaryrefslogtreecommitdiff
path: root/zencore/compactbinary.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2021-09-27 12:34:52 +0200
committerStefan Boberg <[email protected]>2021-09-27 12:34:52 +0200
commitf0036eada7f6bcf6e08afe3ea8517367ed73450e (patch)
treeb1ce3466bba36175cad369028fad1b410a34b5ec /zencore/compactbinary.cpp
parentFixed httpsys Windows compilation error (diff)
parentGetWindowsErrorAsString() -> GetSystemErrorAsString() (diff)
downloadzen-f0036eada7f6bcf6e08afe3ea8517367ed73450e.tar.xz
zen-f0036eada7f6bcf6e08afe3ea8517367ed73450e.zip
Merged latest from main
Diffstat (limited to 'zencore/compactbinary.cpp')
-rw-r--r--zencore/compactbinary.cpp710
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