Merge pull request #5511 from aws-lumberyard-dev/nvsickle/GenericDomJson

Generic DOM: Add DomBackend abstraction and JSON support
monroegm-disable-blank-issue-2
Nicholas Van Sickle 4 years ago committed by GitHub
commit 3d8664ee92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AZ::Dom
{
//! A DOM backend for serializing and deserializing JSON <=> UTF-8 text
//! \param ParseFlags Controls how deserialized JSON is parsed.
//! \param WriteFormat Controls how serialized JSON is formatted.
template<
Json::ParseFlags ParseFlags = Json::ParseFlags::ParseComments,
Json::OutputFormatting WriteFormat = Json::OutputFormatting::PrettyPrintedJson>
class JsonBackend final : public Backend
{
public:
Visitor::Result ReadFromBuffer(const char* buffer, size_t size, AZ::Dom::Lifetime lifetime, Visitor& visitor) override
{
return Json::VisitSerializedJson<ParseFlags>({ buffer, size }, lifetime, visitor);
}
Visitor::Result ReadFromBufferInPlace(char* buffer, [[maybe_unused]] AZStd::optional<size_t> size, Visitor& visitor) override
{
return Json::VisitSerializedJsonInPlace<ParseFlags>(buffer, visitor);
}
Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback)
{
AZ::IO::ByteContainerStream<AZStd::string> stream{ &buffer };
AZStd::unique_ptr<Visitor> visitor = Json::CreateJsonStreamWriter(stream, WriteFormat);
return callback(*visitor);
}
};
} // namespace AZ::Dom

