aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore')
-rw-r--r--src/zencore/compactbinary.cpp781
-rw-r--r--src/zencore/compactbinaryjson.cpp805
-rw-r--r--src/zencore/compactbinaryyaml.cpp352
-rw-r--r--src/zencore/include/zencore/compactbinary.h32
-rw-r--r--src/zencore/xmake.lua2
-rw-r--r--src/zencore/zencore.cpp2
6 files changed, 1191 insertions, 783 deletions
diff --git a/src/zencore/compactbinary.cpp b/src/zencore/compactbinary.cpp
index 6677b5a61..0907f8a2e 100644
--- a/src/zencore/compactbinary.cpp
+++ b/src/zencore/compactbinary.cpp
@@ -24,10 +24,6 @@
# include <time.h>
#endif
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <json11.hpp>
-ZEN_THIRD_PARTY_INCLUDES_END
-
namespace zen {
const int DaysToMonth[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
@@ -1468,339 +1464,6 @@ SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object)
//////////////////////////////////////////////////////////////////////////
-class CbJsonWriter
-{
-public:
- explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; }
-
- void BeginObject()
- {
- Builder << '{';
- NewLineAndIndent << '\t';
- NeedsNewLine = true;
- }
-
- void EndObject()
- {
- NewLineAndIndent.RemoveSuffix(1);
- if (NeedsComma)
- {
- WriteOptionalNewLine();
- }
- Builder << '}';
- }
-
- void BeginArray()
- {
- Builder << '[';
- NewLineAndIndent << '\t';
- NeedsNewLine = true;
- }
-
- void EndArray()
- {
- NewLineAndIndent.RemoveSuffix(1);
- if (NeedsComma)
- {
- WriteOptionalNewLine();
- }
- Builder << ']';
- }
-
- void WriteField(CbFieldView Field)
- {
- 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:
- {
- BeginObject();
- for (CbFieldView It : Field)
- {
- WriteField(It);
- }
- EndObject();
- }
- break;
- case CbFieldType::Array:
- case CbFieldType::UniformArray:
- {
- BeginArray();
- for (CbFieldView It : Field)
- {
- WriteField(It);
- }
- EndArray();
- }
- 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:
- {
- const float Value = Accessor.AsFloat32();
- if (std::isfinite(Value))
- {
- Builder.Append(fmt::format("{:.9g}", Value));
- }
- else
- {
- Builder << "null"sv;
- }
- }
- break;
- case CbFieldType::Float64:
- {
- const double Value = Accessor.AsFloat64();
- if (std::isfinite(Value))
- {
- Builder.Append(fmt::format("{:.17g}", Value));
- }
- else
- {
- Builder << "null"sv;
- }
- }
- 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_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType()));
- 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;
- ExtendableStringBuilder<32> NewLineAndIndent;
- bool NeedsComma{false};
- bool NeedsNewLine{false};
-};
-
-void
-CompactBinaryToJson(const CbObjectView& Object, StringBuilderBase& Builder)
-{
- CbJsonWriter Writer(Builder);
- Writer.WriteField(Object.AsFieldView());
-}
-
-void
-CompactBinaryToJson(const CbArrayView& Array, StringBuilderBase& Builder)
-{
- CbJsonWriter Writer(Builder);
- Writer.WriteField(Array.AsFieldView());
-}
-
-void
-CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder)
-{
- std::vector<CbFieldView> Fields = ReadCompactBinaryStream(Data);
- CbJsonWriter Writer(InBuilder);
- if (!Fields.empty())
- {
- if (Fields.size() == 1)
- {
- Writer.WriteField(Fields[0]);
- return;
- }
- bool UseTopLevelObject = Fields[0].HasName();
- if (UseTopLevelObject)
- {
- Writer.BeginObject();
- }
- else
- {
- Writer.BeginArray();
- }
- for (const CbFieldView& Field : Fields)
- {
- Writer.WriteField(Field);
- }
- if (UseTopLevelObject)
- {
- Writer.EndObject();
- }
- else
- {
- Writer.EndArray();
- }
- }
-}
-
std::vector<CbFieldView>
ReadCompactBinaryStream(MemoryView Data)
{
@@ -1823,225 +1486,6 @@ ReadCompactBinaryStream(MemoryView Data)
//////////////////////////////////////////////////////////////////////////
-class CbJsonReader
-{
-public:
- static CbFieldIterator Read(std::string_view JsonText, std::string& Error)
- {
- using namespace json11;
-
- const Json Json = Json::parse(std::string(JsonText), Error);
-
- if (Error.empty())
- {
- CbWriter Writer;
- if (ReadField(Writer, Json, std::string_view(), Error))
- {
- return Writer.Save();
- }
- }
-
- return CbFieldIterator();
- }
-
-private:
- static bool ReadField(CbWriter& Writer, const json11::Json& Json, const std::string_view FieldName, std::string& Error)
- {
- using namespace json11;
-
- switch (Json.type())
- {
- case Json::Type::OBJECT:
- {
- if (FieldName.empty())
- {
- Writer.BeginObject();
- }
- else
- {
- Writer.BeginObject(FieldName);
- }
-
- for (const auto& Kv : Json.object_items())
- {
- const std::string& Name = Kv.first;
- const json11::Json& Item = Kv.second;
-
- if (ReadField(Writer, Item, Name, Error) == false)
- {
- return false;
- }
- }
-
- Writer.EndObject();
- }
- break;
- case Json::Type::ARRAY:
- {
- if (FieldName.empty())
- {
- Writer.BeginArray();
- }
- else
- {
- Writer.BeginArray(FieldName);
- }
-
- for (const json11::Json& Item : Json.array_items())
- {
- if (ReadField(Writer, Item, std::string_view(), Error) == false)
- {
- return false;
- }
- }
-
- Writer.EndArray();
- }
- break;
- case Json::Type::NUL:
- {
- if (FieldName.empty())
- {
- Writer.AddNull();
- }
- else
- {
- Writer.AddNull(FieldName);
- }
- }
- break;
- case Json::Type::BOOL:
- {
- if (FieldName.empty())
- {
- Writer.AddBool(Json.bool_value());
- }
- else
- {
- Writer.AddBool(FieldName, Json.bool_value());
- }
- }
- break;
- case Json::Type::NUMBER:
- {
- if (FieldName.empty())
- {
- Writer.AddFloat(Json.number_value());
- }
- else
- {
- Writer.AddFloat(FieldName, Json.number_value());
- }
- }
- break;
- case Json::Type::STRING:
- {
- Oid Id;
- if (TryParseObjectId(Json.string_value(), Id))
- {
- if (FieldName.empty())
- {
- Writer.AddObjectId(Id);
- }
- else
- {
- Writer.AddObjectId(FieldName, Id);
- }
-
- return true;
- }
-
- IoHash Hash;
- if (TryParseIoHash(Json.string_value(), Hash))
- {
- if (FieldName.empty())
- {
- Writer.AddHash(Hash);
- }
- else
- {
- Writer.AddHash(FieldName, Hash);
- }
-
- return true;
- }
-
- if (FieldName.empty())
- {
- Writer.AddString(Json.string_value());
- }
- else
- {
- Writer.AddString(FieldName, Json.string_value());
- }
- }
- break;
- default:
- break;
- }
-
- return true;
- }
-
- static constexpr AsciiSet HexCharSet = AsciiSet("0123456789abcdefABCDEF");
-
- static bool TryParseObjectId(std::string_view Str, Oid& Id)
- {
- using namespace std::literals;
-
- if (Str.size() == Oid::StringLength && AsciiSet::HasOnly(Str, HexCharSet))
- {
- Id = Oid::FromHexString(Str);
- return true;
- }
-
- if (Str.starts_with("0x"sv))
- {
- return TryParseObjectId(Str.substr(2), Id);
- }
-
- return false;
- }
-
- static bool TryParseIoHash(std::string_view Str, IoHash& Hash)
- {
- using namespace std::literals;
-
- if (Str.size() == IoHash::StringLength && AsciiSet::HasOnly(Str, HexCharSet))
- {
- Hash = IoHash::FromHexString(Str);
- return true;
- }
-
- if (Str.starts_with("0x"sv))
- {
- return TryParseIoHash(Str.substr(2), Hash);
- }
-
- return false;
- }
-};
-
-CbFieldIterator
-LoadCompactBinaryFromJson(std::string_view Json, std::string& Error)
-{
- if (Json.empty() == false)
- {
- return CbJsonReader::Read(Json, Error);
- }
-
- return CbFieldIterator();
-}
-
-CbFieldIterator
-LoadCompactBinaryFromJson(std::string_view Json)
-{
- std::string Error;
- return LoadCompactBinaryFromJson(Json, Error);
-}
-
-//////////////////////////////////////////////////////////////////////////
-
#if ZEN_WITH_TESTS
void
uson_forcelink()
@@ -2211,130 +1655,6 @@ TEST_CASE("uson.null")
}
}
-TEST_CASE("uson.json")
-{
- using namespace std::literals;
-
- SUBCASE("string")
- {
- CbObjectWriter Writer;
- Writer << "KeyOne"
- << "ValueOne";
- Writer << "KeyTwo"
- << "ValueTwo";
- CbObject Obj = Writer.Save();
-
- StringBuilder<128> Sb;
- const char* JsonText = Obj.ToJson(Sb).Data();
-
- std::string JsonError;
- json11::Json Json = json11::Json::parse(JsonText, 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");
- }
-
- SUBCASE("number")
- {
- const float ExpectedFloatValue = 21.21f;
- const double ExpectedDoubleValue = 42.42;
-
- CbObjectWriter Writer;
- Writer << "Float" << ExpectedFloatValue;
- Writer << "Double" << ExpectedDoubleValue;
-
- CbObject Obj = Writer.Save();
-
- StringBuilder<128> Sb;
- const char* JsonText = Obj.ToJson(Sb).Data();
-
- std::string JsonError;
- json11::Json Json = json11::Json::parse(JsonText, JsonError);
-
- const float FloatValue = float(Json["Float"].number_value());
- const double DoubleValue = Json["Double"].number_value();
-
- CHECK(JsonError.empty());
- CHECK(FloatValue == Approx(ExpectedFloatValue));
- CHECK(DoubleValue == Approx(ExpectedDoubleValue));
- }
-
- SUBCASE("number.nan")
- {
- constexpr float FloatNan = std::numeric_limits<float>::quiet_NaN();
- constexpr double DoubleNan = std::numeric_limits<double>::quiet_NaN();
-
- CbObjectWriter Writer;
- Writer << "FloatNan" << FloatNan;
- Writer << "DoubleNan" << DoubleNan;
-
- CbObject Obj = Writer.Save();
-
- StringBuilder<128> Sb;
- const char* JsonText = Obj.ToJson(Sb).Data();
-
- std::string JsonError;
- json11::Json Json = json11::Json::parse(JsonText, JsonError);
-
- const double FloatValue = Json["FloatNan"].number_value();
- const double DoubleValue = Json["DoubleNan"].number_value();
-
- CHECK(JsonError.empty());
- CHECK(FloatValue == 0);
- CHECK(DoubleValue == 0);
- }
-
- SUBCASE("stream")
- {
- const auto MakeObject = [&](std::string_view Name, const std::vector<int>& Fields) -> CbObject {
- CbWriter Writer;
- Writer.SetName(Name);
- Writer.BeginObject();
- for (const auto& Field : Fields)
- {
- Writer.AddInteger(fmt::format("{}", Field), Field);
- }
- Writer.EndObject();
- return Writer.Save().AsObject();
- };
-
- std::vector<uint8_t> Buffer;
-
- auto AppendToBuffer = [&](const void* Data, size_t Count) {
- const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data);
- Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count);
- };
-
- auto Append = [&](const CbFieldView& Field) {
- Field.WriteToStream([&](const void* Data, size_t Count) {
- const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data);
- Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count);
- });
- };
-
- CbObject DataObjects[] = {MakeObject("Empty object"sv, {}),
- MakeObject("OneField object"sv, {5}),
- MakeObject("TwoField object"sv, {-5, 999}),
- MakeObject("ThreeField object"sv, {1, 2, -129})};
- for (const CbObject& Object : DataObjects)
- {
- Object.AsField().WriteToStream(AppendToBuffer);
- }
-
- ExtendableStringBuilder<128> Sb;
- CompactBinaryToJson(MemoryView(Buffer.data(), Buffer.size()), Sb);
- std::string JsonText = Sb.ToString().c_str();
- std::string JsonError;
- json11::Json Json = json11::Json::parse(JsonText, JsonError);
- std::string ParsedJsonString = Json.dump();
- CHECK(!ParsedJsonString.empty());
- }
-}
-
TEST_CASE("uson.datetime")
{
using namespace std::literals;
@@ -2362,107 +1682,6 @@ TEST_CASE("uson.datetime")
}
}
-TEST_CASE("json.uson")
-{
- using namespace std::literals;
- using namespace json11;
-
- SUBCASE("empty")
- {
- CbFieldIterator It = LoadCompactBinaryFromJson(""sv);
- CHECK(It.HasValue() == false);
- }
-
- SUBCASE("object")
- {
- const Json JsonObject = Json::object{{"Null", nullptr},
- {"String", "Value1"},
- {"Bool", true},
- {"Number", 46.2},
- {"Array", Json::array{1, 2, 3}},
- {"Object",
- Json::object{
- {"String", "Value2"},
- }}};
-
- CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
-
- CHECK(Cb["Null"].IsNull());
- CHECK(Cb["String"].AsString() == "Value1"sv);
- CHECK(Cb["Bool"].AsBool());
- CHECK(Cb["Number"].AsDouble() == 46.2);
- CHECK(Cb["Object"].IsObject());
- CbObjectView Object = Cb["Object"].AsObjectView();
- CHECK(Object["String"].AsString() == "Value2"sv);
- }
-
- SUBCASE("array")
- {
- const Json JsonArray = Json::array{42, 43, 44};
- CbArray Cb = LoadCompactBinaryFromJson(JsonArray.dump()).AsArray();
-
- auto It = Cb.CreateIterator();
- CHECK((*It).AsDouble() == 42);
- It++;
- CHECK((*It).AsDouble() == 43);
- It++;
- CHECK((*It).AsDouble() == 44);
- }
-
- SUBCASE("objectid")
- {
- const Oid& Id = Oid::NewOid();
-
- StringBuilder<64> Sb;
- Id.ToString(Sb);
-
- Json JsonObject = Json::object{{"value", Sb.ToString()}};
- CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
-
- CHECK(Cb["value"sv].IsObjectId());
- CHECK(Cb["value"sv].AsObjectId() == Id);
-
- Sb.Reset();
- Sb << "0x";
- Id.ToString(Sb);
-
- JsonObject = Json::object{{"value", Sb.ToString()}};
- Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
-
- CHECK(Cb["value"sv].IsObjectId());
- CHECK(Cb["value"sv].AsObjectId() == Id);
- }
-
- SUBCASE("iohash")
- {
- const uint8_t Data[] = {
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- };
-
- const IoHash Hash = IoHash::HashBuffer(Data, sizeof(Data));
-
- Json JsonObject = Json::object{{"value", Hash.ToHexString()}};
- CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
-
- CHECK(Cb["value"sv].IsHash());
- CHECK(Cb["value"sv].AsHash() == Hash);
-
- JsonObject = Json::object{{"value", "0x" + Hash.ToHexString()}};
- Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
-
- CHECK(Cb["value"sv].IsHash());
- CHECK(Cb["value"sv].AsHash() == Hash);
- }
-}
-
//////////////////////////////////////////////////////////////////////////
TEST_SUITE_BEGIN("core.datetime");
diff --git a/src/zencore/compactbinaryjson.cpp b/src/zencore/compactbinaryjson.cpp
new file mode 100644
index 000000000..58a91c731
--- /dev/null
+++ b/src/zencore/compactbinaryjson.cpp
@@ -0,0 +1,805 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zencore/compactbinary.h"
+#include "zencore/compactbinarybuilder.h"
+#include "zencore/compactbinaryvalue.h"
+
+#include <zencore/assertfmt.h>
+#include <zencore/base64.h>
+#include <zencore/fmtutils.h>
+#include <zencore/string.h>
+#include <zencore/testing.h>
+
+#include <fmt/format.h>
+#include <vector>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <json11.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+class CbJsonWriter
+{
+public:
+ explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; }
+
+ void BeginObject()
+ {
+ Builder << '{';
+ NewLineAndIndent << '\t';
+ NeedsNewLine = true;
+ }
+
+ void EndObject()
+ {
+ NewLineAndIndent.RemoveSuffix(1);
+ if (NeedsComma)
+ {
+ WriteOptionalNewLine();
+ }
+ Builder << '}';
+ }
+
+ void BeginArray()
+ {
+ Builder << '[';
+ NewLineAndIndent << '\t';
+ NeedsNewLine = true;
+ }
+
+ void EndArray()
+ {
+ NewLineAndIndent.RemoveSuffix(1);
+ if (NeedsComma)
+ {
+ WriteOptionalNewLine();
+ }
+ Builder << ']';
+ }
+
+ void WriteField(CbFieldView Field)
+ {
+ 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:
+ {
+ BeginObject();
+ for (CbFieldView It : Field)
+ {
+ WriteField(It);
+ }
+ EndObject();
+ }
+ break;
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ {
+ BeginArray();
+ for (CbFieldView It : Field)
+ {
+ WriteField(It);
+ }
+ EndArray();
+ }
+ 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:
+ {
+ const float Value = Accessor.AsFloat32();
+ if (std::isfinite(Value))
+ {
+ Builder.Append(fmt::format("{:.9g}", Value));
+ }
+ else
+ {
+ Builder << "null"sv;
+ }
+ }
+ break;
+ case CbFieldType::Float64:
+ {
+ const double Value = Accessor.AsFloat64();
+ if (std::isfinite(Value))
+ {
+ Builder.Append(fmt::format("{:.17g}", Value));
+ }
+ else
+ {
+ Builder << "null"sv;
+ }
+ }
+ 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_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType()));
+ 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;
+ ExtendableStringBuilder<32> NewLineAndIndent;
+ bool NeedsComma{false};
+ bool NeedsNewLine{false};
+};
+
+void
+CompactBinaryToJson(const CbObjectView& Object, StringBuilderBase& Builder)
+{
+ CbJsonWriter Writer(Builder);
+ Writer.WriteField(Object.AsFieldView());
+}
+
+void
+CompactBinaryToJson(const CbArrayView& Array, StringBuilderBase& Builder)
+{
+ CbJsonWriter Writer(Builder);
+ Writer.WriteField(Array.AsFieldView());
+}
+
+void
+CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder)
+{
+ std::vector<CbFieldView> Fields = ReadCompactBinaryStream(Data);
+ CbJsonWriter Writer(InBuilder);
+ if (!Fields.empty())
+ {
+ if (Fields.size() == 1)
+ {
+ Writer.WriteField(Fields[0]);
+ return;
+ }
+ bool UseTopLevelObject = Fields[0].HasName();
+ if (UseTopLevelObject)
+ {
+ Writer.BeginObject();
+ }
+ else
+ {
+ Writer.BeginArray();
+ }
+ for (const CbFieldView& Field : Fields)
+ {
+ Writer.WriteField(Field);
+ }
+ if (UseTopLevelObject)
+ {
+ Writer.EndObject();
+ }
+ else
+ {
+ Writer.EndArray();
+ }
+ }
+}
+
+class CbJsonReader
+{
+public:
+ static CbFieldIterator Read(std::string_view JsonText, std::string& Error)
+ {
+ using namespace json11;
+
+ const Json Json = Json::parse(std::string(JsonText), Error);
+
+ if (Error.empty())
+ {
+ CbWriter Writer;
+ if (ReadField(Writer, Json, std::string_view(), Error))
+ {
+ return Writer.Save();
+ }
+ }
+
+ return CbFieldIterator();
+ }
+
+private:
+ static bool ReadField(CbWriter& Writer, const json11::Json& Json, const std::string_view FieldName, std::string& Error)
+ {
+ using namespace json11;
+
+ switch (Json.type())
+ {
+ case Json::Type::OBJECT:
+ {
+ if (FieldName.empty())
+ {
+ Writer.BeginObject();
+ }
+ else
+ {
+ Writer.BeginObject(FieldName);
+ }
+
+ for (const auto& Kv : Json.object_items())
+ {
+ const std::string& Name = Kv.first;
+ const json11::Json& Item = Kv.second;
+
+ if (ReadField(Writer, Item, Name, Error) == false)
+ {
+ return false;
+ }
+ }
+
+ Writer.EndObject();
+ }
+ break;
+ case Json::Type::ARRAY:
+ {
+ if (FieldName.empty())
+ {
+ Writer.BeginArray();
+ }
+ else
+ {
+ Writer.BeginArray(FieldName);
+ }
+
+ for (const json11::Json& Item : Json.array_items())
+ {
+ if (ReadField(Writer, Item, std::string_view(), Error) == false)
+ {
+ return false;
+ }
+ }
+
+ Writer.EndArray();
+ }
+ break;
+ case Json::Type::NUL:
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddNull();
+ }
+ else
+ {
+ Writer.AddNull(FieldName);
+ }
+ }
+ break;
+ case Json::Type::BOOL:
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddBool(Json.bool_value());
+ }
+ else
+ {
+ Writer.AddBool(FieldName, Json.bool_value());
+ }
+ }
+ break;
+ case Json::Type::NUMBER:
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddFloat(Json.number_value());
+ }
+ else
+ {
+ Writer.AddFloat(FieldName, Json.number_value());
+ }
+ }
+ break;
+ case Json::Type::STRING:
+ {
+ Oid Id;
+ if (TryParseObjectId(Json.string_value(), Id))
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddObjectId(Id);
+ }
+ else
+ {
+ Writer.AddObjectId(FieldName, Id);
+ }
+
+ return true;
+ }
+
+ IoHash Hash;
+ if (TryParseIoHash(Json.string_value(), Hash))
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddHash(Hash);
+ }
+ else
+ {
+ Writer.AddHash(FieldName, Hash);
+ }
+
+ return true;
+ }
+
+ if (FieldName.empty())
+ {
+ Writer.AddString(Json.string_value());
+ }
+ else
+ {
+ Writer.AddString(FieldName, Json.string_value());
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ static constexpr AsciiSet HexCharSet = AsciiSet("0123456789abcdefABCDEF");
+
+ static bool TryParseObjectId(std::string_view Str, Oid& Id)
+ {
+ using namespace std::literals;
+
+ if (Str.size() == Oid::StringLength && AsciiSet::HasOnly(Str, HexCharSet))
+ {
+ Id = Oid::FromHexString(Str);
+ return true;
+ }
+
+ if (Str.starts_with("0x"sv))
+ {
+ return TryParseObjectId(Str.substr(2), Id);
+ }
+
+ return false;
+ }
+
+ static bool TryParseIoHash(std::string_view Str, IoHash& Hash)
+ {
+ using namespace std::literals;
+
+ if (Str.size() == IoHash::StringLength && AsciiSet::HasOnly(Str, HexCharSet))
+ {
+ Hash = IoHash::FromHexString(Str);
+ return true;
+ }
+
+ if (Str.starts_with("0x"sv))
+ {
+ return TryParseIoHash(Str.substr(2), Hash);
+ }
+
+ return false;
+ }
+};
+
+CbFieldIterator
+LoadCompactBinaryFromJson(std::string_view Json, std::string& Error)
+{
+ if (Json.empty() == false)
+ {
+ return CbJsonReader::Read(Json, Error);
+ }
+
+ return CbFieldIterator();
+}
+
+CbFieldIterator
+LoadCompactBinaryFromJson(std::string_view Json)
+{
+ std::string Error;
+ return LoadCompactBinaryFromJson(Json, Error);
+}
+
+#if ZEN_WITH_TESTS
+void
+cbjson_forcelink()
+{
+}
+
+TEST_CASE("uson.json")
+{
+ using namespace std::literals;
+
+ SUBCASE("string")
+ {
+ CbObjectWriter Writer;
+ Writer << "KeyOne"
+ << "ValueOne";
+ Writer << "KeyTwo"
+ << "ValueTwo";
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ const char* JsonText = Obj.ToJson(Sb).Data();
+
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, 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");
+ }
+
+ SUBCASE("number")
+ {
+ const float ExpectedFloatValue = 21.21f;
+ const double ExpectedDoubleValue = 42.42;
+
+ CbObjectWriter Writer;
+ Writer << "Float" << ExpectedFloatValue;
+ Writer << "Double" << ExpectedDoubleValue;
+
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ const char* JsonText = Obj.ToJson(Sb).Data();
+
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, JsonError);
+
+ const float FloatValue = float(Json["Float"].number_value());
+ const double DoubleValue = Json["Double"].number_value();
+
+ CHECK(JsonError.empty());
+ CHECK(FloatValue == Approx(ExpectedFloatValue));
+ CHECK(DoubleValue == Approx(ExpectedDoubleValue));
+ }
+
+ SUBCASE("number.nan")
+ {
+ constexpr float FloatNan = std::numeric_limits<float>::quiet_NaN();
+ constexpr double DoubleNan = std::numeric_limits<double>::quiet_NaN();
+
+ CbObjectWriter Writer;
+ Writer << "FloatNan" << FloatNan;
+ Writer << "DoubleNan" << DoubleNan;
+
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ const char* JsonText = Obj.ToJson(Sb).Data();
+
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, JsonError);
+
+ const double FloatValue = Json["FloatNan"].number_value();
+ const double DoubleValue = Json["DoubleNan"].number_value();
+
+ CHECK(JsonError.empty());
+ CHECK(FloatValue == 0);
+ CHECK(DoubleValue == 0);
+ }
+
+ SUBCASE("stream")
+ {
+ const auto MakeObject = [&](std::string_view Name, const std::vector<int>& Fields) -> CbObject {
+ CbWriter Writer;
+ Writer.SetName(Name);
+ Writer.BeginObject();
+ for (const auto& Field : Fields)
+ {
+ Writer.AddInteger(fmt::format("{}", Field), Field);
+ }
+ Writer.EndObject();
+ return Writer.Save().AsObject();
+ };
+
+ std::vector<uint8_t> Buffer;
+
+ auto AppendToBuffer = [&](const void* Data, size_t Count) {
+ const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data);
+ Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count);
+ };
+
+ auto Append = [&](const CbFieldView& Field) {
+ Field.WriteToStream([&](const void* Data, size_t Count) {
+ const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data);
+ Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count);
+ });
+ };
+
+ CbObject DataObjects[] = {MakeObject("Empty object"sv, {}),
+ MakeObject("OneField object"sv, {5}),
+ MakeObject("TwoField object"sv, {-5, 999}),
+ MakeObject("ThreeField object"sv, {1, 2, -129})};
+ for (const CbObject& Object : DataObjects)
+ {
+ Object.AsField().WriteToStream(AppendToBuffer);
+ }
+
+ ExtendableStringBuilder<128> Sb;
+ CompactBinaryToJson(MemoryView(Buffer.data(), Buffer.size()), Sb);
+ std::string JsonText = Sb.ToString().c_str();
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, JsonError);
+ std::string ParsedJsonString = Json.dump();
+ CHECK(!ParsedJsonString.empty());
+ }
+}
+
+TEST_CASE("json.uson")
+{
+ using namespace std::literals;
+ using namespace json11;
+
+ SUBCASE("empty")
+ {
+ CbFieldIterator It = LoadCompactBinaryFromJson(""sv);
+ CHECK(It.HasValue() == false);
+ }
+
+ SUBCASE("object")
+ {
+ const Json JsonObject = Json::object{{"Null", nullptr},
+ {"String", "Value1"},
+ {"Bool", true},
+ {"Number", 46.2},
+ {"Array", Json::array{1, 2, 3}},
+ {"Object",
+ Json::object{
+ {"String", "Value2"},
+ }}};
+
+ CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["Null"].IsNull());
+ CHECK(Cb["String"].AsString() == "Value1"sv);
+ CHECK(Cb["Bool"].AsBool());
+ CHECK(Cb["Number"].AsDouble() == 46.2);
+ CHECK(Cb["Object"].IsObject());
+ CbObjectView Object = Cb["Object"].AsObjectView();
+ CHECK(Object["String"].AsString() == "Value2"sv);
+ }
+
+ SUBCASE("array")
+ {
+ const Json JsonArray = Json::array{42, 43, 44};
+ CbArray Cb = LoadCompactBinaryFromJson(JsonArray.dump()).AsArray();
+
+ auto It = Cb.CreateIterator();
+ CHECK((*It).AsDouble() == 42);
+ It++;
+ CHECK((*It).AsDouble() == 43);
+ It++;
+ CHECK((*It).AsDouble() == 44);
+ }
+
+ SUBCASE("objectid")
+ {
+ const Oid& Id = Oid::NewOid();
+
+ StringBuilder<64> Sb;
+ Id.ToString(Sb);
+
+ Json JsonObject = Json::object{{"value", Sb.ToString()}};
+ CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsObjectId());
+ CHECK(Cb["value"sv].AsObjectId() == Id);
+
+ Sb.Reset();
+ Sb << "0x";
+ Id.ToString(Sb);
+
+ JsonObject = Json::object{{"value", Sb.ToString()}};
+ Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsObjectId());
+ CHECK(Cb["value"sv].AsObjectId() == Id);
+ }
+
+ SUBCASE("iohash")
+ {
+ const uint8_t Data[] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ };
+
+ const IoHash Hash = IoHash::HashBuffer(Data, sizeof(Data));
+
+ Json JsonObject = Json::object{{"value", Hash.ToHexString()}};
+ CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsHash());
+ CHECK(Cb["value"sv].AsHash() == Hash);
+
+ JsonObject = Json::object{{"value", "0x" + Hash.ToHexString()}};
+ Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsHash());
+ CHECK(Cb["value"sv].AsHash() == Hash);
+ }
+}
+
+#endif // ZEN_WITH_TESTS
+
+} // namespace zen
diff --git a/src/zencore/compactbinaryyaml.cpp b/src/zencore/compactbinaryyaml.cpp
new file mode 100644
index 000000000..3a9705684
--- /dev/null
+++ b/src/zencore/compactbinaryyaml.cpp
@@ -0,0 +1,352 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zencore/compactbinary.h"
+#include "zencore/compactbinarybuilder.h"
+#include "zencore/compactbinaryvalue.h"
+
+#include <zencore/assertfmt.h>
+#include <zencore/base64.h>
+#include <zencore/fmtutils.h>
+#include <zencore/string.h>
+#include <zencore/testing.h>
+
+#include <fmt/format.h>
+#include <string_view>
+#include <vector>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <ryml/ryml.hpp>
+#include <ryml/ryml_std.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+//////////////////////////////////////////////////////////////////////////
+
+class CbYamlWriter
+{
+public:
+ explicit CbYamlWriter(StringBuilderBase& InBuilder) : m_StrBuilder(InBuilder) { m_NodeStack.push_back(m_Tree.rootref()); }
+
+ void WriteField(CbFieldView Field)
+ {
+ ryml::NodeRef Node;
+
+ if (m_IsFirst)
+ {
+ Node = Top();
+
+ m_IsFirst = false;
+ }
+ else
+ {
+ Node = Top().append_child();
+ }
+
+ if (std::u8string_view Name = Field.GetU8Name(); !Name.empty())
+ {
+ Node.set_key_serialized(ryml::csubstr((const char*)Name.data(), Name.size()));
+ }
+
+ switch (CbValue Accessor = Field.GetValue(); Accessor.GetType())
+ {
+ case CbFieldType::Null:
+ Node.set_val("null");
+ break;
+ case CbFieldType::Object:
+ case CbFieldType::UniformObject:
+ Node |= ryml::MAP;
+ m_NodeStack.push_back(Node);
+ for (CbFieldView It : Field)
+ {
+ WriteField(It);
+ }
+ m_NodeStack.pop_back();
+ break;
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ Node |= ryml::SEQ;
+ m_NodeStack.push_back(Node);
+ for (CbFieldView It : Field)
+ {
+ WriteField(It);
+ }
+ m_NodeStack.pop_back();
+ break;
+ case CbFieldType::Binary:
+ {
+ ExtendableStringBuilder<256> Builder;
+ const MemoryView Value = Accessor.AsBinary();
+ 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);
+
+ Node.set_key_serialized(Builder.c_str());
+ }
+ break;
+ case CbFieldType::String:
+ {
+ const std::u8string_view U8String = Accessor.AsU8String();
+ Node.set_val(ryml::csubstr((const char*)U8String.data(), U8String.size()));
+ }
+ break;
+ case CbFieldType::IntegerPositive:
+ Node << Accessor.AsIntegerPositive();
+ break;
+ case CbFieldType::IntegerNegative:
+ Node << Accessor.AsIntegerNegative();
+ break;
+ case CbFieldType::Float32:
+ if (const float Value = Accessor.AsFloat32(); std::isfinite(Value))
+ {
+ Node << Value;
+ }
+ else
+ {
+ Node << "null";
+ }
+ break;
+ case CbFieldType::Float64:
+ if (const double Value = Accessor.AsFloat64(); std::isfinite(Value))
+ {
+ Node << Value;
+ }
+ else
+ {
+ Node << "null";
+ }
+ break;
+ case CbFieldType::BoolFalse:
+ Node << "false";
+ break;
+ case CbFieldType::BoolTrue:
+ Node << "true";
+ break;
+ case CbFieldType::ObjectAttachment:
+ case CbFieldType::BinaryAttachment:
+ Node << Accessor.AsAttachment().ToHexString();
+ break;
+ case CbFieldType::Hash:
+ Node << Accessor.AsHash().ToHexString();
+ break;
+ case CbFieldType::Uuid:
+ Node << fmt::format("{}", Accessor.AsUuid());
+ break;
+ case CbFieldType::DateTime:
+ Node << DateTime(Accessor.AsDateTimeTicks()).ToIso8601();
+ break;
+ case CbFieldType::TimeSpan:
+ if (const TimeSpan Span(Accessor.AsTimeSpanTicks()); Span.GetDays() == 0)
+ {
+ Node << Span.ToString("%h:%m:%s.%n");
+ }
+ else
+ {
+ Node << Span.ToString("%d.%h:%m:%s.%n");
+ }
+ break;
+ case CbFieldType::ObjectId:
+ Node << fmt::format("{}", Accessor.AsObjectId());
+ break;
+ case CbFieldType::CustomById:
+ {
+ CbCustomById Custom = Accessor.AsCustomById();
+
+ Node |= ryml::MAP;
+
+ ryml::NodeRef IdNode = Node.append_child();
+ IdNode.set_key("Id");
+ IdNode.set_val_serialized(fmt::format("{}", Custom.Id));
+
+ ryml::NodeRef DataNode = Node.append_child();
+ DataNode.set_key("Data");
+
+ ExtendableStringBuilder<256> Builder;
+ const MemoryView& Value = Custom.Data;
+ 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);
+
+ DataNode.set_val_serialized(Builder.c_str());
+ }
+ break;
+ case CbFieldType::CustomByName:
+ {
+ CbCustomByName Custom = Accessor.AsCustomByName();
+
+ Node |= ryml::MAP;
+
+ ryml::NodeRef NameNode = Node.append_child();
+ NameNode.set_key("Name");
+ std::string_view Name = std::string_view((const char*)Custom.Name.data(), Custom.Name.size());
+ NameNode.set_val_serialized(std::string(Name));
+
+ ryml::NodeRef DataNode = Node.append_child();
+ DataNode.set_key("Data");
+
+ ExtendableStringBuilder<256> Builder;
+ const MemoryView& Value = Custom.Data;
+ 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);
+
+ DataNode.set_val_serialized(Builder.c_str());
+ }
+ break;
+ default:
+ ZEN_ASSERT_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType()));
+ break;
+ }
+
+ if (m_NodeStack.size() == 1)
+ {
+ std::string Yaml = ryml::emitrs_yaml<std::string>(m_Tree);
+ m_StrBuilder << Yaml;
+ }
+ }
+
+private:
+ StringBuilderBase& m_StrBuilder;
+ bool m_IsFirst = true;
+
+ ryml::Tree m_Tree;
+ std::vector<ryml::NodeRef> m_NodeStack;
+ ryml::NodeRef& Top() { return m_NodeStack.back(); }
+};
+
+void
+CompactBinaryToYaml(const CbObjectView& Object, StringBuilderBase& Builder)
+{
+ CbYamlWriter Writer(Builder);
+ Writer.WriteField(Object.AsFieldView());
+}
+
+void
+CompactBinaryToYaml(const CbArrayView& Array, StringBuilderBase& Builder)
+{
+ CbYamlWriter Writer(Builder);
+ Writer.WriteField(Array.AsFieldView());
+}
+
+#if ZEN_WITH_TESTS
+void
+cbyaml_forcelink()
+{
+}
+
+TEST_CASE("uson.yaml")
+{
+ using namespace std::literals;
+
+ SUBCASE("simple")
+ {
+ CbObjectWriter Writer;
+ Writer << "KeyOne"
+ << "ValueOne";
+ Writer << "KeyTwo"
+ << "ValueTwo";
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ CbYamlWriter YamlWriter(Sb);
+ YamlWriter.WriteField(Obj.AsFieldView());
+
+ CHECK_EQ(Sb.ToView(), "KeyOne: ValueOne\nKeyTwo: ValueTwo\n"sv);
+ }
+
+ SUBCASE("scalar_fields")
+ {
+ CbObjectWriter Writer;
+ Writer << "small_int"sv << 10;
+ Writer << "neg_small_int"sv << -10;
+ Writer << "small_real"sv << 10.5f;
+ Writer << "neg_small_real"sv << -10.5f;
+ Writer.AddNull("null_val"sv);
+ Writer.AddDateTimeTicks("date"sv, 622'033'000'000'000'000ull);
+ Writer.AddHash("hash"sv, IoHash::FromHexString("0011223344556677889900112233445566778899"sv));
+ Writer.AddObjectId("oid"sv, Oid::FromHexString("112233445566778899001122"sv));
+ Writer.AddTimeSpanTicks("dt"sv, 3'000'000'000'000ull);
+ Writer.AddUuid("guid"sv, Guid::FromString("E0596ADC-996A-4BA4-ACA3-A2A378AB2796"));
+ Writer.AddBool("yes"sv, true);
+ Writer.AddBool("no"sv, false);
+ CbObject Obj = Writer.Save();
+
+ ExtendableStringBuilder<128> Sb;
+ CbYamlWriter YamlWriter(Sb);
+ YamlWriter.WriteField(Obj.AsFieldView());
+
+ CHECK_EQ(Sb.ToView(),
+ "small_int: 10\n"
+ "neg_small_int: -10\n"
+ "small_real: 10.5\n"
+ "neg_small_real: -10.5\n"
+ "null_val: null\n"
+ "date: '1972-02-23T14:26:40.000Z'\n"
+ "hash: 0011223344556677889900112233445566778899\n"
+ "oid: 112233445566778899001122\n"
+ "dt: '+3.11:20:00.000000000'\n"
+ "guid: 'e0596adc-996a-4ba4-aca3-a2a378ab2796'\n"
+ "yes: true\n"
+ "no: false\n"sv);
+ }
+
+ SUBCASE("complex_fields")
+ {
+ CbObjectWriter Writer;
+ Writer.BeginObject("sub");
+ Writer.AddBool("no"sv, false);
+ Writer.BeginObject("sub");
+ Writer.AddBool("yes"sv, true);
+ Writer.BeginArray("seq");
+ Writer.AddInteger(1);
+ Writer.AddInteger(2);
+ Writer.AddInteger(3);
+ Writer.EndArray();
+ Writer.EndObject();
+ Writer.EndObject();
+ Writer.BeginArray("seq");
+ Writer.AddInteger(1);
+ Writer.AddInteger(2);
+ Writer.AddInteger(3);
+ Writer.EndArray();
+ Writer.BeginArray("mixed_seq");
+ Writer.AddInteger(1);
+ Writer.AddString("hello"sv);
+ Writer.AddFloat(44.4f);
+ Writer.BeginObject();
+ Writer.AddBool("yes"sv, true);
+ Writer.AddBool("no"sv, false);
+ Writer.EndObject();
+ Writer.EndArray();
+ CbObject Obj = Writer.Save();
+
+ ExtendableStringBuilder<128> Sb;
+ CbYamlWriter YamlWriter(Sb);
+ YamlWriter.WriteField(Obj.AsFieldView());
+
+ CHECK_EQ(Sb.ToView(),
+ R"(sub:
+ no: false
+ sub:
+ yes: true
+ seq:
+ - 1
+ - 2
+ - 3
+seq:
+ - 1
+ - 2
+ - 3
+mixed_seq:
+ - 1
+ - hello
+ - 44.4
+ - yes: true
+ no: false
+)"sv);
+ }
+}
+#endif
+
+} // namespace zen
diff --git a/src/zencore/include/zencore/compactbinary.h b/src/zencore/include/zencore/compactbinary.h
index 675e2a8d4..a8707ba2b 100644
--- a/src/zencore/include/zencore/compactbinary.h
+++ b/src/zencore/include/zencore/compactbinary.h
@@ -895,6 +895,11 @@ private:
ZENCORE_API void CompactBinaryToJson(const CbArrayView& Object, StringBuilderBase& Builder);
/**
+ * Serialize a compact binary array to YAML.
+ */
+ZENCORE_API void CompactBinaryToYaml(const CbArrayView& Object, StringBuilderBase& Builder);
+
+/**
* Array of CbField that have no names.
*
* Accessing a field of the array requires iteration. Access by index is not provided because the
@@ -974,6 +979,12 @@ public:
return Builder;
}
+ StringBuilderBase& ToYaml(StringBuilderBase& Builder) const
+ {
+ CompactBinaryToYaml(*this, Builder);
+ return Builder;
+ }
+
private:
friend inline CbFieldViewIterator begin(const CbArrayView& Array) { return Array.CreateViewIterator(); }
friend inline CbFieldViewIterator end(const CbArrayView&) { return CbFieldViewIterator(); }
@@ -986,6 +997,10 @@ private:
* Serialize a compact binary object to JSON.
*/
ZENCORE_API void CompactBinaryToJson(const CbObjectView& Object, StringBuilderBase& Builder);
+/**
+ * Serialize a compact binary object to YAML.
+ */
+ZENCORE_API void CompactBinaryToYaml(const CbObjectView& Object, StringBuilderBase& Builder);
class CbObjectView : protected CbFieldView
{
@@ -1075,6 +1090,12 @@ public:
return Builder;
}
+ StringBuilderBase& ToYaml(StringBuilderBase& Builder) const
+ {
+ CompactBinaryToYaml(*this, Builder);
+ return Builder;
+ }
+
private:
friend inline CbFieldViewIterator begin(const CbObjectView& Object) { return Object.CreateViewIterator(); }
friend inline CbFieldViewIterator end(const CbObjectView&) { return CbFieldViewIterator(); }
@@ -1497,12 +1518,19 @@ end(CbFieldView&)
}
/**
- * Serialize serialized compact binary blob to jaons. It must be 0 to n fields with including type for each field
+ * Serialize serialized compact binary blob to JSON. It must be 0 to n fields with including type for each field
*/
ZENCORE_API void CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder);
+/**
+ * Serialize serialized compact binary blob to YAML. It must be 0 to n fields with including type for each field
+ */
+ZENCORE_API void CompactBinaryToYaml(MemoryView Data, StringBuilderBase& InBuilder);
+
ZENCORE_API std::vector<CbFieldView> ReadCompactBinaryStream(MemoryView Data);
-void uson_forcelink(); // internal
+void uson_forcelink(); // internal
+void cbjson_forcelink(); // internal
+void cbyaml_forcelink(); // internal
} // namespace zen
diff --git a/src/zencore/xmake.lua b/src/zencore/xmake.lua
index 5420464fa..e6102679d 100644
--- a/src/zencore/xmake.lua
+++ b/src/zencore/xmake.lua
@@ -32,6 +32,8 @@ target('zencore')
add_packages(
"vcpkg::blake3",
"vcpkg::json11",
+ "vcpkg::ryml",
+ "vcpkg::c4core",
"vcpkg::mimalloc",
"vcpkg::openssl", -- required for crypto
"vcpkg::spdlog")
diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp
index b80b1d280..c97f5e5ca 100644
--- a/src/zencore/zencore.cpp
+++ b/src/zencore/zencore.cpp
@@ -160,6 +160,8 @@ zencore_forcelinktests()
zen::uson_forcelink();
zen::usonbuilder_forcelink();
zen::usonpackage_forcelink();
+ zen::cbjson_forcelink();
+ zen::cbyaml_forcelink();
zen::workthreadpool_forcelink();
}
} // namespace zen