@ -0,0 +1,582 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/TextStreamWriters.h>
#include <AzCore/JSON/filewritestream.h>
#include <AzCore/JSON/memorystream.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/JSON/rapidjson.h>
#include <AzCore/JSON/reader.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/optional.h>
namespace AZ::Dom::Json
{
//
// class RapidJsonValueWriter
//
RapidJsonValueWriter::RapidJsonValueWriter(rapidjson::Value& outputValue, rapidjson::Value::AllocatorType& allocator)
: m_result(outputValue)
, m_allocator(allocator)
{
}
VisitorFlags RapidJsonValueWriter::GetVisitorFlags() const
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects;
}
Visitor::Result RapidJsonValueWriter::Null()
{
CurrentValue().SetNull();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Bool(bool value)
{
CurrentValue().SetBool(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Int64(AZ::s64 value)
{
CurrentValue().SetInt64(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Uint64(AZ::u64 value)
{
CurrentValue().SetUint64(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Double(double value)
{
CurrentValue().SetDouble(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::String(AZStd::string_view value, Lifetime lifetime)
{
if (lifetime == Lifetime::Temporary)
{
CurrentValue().SetString(value.data(), aznumeric_cast<rapidjson::SizeType>(value.length()), m_allocator);
}
else
{
CurrentValue().SetString(value.data(), aznumeric_cast<rapidjson::SizeType>(value.length()));
}
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::StartObject()
{
CurrentValue().SetObject();
const bool isObject = true;
m_entryStack.emplace_front(isObject, CurrentValue());
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::EndObject(AZ::u64 attributeCount)
{
if (m_entryStack.empty())
{
return VisitorFailure(VisitorErrorCode::InternalError, "EndObject called without a matching BeginObject call");
}
const ValueInfo& frontEntry = m_entryStack.front();
if (!frontEntry.m_isObject)
{
return VisitorFailure(VisitorErrorCode::InternalError, "Expected EndArray and received EndObject instead");
}
if (frontEntry.m_entryCount != attributeCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"EndObject: Expected %llu attributes but received %llu attributes instead", attributeCount,
frontEntry.m_entryCount));
}
m_entryStack.pop_front();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Key(AZ::Name key)
{
return RawKey(key.GetStringView(), Lifetime::Persistent);
}
Visitor::Result RapidJsonValueWriter::RawKey(AZStd::string_view key, Lifetime lifetime)
{
AZ_Assert(!m_entryStack.empty(), "Attempmted to push a key with no object");
AZ_Assert(m_entryStack.front().m_isObject, "Attempted to push a key to an array");
if (lifetime == Lifetime::Persistent)
{
m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()));
}
else
{
m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()), m_allocator);
}
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::StartArray()
{
CurrentValue().SetArray();
const bool isObject = false;
m_entryStack.emplace_front(isObject, CurrentValue());
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::EndArray(AZ::u64 elementCount)
{
if (m_entryStack.empty())
{
return VisitorFailure(VisitorErrorCode::InternalError, "EndArray called without a matching BeginArray call");
}
const ValueInfo& frontEntry = m_entryStack.front();
if (frontEntry.m_isObject)
{
return VisitorFailure(VisitorErrorCode::InternalError, "Expected EndObject and received EndArray instead");
}
if (frontEntry.m_entryCount != elementCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"EndArray: Expected %llu elements but received %llu elements instead", elementCount, frontEntry.m_entryCount));
}
m_entryStack.pop_front();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::FinishWrite()
{
if (m_entryStack.empty())
{
return VisitorSuccess();
}
// Retrieve the top value of the stack and replace it with a null value
rapidjson::Value value;
m_entryStack.front().m_value.Swap(value);
ValueInfo& newEntry = m_entryStack.front();
++newEntry.m_entryCount;
if (newEntry.m_key.IsString())
{
newEntry.m_container.AddMember(m_entryStack.front().m_key.Move(), AZStd::move(value), m_allocator);
newEntry.m_key.SetNull();
}
else
{
newEntry.m_container.PushBack(AZStd::move(value), m_allocator);
}
return VisitorSuccess();
}
rapidjson::Value& RapidJsonValueWriter::CurrentValue()
{
if (m_entryStack.empty())
{
return m_result;
}
return m_entryStack.front().m_value;
}
RapidJsonValueWriter::ValueInfo::ValueInfo(bool isObject, rapidjson::Value& container)
: m_isObject(isObject)
, m_container(container)
{
}
//
// class StreamWriter
//
// Visitor that writes to a rapidjson::Writer
template<class Writer>
class StreamWriter : public Visitor
{
public:
StreamWriter(AZ::IO::GenericStream* stream)
: m_streamWriter(stream)
, m_writer(Writer(m_streamWriter))
{
}
VisitorFlags GetVisitorFlags() const override
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects;
}
Result Null() override
{
return CheckWrite(m_writer.Null());
}
Result Bool(bool value) override
{
return CheckWrite(m_writer.Bool(value));
}
Result Int64(AZ::s64 value) override
{
return CheckWrite(m_writer.Int64(value));
}
Result Uint64(AZ::u64 value) override
{
return CheckWrite(m_writer.Uint64(value));
}
Result Double(double value) override
{
return CheckWrite(m_writer.Double(value));
}
Result String(AZStd::string_view value, Lifetime lifetime) override
{
const bool shouldCopy = lifetime == Lifetime::Temporary;
return CheckWrite(m_writer.String(value.data(), aznumeric_cast<rapidjson::SizeType>(value.size()), shouldCopy));
}
Result StartObject() override
{
return CheckWrite(m_writer.StartObject());
}
Result EndObject(AZ::u64 attributeCount) override
{
return CheckWrite(m_writer.EndObject(aznumeric_cast<rapidjson::SizeType>(attributeCount)));
}
Result Key(AZ::Name key) override
{
return RawKey(key.GetStringView(), Lifetime::Persistent);
}
Result RawKey(AZStd::string_view key, Lifetime lifetime) override
{
const bool shouldCopy = lifetime == Lifetime::Temporary;
return CheckWrite(m_writer.Key(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()), shouldCopy));
}
Result StartArray() override
{
return CheckWrite(m_writer.StartArray());
}
Result EndArray(AZ::u64 elementCount) override
{
return CheckWrite(m_writer.EndArray(aznumeric_cast<rapidjson::SizeType>(elementCount)));
}
private:
Result CheckWrite(bool writeSucceeded)
{
if (writeSucceeded)
{
return VisitorSuccess();
}
else
{
return VisitorFailure(VisitorErrorCode::InternalError, "Failed to write JSON");
}
}
AZ::IO::RapidJSONStreamWriter m_streamWriter;
Writer m_writer;
};
//
// struct JsonReadHandler
//
RapidJsonReadHandler::RapidJsonReadHandler(Visitor* visitor, Lifetime stringLifetime)
: m_visitor(visitor)
, m_stringLifetime(stringLifetime)
, m_outcome(AZ::Success())
{
}
bool RapidJsonReadHandler::Null()
{
return CheckResult(m_visitor->Null());
}
bool RapidJsonReadHandler::Bool(bool b)
{
return CheckResult(m_visitor->Bool(b));
}
bool RapidJsonReadHandler::Int(int i)
{
return CheckResult(m_visitor->Int64(aznumeric_cast<AZ::s64>(i)));
}
bool RapidJsonReadHandler::Uint(unsigned i)
{
return CheckResult(m_visitor->Uint64(aznumeric_cast<AZ::u64>(i)));
}
bool RapidJsonReadHandler::Int64(int64_t i)
{
return CheckResult(m_visitor->Int64(i));
}
bool RapidJsonReadHandler::Uint64(uint64_t i)
{
return CheckResult(m_visitor->Uint64(i));
}
bool RapidJsonReadHandler::Double(double d)
{
return CheckResult(m_visitor->Double(d));
}
bool RapidJsonReadHandler::RawNumber(
[[maybe_unused]] const char* str, [[maybe_unused]] rapidjson::SizeType length, [[maybe_unused]] bool copy)
{
AZ_Assert(false, "Raw numbers are unsupported in the rapidjson DOM backend");
return false;
}
bool RapidJsonReadHandler::String(const char* str, rapidjson::SizeType length, bool copy)
{
const Lifetime lifetime = copy ? m_stringLifetime : Lifetime::Temporary;
return CheckResult(m_visitor->String(AZStd::string_view(str, length), lifetime));
}
bool RapidJsonReadHandler::StartObject()
{
return CheckResult(m_visitor->StartObject());
}
bool RapidJsonReadHandler::Key(const char* str, rapidjson::SizeType length, [[maybe_unused]] bool copy)
{
AZStd::string_view key = AZStd::string_view(str, length);
if (!m_visitor->SupportsRawKeys())
{
m_visitor->Key(AZ::Name(key));
}
const Lifetime lifetime = copy ? m_stringLifetime : Lifetime::Temporary;
return CheckResult(m_visitor->RawKey(key, lifetime));
}
bool RapidJsonReadHandler::EndObject([[maybe_unused]] rapidjson::SizeType memberCount)
{
return CheckResult(m_visitor->EndObject(memberCount));
}
bool RapidJsonReadHandler::StartArray()
{
return CheckResult(m_visitor->StartArray());
}
bool RapidJsonReadHandler::EndArray([[maybe_unused]] rapidjson::SizeType elementCount)
{
return CheckResult(m_visitor->EndArray(elementCount));
}
Visitor::Result&& RapidJsonReadHandler::TakeOutcome()
{
return AZStd::move(m_outcome);
}
bool RapidJsonReadHandler::CheckResult(Visitor::Result result)
{
if (result.IsSuccess())
{
return true;
}
else
{
m_outcome = AZStd::move(result);
return false;
}
}
//
// Serialized JSON util functions
//
AZStd::unique_ptr<Visitor> CreateJsonStreamWriter(AZ::IO::GenericStream& stream, OutputFormatting format)
{
if (format == OutputFormatting::MinifiedJson)
{
using WriterType = rapidjson::Writer<AZ::IO::RapidJSONStreamWriter>;
return AZStd::make_unique<StreamWriter<WriterType>>(&stream);
}
else
{
using WriterType = rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter>;
return AZStd::make_unique<StreamWriter<WriterType>>(&stream);
}
}
//
// In-memory rapidjson util functions
//
AZ::Outcome<rapidjson::Document, AZStd::string> WriteToRapidJsonDocument(Backend::WriteCallback writeCallback)
{
rapidjson::Document document;
RapidJsonValueWriter writer(document, document.GetAllocator());
auto result = writeCallback(writer);
if (!result.IsSuccess())
{
return AZ::Failure(result.TakeError().FormatVisitorErrorMessage());
}
return AZ::Success(AZStd::move(document));
}
Visitor::Result WriteToRapidJsonValue(
rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator, Backend::WriteCallback writeCallback)
{
RapidJsonValueWriter writer(value, allocator);
return writeCallback(writer);
}
Visitor::Result VisitRapidJsonValue(const rapidjson::Value& value, Visitor& visitor, Lifetime lifetime)
{
struct EndArrayMarker
{
};
struct EndObjectMarker
{
};
// Processing stack consists of values comprised of one of a:
// - rapidjson::Value to process
// - EndArrayMarker or EndObjectMarker denoting the end of an array or object
// - string denoting a key at the beginning of a key/value pair
using Entry = AZStd::variant<const rapidjson::Value*, EndArrayMarker, EndObjectMarker, AZStd::string_view>;
AZStd::stack<Entry> entryStack;
AZStd::stack<u64> entryCountStack;
entryStack.push(&value);
while (!entryStack.empty())
{
const Entry currentEntry = entryStack.top();
entryStack.pop();
Visitor::Result result = AZ::Success();
AZStd::visit(
[&visitor, &entryStack, &entryCountStack, &result, lifetime](auto&& arg)
{
using Alternative = AZStd::decay_t<decltype(arg)>;
if constexpr (AZStd::is_same_v<Alternative, const rapidjson::Value*>)
{
const rapidjson::Value& currentValue = *arg;
if (!entryCountStack.empty())
{
++entryCountStack.top();
}
switch (currentValue.GetType())
{
case rapidjson::kNullType:
result = visitor.Null();
break;
case rapidjson::kFalseType:
result = visitor.Bool(false);
break;
case rapidjson::kTrueType:
result = visitor.Bool(true);
break;
case rapidjson::kObjectType:
entryStack.push(EndObjectMarker{});
entryCountStack.push(0);
result = visitor.StartObject();
for (auto it = currentValue.MemberEnd(); it != currentValue.MemberBegin(); --it)
{
auto entry = (it - 1);
const AZStd::string_view key(
entry->name.GetString(), aznumeric_cast<size_t>(entry->name.GetStringLength()));
entryStack.push(&entry->value);
entryStack.push(key);
}
break;
case rapidjson::kArrayType:
entryStack.push(EndArrayMarker{});
entryCountStack.push(0);
result = visitor.StartArray();
for (auto it = currentValue.End(); it != currentValue.Begin(); --it)
{
auto entry = (it - 1);
entryStack.push(entry);
}
break;
case rapidjson::kStringType:
result = visitor.String(
AZStd::string_view(currentValue.GetString(), aznumeric_cast<size_t>(currentValue.GetStringLength())),
lifetime);
break;
case rapidjson::kNumberType:
if (currentValue.IsFloat() || currentValue.IsDouble())
{
result = visitor.Double(currentValue.GetDouble());
}
else if (currentValue.IsInt64() || currentValue.IsInt())
{
result = visitor.Int64(currentValue.GetInt64());
}
else
{
result = visitor.Uint64(currentValue.GetUint64());
}
break;
default:
result = AZ::Failure(VisitorError(VisitorErrorCode::InvalidData, "Value with invalid type specified"));
}
}
else if constexpr (AZStd::is_same_v<Alternative, EndArrayMarker>)
{
result = visitor.EndArray(entryCountStack.top());
entryCountStack.pop();
}
else if constexpr (AZStd::is_same_v<Alternative, EndObjectMarker>)
{
result = visitor.EndObject(entryCountStack.top());
entryCountStack.pop();
}
else if constexpr (AZStd::is_same_v<Alternative, AZStd::string_view>)
{
if (visitor.SupportsRawKeys())
{
visitor.RawKey(arg, lifetime);
}
else
{
visitor.Key(AZ::Name(arg));
}
}
},
currentEntry);
if (!result.IsSuccess())
{
return result;
}
}
return AZ::Success();
}
} // namespace AZ::Dom::Json

@ -0,0 +1,256 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/DOM/DomVisitor.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/document.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::Dom::Json
{
//! Specifies how JSON should be formatted when serialized.
enum class OutputFormatting
{
MinifiedJson, //!< Formats JSON in compact minified form, focusing on minimizing output size.
PrettyPrintedJson, //!< Formats JSON in a pretty printed form, focusing on legibility to readers.
};
//! Specifies parsing behavior when deserializing JSON.
enum class ParseFlags : int
{
Null = 0,
StopWhenDone = rapidjson::kParseStopWhenDoneFlag,
FullFloatingPointPrecision = rapidjson::kParseFullPrecisionFlag,
ParseComments = rapidjson::kParseCommentsFlag,
ParseNumbersAsStrings = rapidjson::kParseNumbersAsStringsFlag,
ParseTrailingCommas = rapidjson::kParseTrailingCommasFlag,
ParseNanAndInfinity = rapidjson::kParseNanAndInfFlag,
ParseEscapedApostrophies = rapidjson::kParseEscapedApostropheFlag,
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ParseFlags);
//! Visitor that feeds into a rapidjson::Value
class RapidJsonValueWriter final : public Visitor
{
public:
RapidJsonValueWriter(rapidjson::Value& outputValue, rapidjson::Value::AllocatorType& allocator);
VisitorFlags GetVisitorFlags() const override;
Result Null() override;
Result Bool(bool value) override;
Result Int64(AZ::s64 value) override;
Result Uint64(AZ::u64 value) override;
Result Double(double value) override;
Result String(AZStd::string_view value, Lifetime lifetime) override;
Result StartObject() override;
Result EndObject(AZ::u64 attributeCount) override;
Result Key(AZ::Name key) override;
Result RawKey(AZStd::string_view key, Lifetime lifetime) override;
Result StartArray() override;
Result EndArray(AZ::u64 elementCount) override;
private:
Result FinishWrite();
rapidjson::Value& CurrentValue();
struct ValueInfo
{
ValueInfo(bool isObject, rapidjson::Value& container);
rapidjson::Value m_key;
rapidjson::Value m_value;
rapidjson::Value& m_container;
AZ::u64 m_entryCount = 0;
bool m_isObject;
};
rapidjson::Value& m_result;
rapidjson::Value::AllocatorType& m_allocator;
AZStd::deque<ValueInfo> m_entryStack;
};
//! Handler for a rapidjson::Reader that translates reads into an AZ::Dom::Visitor
struct RapidJsonReadHandler
{
public:
RapidJsonReadHandler(Visitor* visitor, Lifetime stringLifetime);
bool Null();
bool Bool(bool b);
bool Int(int i);
bool Uint(unsigned i);
bool Int64(int64_t i);
bool Uint64(uint64_t i);
bool Double(double d);
bool RawNumber(const char* str, rapidjson::SizeType length, bool copy);
bool String(const char* str, rapidjson::SizeType length, bool copy);
bool StartObject();
bool Key(const char* str, rapidjson::SizeType length, bool copy);
bool EndObject(rapidjson::SizeType memberCount);
bool StartArray();
bool EndArray(rapidjson::SizeType elementCount);
Visitor::Result&& TakeOutcome();
private:
bool CheckResult(Visitor::Result result);
Visitor::Result m_outcome;
Visitor* m_visitor;
Lifetime m_stringLifetime;
};
//! rapidjson stream wrapper for AZStd::string suitable for in-situ parsing
//! Faster than rapidjson::MemoryStream for reading from AZStd::string / AZStd::string_view (because it requires a null terminator)
//! \note This needs to be inlined for performance reasons.
struct NullDelimitedStringStream
{
using Ch = char; //<! Denotes the string character storage type for rapidjson
AZ_FORCE_INLINE NullDelimitedStringStream(char* buffer)
{
m_cursor = buffer;
m_begin = m_cursor;
}
AZ_FORCE_INLINE NullDelimitedStringStream(AZStd::string_view buffer)
{
// rapidjson won't actually call PutBegin or Put unless kParseInSituFlag is set, so this is safe
m_cursor = const_cast<char*>(buffer.data());
m_begin = m_cursor;
}
AZ_FORCE_INLINE char Peek() const
{
return *m_cursor;
}
AZ_FORCE_INLINE char Take()
{
return *m_cursor++;
}
AZ_FORCE_INLINE size_t Tell() const
{
return static_cast<size_t>(m_cursor - m_begin);
}
AZ_FORCE_INLINE char* PutBegin()
{
m_write = m_cursor;
return m_cursor;
}
AZ_FORCE_INLINE void Put(char c)
{
(*m_write++) = c;
}
AZ_FORCE_INLINE void Flush()
{
}
AZ_FORCE_INLINE size_t PutEnd(char* begin)
{
return m_write - begin;
}
AZ_FORCE_INLINE const char* Peek4() const
{
AZ_Assert(false, "Not implemented, encoding is hard-coded to UTF-8");
return m_cursor;
}
char* m_cursor; //!< Current read position.
char* m_write; //!< Current write position.
const char* m_begin; //!< Head of string.
};
//! Creates a Visitor that will write serialized JSON to the specified stream.
//! \param stream The stream the visitor will write to.
//! \param format The format to write in.
//! \return A Visitor that will write to stream when visited.
AZStd::unique_ptr<Visitor> CreateJsonStreamWriter(
AZ::IO::GenericStream& stream, OutputFormatting format = OutputFormatting::PrettyPrintedJson);
//! Reads serialized JSON from a string and applies it to a visitor.
//! \param buffer The UTF-8 serialized JSON to read.
//! \param lifetime Specifies the lifetime of the specified buffer. If the string specified by buffer might be deallocated,
//! ensure Lifetime::Temporary is specified.
//! \param visitor The visitor to visit with the JSON buffer's contents.
//! \param parseFlags (template) Settings for adjusting parser behavior.
//! \return The aggregate result specifying whether the visitor operations were successful.
template<ParseFlags parseFlags = ParseFlags::ParseComments>
Visitor::Result VisitSerializedJson(AZStd::string_view buffer, Lifetime lifetime, Visitor& visitor);
//! Reads serialized JSON from a string in-place and applies it to a visitor.
//! \param buffer The UTF-8 serialized JSON to read. This buffer will be modified as part of the deserialization process to
//! apply null terminators.
//! \param visitor The visitor to visit with the JSON buffer's contents. The strings provided to the visitor will only
//! be valid for the lifetime of buffer.
//! \param parseFlags (template) Settings for adjusting parser behavior.
//! \return The aggregate result specifying whether the visitor operations were successful.
template<ParseFlags parseFlags = ParseFlags::ParseComments>
Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor);
//! Takes a visitor specified by a callback and produces a rapidjson::Document.
//! \param writeCallback A callback specifying a visitor to accept to build the resulting document.
//! \return An outcome with either the rapidjson::Document or an error message.
AZ::Outcome<rapidjson::Document, AZStd::string> WriteToRapidJsonDocument(Backend::WriteCallback writeCallback);
//! Takes a visitor specified by a callback and reads them into a rapidjson::Value.
//! \param value The value to read into, its contents will be overridden.
//! \param allocator The allocator to use when performing rapidjson allocations (generally provded by the rapidjson::Document).
//! \param writeCallback A callback specifying a visitor to accept to build the resulting document.
//! \return An outcome with either the rapidjson::Document or an error message.
Visitor::Result WriteToRapidJsonValue(
rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator, Backend::WriteCallback writeCallback);
//! Accepts a visitor with the contents of a rapidjson::Value.
//! \param value The rapidjson::Value to apply to visitor.
//! \param visitor The visitor to receive the contents of value.
//! \param lifetime The lifetime to specify for visiting strings. If the rapidjson::Value might be destroyed or changed
//! before the visitor is finished using these values, Lifetime::Temporary should be specified.
//! \return The aggregate result specifying whether the visitor operations were successful.
Visitor::Result VisitRapidJsonValue(const rapidjson::Value& value, Visitor& visitor, Lifetime lifetime);
template<ParseFlags parseFlags>
Visitor::Result VisitSerializedJson(AZStd::string_view buffer, Lifetime lifetime, Visitor& visitor)
{
rapidjson::Reader reader;
RapidJsonReadHandler handler(&visitor, lifetime);
// If the string is null terminated, we can use the faster AzStringStream path - otherwise we fall back on rapidjson::MemoryStream
if (buffer.data()[buffer.size()] == '\0')
{
NullDelimitedStringStream stream(buffer);
reader.Parse<aznumeric_cast<unsigned>(parseFlags)>(stream, handler);
}
else
{
rapidjson::MemoryStream stream(buffer.data(), buffer.size());
reader.Parse<aznumeric_cast<unsigned>(parseFlags)>(stream, handler);
}
return handler.TakeOutcome();
}
template<ParseFlags parseFlags>
Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor)
{
rapidjson::Reader reader;
NullDelimitedStringStream stream(buffer);
RapidJsonReadHandler handler(&visitor, Lifetime::Persistent);
reader.Parse<aznumeric_cast<unsigned>(parseFlags) | rapidjson::kParseInsituFlag>(stream, handler);
return handler.TakeOutcome();
}
} // namespace AZ::Dom::Json

@ -0,0 +1,17 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomBackend.h>
namespace AZ::Dom
{
Visitor::Result Backend::ReadFromBufferInPlace(char* buffer, AZStd::optional<size_t> size, Visitor& visitor)
{
return ReadFromBuffer(buffer, size.value_or(strlen(buffer)), AZ::Dom::Lifetime::Persistent, visitor);
}
} // namespace AZ::Dom

@ -0,0 +1,44 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomVisitor.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::Dom
{
//! Backends are registered centrally and used to transition DOM formats to and from a textual format.
class Backend
{
public:
virtual ~Backend() = default;
//! Attempt to read this format from the given buffer into the target Visitor.
virtual Visitor::Result ReadFromBuffer(
const char* buffer, size_t size, AZ::Dom::Lifetime lifetime, Visitor& visitor) = 0;
//! Attempt to read this format from a mutable string into the target Visitor. This enables some backends to
//! parse without making additional string allocations.
//! This string must be null terminated.
//! This string may be modified and read in place without being copied, so when calling this please ensure that:
//! - The string won't be deallocated until the visitor no longer needs the values and
//! - The string is safe to modify in place.
//! The base implementation simply calls ReadFromBuffer.
virtual Visitor::Result ReadFromBufferInPlace(char* buffer, AZStd::optional<size_t> size, Visitor& visitor);
//! A callback that accepts a Visitor, making DOM calls to inform the serializer, and returns an
//! aggregate error code to indicate whether or not the operation succeeded.
using WriteCallback = AZStd::function<Visitor::Result(Visitor&)>;
//! Attempt to write a value to the specified string using a write callback.
virtual Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback) = 0;
};
} // namespace AZ::Dom

@ -0,0 +1,24 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomUtils.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AZ::Dom::Utils
{
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor)
{
return backend.ReadFromBuffer(string.data(), string.length(), lifetime, visitor);
}
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor)
{
return backend.ReadFromBufferInPlace(string.data(), string.size(), visitor);
}
}

@ -0,0 +1,17 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomBackend.h>
namespace AZ::Dom::Utils
{
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor);
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor);
} // namespace AZ::Dom::Utils

@ -8,7 +8,7 @@
#include <AzCore/DOM/DomVisitor.h>
namespace AZ::DOM
namespace AZ::Dom
{
const char* VisitorError::CodeToString(VisitorErrorCode code)
{
@ -236,4 +236,4 @@ namespace AZ::DOM
{
return (GetVisitorFlags() & VisitorFlags::SupportsOpaqueValues) != VisitorFlags::Null;
}
} // namespace AZ::DOM
} // namespace AZ::Dom

@ -13,11 +13,11 @@
#include <AzCore/std/any.h>
#include <AzCore/std/string/string.h>
namespace AZ::DOM
namespace AZ::Dom
{
//
// Lifetime enum
//
//
//! Specifies the period in which a reference value will still be alive and safe to read.
enum class Lifetime
{
@ -30,7 +30,7 @@ namespace AZ::DOM
//
// VisitorErrorCode enum
//
//
//! Error code specifying the reason a Visitor operation failed.
enum class VisitorErrorCode
{
@ -75,7 +75,7 @@ namespace AZ::DOM
};
//! A type alias for opaque DOM types that aren't meant to be serializable.
//! /see VisitorInterface::OpaqueValue
//! \see VisitorInterface::OpaqueValue
using OpaqueType = AZStd::any;
//
@ -116,7 +116,7 @@ namespace AZ::DOM
//! - \ref Double: 64 bit double precision float
//! - \ref Null: sentinel "empty" type with no value representation
//! - \ref String: UTF8 encoded string
//! - \ref Object: an ordered container of key/value pairs where keys are AZ::Names and values may be any DOM type
//! - \ref Object: an ordered container of key/value pairs where keys are \ref AZ::Name and values may be any DOM type
//! (including Object)
//! - \ref Array: an ordered container of values, in which values are any DOM value type (including Array)
//! - \ref Node: a container
@ -144,17 +144,17 @@ namespace AZ::DOM
//! Raw (\see VisitorFlags::SupportsRawValues) and opaque values (\see VisitorFlags::SupportsOpaqueValues)
//! are disallowed by default, as their handling is intended to be implementation-specific.
virtual VisitorFlags GetVisitorFlags() const;
//! /see VisitorFlags::SupportsRawValues
//! \see VisitorFlags::SupportsRawValues
bool SupportsRawValues() const;
//! /see VisitorFlags::SupportsRawKeys
//! \see VisitorFlags::SupportsRawKeys
bool SupportsRawKeys() const;
//! /see VisitorFlags::SupportsObjects
//! \see VisitorFlags::SupportsObjects
bool SupportsObjects() const;
//! /see VisitorFlags::SupportsArrays
//! \see VisitorFlags::SupportsArrays
bool SupportsArrays() const;
//! /see VisitorFlags::SupportsNodes
//! \see VisitorFlags::SupportsNodes
bool SupportsNodes() const;
//! /see VisitorFlags::SupportsOpaqueValues
//! \see VisitorFlags::SupportsOpaqueValues
bool SupportsOpaqueValues() const;
//! Operates on an empty null value.
@ -231,7 +231,8 @@ namespace AZ::DOM
static Result VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo);
//! Helper method, constructs a failure \ref Result with the specified error.
static Result VisitorFailure(VisitorError error);
//! Helper method, constructs a success \ref Result.
static Result VisitorSuccess();
};
} // namespace AZ::DOM
} // namespace AZ::Dom

@ -125,8 +125,15 @@ set(FILES
Debug/TraceMessagesDrillerBus.h
Debug/TraceReflection.cpp
Debug/TraceReflection.h
DOM/DomBackend.cpp
DOM/DomBackend.h
DOM/DomUtils.cpp
DOM/DomUtils.h
DOM/DomVisitor.cpp
DOM/DomVisitor.h
DOM/Backends/JSON/JsonBackend.h
DOM/Backends/JSON/JsonSerializationUtils.cpp
DOM/Backends/JSON/JsonSerializationUtils.h
Driller/DefaultStringPool.h
Driller/Driller.cpp
Driller/Driller.h

@ -0,0 +1,185 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#if defined(HAVE_BENCHMARK)
#include <AzCore/DOM/DomUtils.h>
#include <AzCore/DOM/Backends/JSON/JsonBackend.h>
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/JSON/document.h>
#include <AzCore/Name/NameDictionary.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace Benchmark
{
class DomJsonBenchmark : public UnitTest::AllocatorsBenchmarkFixture
{
public:
void SetUp(const ::benchmark::State& st) override
{
UnitTest::AllocatorsBenchmarkFixture::SetUp(st);
AZ::NameDictionary::Create();
}
void SetUp(::benchmark::State& st) override
{
UnitTest::AllocatorsBenchmarkFixture::SetUp(st);
AZ::NameDictionary::Create();
}
void TearDown(::benchmark::State& st) override
{
AZ::NameDictionary::Destroy();
UnitTest::AllocatorsBenchmarkFixture::TearDown(st);
}
void TearDown(const ::benchmark::State& st) override
{
AZ::NameDictionary::Destroy();
UnitTest::AllocatorsBenchmarkFixture::TearDown(st);
}
AZStd::string GenerateDomJsonBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength)
{
rapidjson::Document document;
document.SetObject();
AZStd::string entryTemplate;
while (entryTemplate.size() < static_cast<size_t>(stringTemplateLength))
{
entryTemplate += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ";
}
entryTemplate.resize(stringTemplateLength);
AZStd::string buffer;
auto createString = [&](int n) -> rapidjson::Value
{
buffer = AZStd::string::format("#%i %s", n, entryTemplate.c_str());
return rapidjson::Value(buffer.data(), static_cast<rapidjson::SizeType>(buffer.size()), document.GetAllocator());
};
auto createEntry = [&](int n) -> rapidjson::Value
{
rapidjson::Value entry(rapidjson::kObjectType);
entry.AddMember("string", createString(n), document.GetAllocator());
entry.AddMember("int", rapidjson::Value(n), document.GetAllocator());
entry.AddMember("double", rapidjson::Value(static_cast<double>(n) * 0.5), document.GetAllocator());
entry.AddMember("bool", rapidjson::Value(n % 2 == 0), document.GetAllocator());
entry.AddMember("null", rapidjson::Value(rapidjson::kNullType), document.GetAllocator());
return entry;
};
auto createArray = [&]() -> rapidjson::Value
{
rapidjson::Value array;
array.SetArray();
for (int i = 0; i < entryCount; ++i)
{
array.PushBack(createEntry(i), document.GetAllocator());
}
return array;
};
auto createObject = [&]() -> rapidjson::Value
{
rapidjson::Value object;
object.SetObject();
for (int i = 0; i < entryCount; ++i)
{
buffer = AZStd::string::format("Key%i", i);
rapidjson::Value key;
key.SetString(buffer.data(), static_cast<rapidjson::SizeType>(buffer.length()), document.GetAllocator());
object.AddMember(key.Move(), createArray(), document.GetAllocator());
}
return object;
};
document.SetObject();
document.AddMember("entries", createObject(), document.GetAllocator());
AZStd::string serializedJson;
auto result = AZ::JsonSerializationUtils::WriteJsonString(document, serializedJson);
AZ_Assert(result.IsSuccess(), "Failed to serialize generated JSON");
return serializedJson;
}
};
// Helper macro for registering JSON benchmarks
#define BENCHMARK_REGISTER_JSON(BaseClass, Method) \
BENCHMARK_REGISTER_F(BaseClass, Method) \
->Args({ 10, 5 }) \
->Args({ 10, 500 }) \
->Args({ 100, 5 }) \
->Args({ 100, 500 }) \
->Unit(benchmark::kMillisecond);
BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocumentInPlace)(benchmark::State& state)
{
AZ::Dom::JsonBackend backend;
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
for (auto _ : state)
{
state.PauseTiming();
AZStd::string payloadCopy = serializedPayload;
state.ResumeTiming();
auto result = AZ::Dom::Json::WriteToRapidJsonDocument(
[&](AZ::Dom::Visitor& visitor)
{
return AZ::Dom::Utils::ReadFromStringInPlace(backend, payloadCopy, visitor);
});
benchmark::DoNotOptimize(result.GetValue());
}
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
}
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocumentInPlace)
BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocument)(benchmark::State& state)
{
AZ::Dom::JsonBackend backend;
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
for (auto _ : state)
{
auto result = AZ::Dom::Json::WriteToRapidJsonDocument(
[&](AZ::Dom::Visitor& visitor)
{
return AZ::Dom::Utils::ReadFromString(backend, serializedPayload, AZ::Dom::Lifetime::Temporary, visitor);
});
benchmark::DoNotOptimize(result.GetValue());
}
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
}
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocument)
BENCHMARK_DEFINE_F(DomJsonBenchmark, JsonUtilsDeserializeToDocument)(benchmark::State& state)
{
AZ::Dom::JsonBackend backend;
AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1));
for (auto _ : state)
{
auto result = AZ::JsonSerializationUtils::ReadJsonString(serializedPayload);
benchmark::DoNotOptimize(result.GetValue());
}
state.SetBytesProcessed(serializedPayload.size() * state.iterations());
}
BENCHMARK_REGISTER_JSON(DomJsonBenchmark, JsonUtilsDeserializeToDocument)
#undef BENCHMARK_REGISTER_JSON
} // namespace Benchmark
#endif // defined(HAVE_BENCHMARK)

@ -0,0 +1,219 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/Backends/JSON/JsonBackend.h>
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/DOM/DomUtils.h>
#include <AzCore/Name/NameDictionary.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace AZ::Dom::Tests
{
class DomJsonTests : public UnitTest::AllocatorsFixture
{
public:
void SetUp() override
{
UnitTest::AllocatorsFixture::SetUp();
NameDictionary::Create();
m_document = AZStd::make_unique<rapidjson::Document>();
}
void TearDown() override
{
m_document.reset();
NameDictionary::Destroy();
UnitTest::AllocatorsFixture::TearDown();
}
rapidjson::Value CreateString(const AZStd::string& text)
{
rapidjson::Value key;
key.SetString(text.c_str(), static_cast<rapidjson::SizeType>(text.length()), m_document->GetAllocator());
return key;
}
template<class T>
void AddValue(const AZStd::string& key, T value)
{
m_document->AddMember(CreateString(key), rapidjson::Value(value), m_document->GetAllocator());
}
// Validate round-trip serialization to and from rapidjson::Document and a UTF-8 encoded string
void PerformSerializationChecks()
{
// Generate a canonical serializaed representation of this document using rapidjson
// This will be pretty-printed using the same rapidjson pretty printer we use, so should be binary identical
// to any output generated by the visitor API
AZStd::string canonicalSerializedDocument;
AZ::JsonSerializationUtils::WriteJsonString(*m_document, canonicalSerializedDocument);
auto visitDocumentFn = [this](AZ::Dom::Visitor& visitor)
{
return Json::VisitRapidJsonValue(*m_document, visitor, Lifetime::Temporary);
};
// Document -> Document
{
auto result = Json::WriteToRapidJsonDocument(visitDocumentFn);
EXPECT_TRUE(result.IsSuccess());
EXPECT_EQ(AZ::JsonSerialization::Compare(*m_document, result.GetValue()), AZ::JsonSerializerCompareResult::Equal);
}
// Document -> string
{
AZStd::string serializedDocument;
JsonBackend backend;
auto result = backend.WriteToBuffer(serializedDocument, visitDocumentFn);
EXPECT_TRUE(result.IsSuccess());
EXPECT_EQ(canonicalSerializedDocument, serializedDocument);
}
// string -> Document
{
auto result = Json::WriteToRapidJsonDocument(
[&canonicalSerializedDocument](AZ::Dom::Visitor& visitor)
{
JsonBackend backend;
return Dom::Utils::ReadFromString(backend, canonicalSerializedDocument, Lifetime::Temporary, visitor);
});
EXPECT_TRUE(result.IsSuccess());
EXPECT_EQ(AZ::JsonSerialization::Compare(*m_document, result.GetValue()), JsonSerializerCompareResult::Equal);
}
// string -> string
{
AZStd::string serializedDocument;
JsonBackend backend;
auto result = backend.WriteToBuffer(
serializedDocument,
[&backend, &canonicalSerializedDocument](AZ::Dom::Visitor& visitor)
{
return Dom::Utils::ReadFromString(backend, canonicalSerializedDocument, Lifetime::Temporary, visitor);
});
EXPECT_TRUE(result.IsSuccess());
EXPECT_EQ(canonicalSerializedDocument, serializedDocument);
}
}
AZStd::unique_ptr<rapidjson::Document> m_document;
};
TEST_F(DomJsonTests, EmptyArray)
{
m_document->SetArray();
PerformSerializationChecks();
}
TEST_F(DomJsonTests, SimpleArray)
{
m_document->SetArray();
for (int i = 0; i < 5; ++i)
{
m_document->PushBack(i, m_document->GetAllocator());
}
PerformSerializationChecks();
}
TEST_F(DomJsonTests, NestedArrays)
{
m_document->SetArray();
for (int j = 0; j < 7; ++j)
{
rapidjson::Value nestedArray(rapidjson::kArrayType);
for (int i = 0; i < 5; ++i)
{
nestedArray.PushBack(i, m_document->GetAllocator());
}
m_document->PushBack(nestedArray.Move(), m_document->GetAllocator());
}
PerformSerializationChecks();
}
TEST_F(DomJsonTests, EmptyObject)
{
m_document->SetObject();
PerformSerializationChecks();
}
TEST_F(DomJsonTests, SimpleObject)
{
m_document->SetObject();
for (int i = 0; i < 5; ++i)
{
m_document->AddMember(CreateString(AZStd::string::format("Key%i", i)), rapidjson::Value(i), m_document->GetAllocator());
}
PerformSerializationChecks();
}
TEST_F(DomJsonTests, NestedObjects)
{
m_document->SetObject();
for (int j = 0; j < 7; ++j)
{
rapidjson::Value nestedObject(rapidjson::kObjectType);
for (int i = 0; i < 5; ++i)
{
nestedObject.AddMember(CreateString(AZStd::string::format("Key%i", i)), rapidjson::Value(i), m_document->GetAllocator());
}
m_document->AddMember(CreateString(AZStd::string::format("Obj%i", j)), nestedObject.Move(), m_document->GetAllocator());
}
PerformSerializationChecks();
}
TEST_F(DomJsonTests, Int64)
{
m_document->SetObject();
AddValue("int64_min", AZStd::numeric_limits<int64_t>::min());
AddValue("int64_max", AZStd::numeric_limits<int64_t>::max());
PerformSerializationChecks();
}
TEST_F(DomJsonTests, Uint64)
{
m_document->SetObject();
AddValue("uint64_min", AZStd::numeric_limits<uint64_t>::min());
AddValue("uint64_max", AZStd::numeric_limits<uint64_t>::max());
PerformSerializationChecks();
}
TEST_F(DomJsonTests, Double)
{
m_document->SetObject();
AddValue("double_min", AZStd::numeric_limits<double>::min());
AddValue("double_max", AZStd::numeric_limits<double>::max());
PerformSerializationChecks();
}
TEST_F(DomJsonTests, Null)
{
m_document->SetObject();
m_document->AddMember(CreateString("null_value"), rapidjson::Value(rapidjson::kNullType), m_document->GetAllocator());
PerformSerializationChecks();
}
TEST_F(DomJsonTests, Bool)
{
m_document->SetObject();
AddValue("true_value", true);
AddValue("false_value", false);
PerformSerializationChecks();
}
TEST_F(DomJsonTests, String)
{
m_document->SetObject();
m_document->AddMember(CreateString("empty_string"), CreateString(""), m_document->GetAllocator());
m_document->AddMember(CreateString("short_string"), CreateString("test"), m_document->GetAllocator());
m_document->AddMember(
CreateString("long_string"), CreateString("abcdefghijklmnopqrstuvwxyz0123456789"), m_document->GetAllocator());
PerformSerializationChecks();
}
} // namespace AZ::Dom::Tests

@ -213,6 +213,8 @@ set(FILES
AZStd/Variant.cpp
AZStd/VariantSerialization.cpp
AZStd/VectorAndArray.cpp
DOM/DomJsonTests.cpp
DOM/DomJsonBenchmarks.cpp
)
# Prevent the following files from being grouped in UNITY builds

Loading…
Cancel
Save