Files
UnrealEngine/Engine/Source/Runtime/Json/Public/Serialization/JsonWriter.h
2025-05-18 13:04:45 +08:00

702 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Utf8String.h"
#include "CoreMinimal.h"
#include "Serialization/JsonTypes.h"
#include "Policies/PrettyJsonPrintPolicy.h"
#include "Serialization/MemoryWriter.h"
/**
* Takes an input source char representing and returns if it is possible to represent in DstChar expected encoding
*
* @param Char source char
* @return false if character is a control char or if it is out of range of representation in case of converting to ANSICHAR. true otherwise
**/
template <typename DstChar, typename SrcChar>
bool HasDestinationJsonStringCharRepresentation(SrcChar Char)
{
return Char >= CHARTEXT(SrcChar, ' ') && (std::is_same_v<DstChar, SrcChar> || !std::is_same_v<DstChar, ANSICHAR> || Char <= (SrcChar)0x7E);
}
/**
* Takes an input string and escapes it so it can be written as a valid Json string. Also adds the quotes.
* Appends to a given string-like object to avoid reallocations.
* String-like object must support operator+=(const TCHAR*) and operation+=(TCHAR)
*
* @param AppendTo the string to append to.
* @param StringVal the string to escape
* @return the AppendTo string for convenience.
*/
template<typename StringType>
inline StringType& AppendEscapeJsonString(StringType& AppendTo, const FString& StringVal)
{
using CharType = typename StringType::ElementType;
AppendTo += TEXT("\"");
for (const CharType* Char = *StringVal; *Char != CHARTEXT(CharType, '\0'); ++Char)
{
switch (*Char)
{
case CHARTEXT(CharType, '\\'): AppendTo += CHARTEXT(CharType, "\\\\"); break;
case CHARTEXT(CharType, '\n'): AppendTo += CHARTEXT(CharType, "\\n"); break;
case CHARTEXT(CharType, '\t'): AppendTo += CHARTEXT(CharType, "\\t"); break;
case CHARTEXT(CharType, '\b'): AppendTo += CHARTEXT(CharType, "\\b"); break;
case CHARTEXT(CharType, '\f'): AppendTo += CHARTEXT(CharType, "\\f"); break;
case CHARTEXT(CharType, '\r'): AppendTo += CHARTEXT(CharType, "\\r"); break;
case CHARTEXT(CharType, '\"'): AppendTo += CHARTEXT(CharType, "\\\""); break;
default:
// Must escape control characters or non representable characters
if (HasDestinationJsonStringCharRepresentation<CharType>(*Char) )
{
AppendTo += *Char;
}
else
{
AppendTo.Appendf(CHARTEXT(CharType, "\\u%04x"), *Char);
}
}
}
AppendTo += CHARTEXT(CharType, "\"");
return AppendTo;
}
/**
* Takes an input string and escapes it so it can be written as a valid Json string. Also adds the quotes.
*
* @param StringVal the string to escape
* @return the given string, escaped to produce a valid Json string.
*/
inline FString EscapeJsonString(const FString& StringVal)
{
FString Result;
return AppendEscapeJsonString(Result, StringVal);
}
/**
* Template for Json writers.
*
* @param CharType The type of characters to print, i.e. TCHAR or ANSICHAR.
* @param PrintPolicy The print policy to use when writing the output string (default = TPrettyJsonPrintPolicy).
*/
template <class CharType = TCHAR, class PrintPolicy = TPrettyJsonPrintPolicy<CharType> >
class TJsonWriter
{
public:
static TSharedRef< TJsonWriter > Create( FArchive* const Stream, int32 InitialIndentLevel = 0 )
{
return MakeShareable( new TJsonWriter< CharType, PrintPolicy >( Stream, InitialIndentLevel ) );
}
public:
virtual ~TJsonWriter() { }
FORCEINLINE int32 GetIndentLevel() const { return IndentLevel; }
bool CanWriteObjectStart() const
{
return CanWriteObjectWithoutIdentifier();
}
EJson GetCurrentElementType() const
{
return Stack.Num() > 0 ? Stack.Top() : EJson::None;
}
void WriteObjectStart()
{
check(CanWriteObjectWithoutIdentifier());
WriteCommaIfNeeded();
if ( PreviousTokenWritten != EJsonToken::None )
{
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
}
PrintPolicy::WriteChar(Stream, CharType('{'));
++IndentLevel;
Stack.Push( EJson::Object );
PreviousTokenWritten = EJsonToken::CurlyOpen;
}
template<typename IdentifierType>
void WriteObjectStart(IdentifierType&& Identifier)
{
check( Stack.Top() == EJson::Object );
WriteIdentifier(Forward<IdentifierType>(Identifier));
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
PrintPolicy::WriteChar(Stream, CharType('{'));
++IndentLevel;
Stack.Push( EJson::Object );
PreviousTokenWritten = EJsonToken::CurlyOpen;
}
void WriteObjectEnd()
{
check( Stack.Top() == EJson::Object );
PrintPolicy::WriteLineTerminator(Stream);
--IndentLevel;
PrintPolicy::WriteTabs(Stream, IndentLevel);
PrintPolicy::WriteChar(Stream, CharType('}'));
Stack.Pop();
PreviousTokenWritten = EJsonToken::CurlyClose;
}
void WriteArrayStart()
{
check(CanWriteValueWithoutIdentifier());
WriteCommaIfNeeded();
if ( PreviousTokenWritten != EJsonToken::None )
{
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
}
PrintPolicy::WriteChar(Stream, CharType('['));
++IndentLevel;
Stack.Push( EJson::Array );
PreviousTokenWritten = EJsonToken::SquareOpen;
}
template<typename IdentifierType>
void WriteArrayStart(IdentifierType&& Identifier)
{
check( Stack.Top() == EJson::Object );
WriteIdentifier(Forward<IdentifierType>(Identifier));
PrintPolicy::WriteSpace( Stream );
PrintPolicy::WriteChar(Stream, CharType('['));
++IndentLevel;
Stack.Push( EJson::Array );
PreviousTokenWritten = EJsonToken::SquareOpen;
}
void WriteArrayEnd()
{
check( Stack.Top() == EJson::Array );
--IndentLevel;
if ( PreviousTokenWritten == EJsonToken::SquareClose || PreviousTokenWritten == EJsonToken::CurlyClose || PreviousTokenWritten == EJsonToken::String )
{
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
}
else if ( PreviousTokenWritten != EJsonToken::SquareOpen )
{
PrintPolicy::WriteSpace( Stream );
}
PrintPolicy::WriteChar(Stream, CharType(']'));
Stack.Pop();
PreviousTokenWritten = EJsonToken::SquareClose;
}
template <class FValue>
void WriteValue(FValue Value)
{
check(CanWriteValueWithoutIdentifier());
WriteCommaIfNeeded();
if (PreviousTokenWritten == EJsonToken::SquareOpen || EJsonToken_IsShortValue(PreviousTokenWritten))
{
PrintPolicy::WriteSpace( Stream );
}
else
{
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
}
PreviousTokenWritten = WriteValueOnly( Value );
}
void WriteValue(FStringView Value)
{
check(CanWriteValueWithoutIdentifier());
WriteCommaIfNeeded();
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
PreviousTokenWritten = WriteValueOnly(Value);
}
void WriteValue(const FString& Value)
{
check(CanWriteValueWithoutIdentifier());
WriteCommaIfNeeded();
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
PreviousTokenWritten = WriteValueOnly(Value);
}
template<class FValue, typename IdentifierType>
void WriteValue(IdentifierType&& Identifier, FValue Value)
{
check( Stack.Top() == EJson::Object );
WriteIdentifier(Forward<IdentifierType>(Identifier));
PrintPolicy::WriteSpace(Stream);
PreviousTokenWritten = WriteValueOnly(MoveTemp(Value));
}
template<class ElementType, typename IdentifierType>
void WriteValue(IdentifierType&& Identifier, const TArray<ElementType>& Array)
{
WriteArrayStart(Forward<IdentifierType>(Identifier));
for (int Idx = 0; Idx < Array.Num(); Idx++)
{
WriteValue(Array[Idx]);
}
WriteArrayEnd();
}
template<typename IdentifierType, typename MapIdentifierType, class MapElementType>
void WriteValue(IdentifierType&& Identifier, const TMap<MapIdentifierType, MapElementType>& Map)
{
WriteObjectStart(Forward<IdentifierType>(Identifier));
for (const TPair<MapIdentifierType, MapElementType>& Element : Map)
{
WriteValue(Element.Key, Element.Value);
}
WriteObjectEnd();
}
template<typename MapIdentifierType, class MapElementType>
void WriteValue(const TMap<MapIdentifierType, MapElementType>& Map)
{
WriteObjectStart();
for (const TPair<MapIdentifierType, MapElementType>& Element : Map)
{
WriteValue(Element.Key, Element.Value);
}
WriteObjectEnd();
}
void WriteValue(FStringView Identifier, const TCHAR* Value)
{
WriteValue(Identifier, FStringView(Value));
}
// WARNING: THIS IS DANGEROUS. Use this only if you know for a fact that the Value is valid JSON!
// Use this to insert the results of a different JSON Writer in.
void WriteRawJSONValue( FStringView Identifier, FStringView Value )
{
WriteRawJSONValueImpl(Identifier, Value);
}
// WARNING: THIS IS DANGEROUS. Use this only if you know for a fact that the Value is valid JSON!
// Use this to insert the results of a different JSON Writer in.
void WriteRawJSONValue( FUtf8StringView Identifier, FUtf8StringView Value )
{
WriteRawJSONValueImpl(Identifier, Value);
}
template<typename IdentifierType>
void WriteNull(IdentifierType&& Identifier)
{
WriteValue(Forward<IdentifierType>(Identifier), nullptr);
}
void WriteValue(const TCHAR* Value)
{
WriteValue(FStringView(Value));
}
// WARNING: THIS IS DANGEROUS. Use this only if you know for a fact that the Value is valid JSON!
// Use this to insert the results of a different JSON Writer in.
void WriteRawJSONValue(FStringView Value)
{
WriteRawJSONValueImpl(Value);
}
// WARNING: THIS IS DANGEROUS. Use this only if you know for a fact that the Value is valid JSON!
// Use this to insert the results of a different JSON Writer in.
void WriteRawJSONValue(FUtf8StringView Value)
{
WriteRawJSONValueImpl(Value);
}
void WriteNull()
{
WriteValue(nullptr);
}
virtual bool Close()
{
return ( PreviousTokenWritten == EJsonToken::None ||
PreviousTokenWritten == EJsonToken::CurlyClose ||
PreviousTokenWritten == EJsonToken::SquareClose )
&& Stack.Num() == 0;
}
/**
* WriteValue("Foo", Bar) should be equivalent to WriteIdentifierPrefix("Foo"), WriteValue(Bar)
*/
template<typename IdentifierType>
void WriteIdentifierPrefix(IdentifierType&& Identifier)
{
check(Stack.Top() == EJson::Object);
WriteIdentifier(Forward<IdentifierType>(Identifier));
PrintPolicy::WriteSpace(Stream);
PreviousTokenWritten = EJsonToken::Identifier;
}
protected:
/**
* Creates and initializes a new instance.
*
* @param InStream An archive containing the input.
* @param InitialIndentLevel The initial indentation level.
*/
TJsonWriter( FArchive* const InStream, int32 InitialIndentLevel )
: Stream( InStream )
, Stack()
, PreviousTokenWritten(EJsonToken::None)
, IndentLevel(InitialIndentLevel)
{ }
protected:
FORCEINLINE bool CanWriteValueWithoutIdentifier() const
{
return Stack.Num() <= 0 || Stack.Top() == EJson::Array || PreviousTokenWritten == EJsonToken::Identifier;
}
FORCEINLINE bool CanWriteObjectWithoutIdentifier() const
{
return Stack.Num() <= 0 || Stack.Top() == EJson::Array || PreviousTokenWritten == EJsonToken::Identifier || PreviousTokenWritten == EJsonToken::Colon;
}
FORCEINLINE void WriteCommaIfNeeded()
{
if ( PreviousTokenWritten != EJsonToken::CurlyOpen && PreviousTokenWritten != EJsonToken::SquareOpen && PreviousTokenWritten != EJsonToken::Identifier && PreviousTokenWritten != EJsonToken::None)
{
PrintPolicy::WriteChar(Stream, CharType(','));
}
}
template <typename InCharType>
void WriteIdentifier(const InCharType* Identifier)
{
WriteCommaIfNeeded();
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
WriteStringValue(TStringView<InCharType>(Identifier));
PrintPolicy::WriteChar(Stream, CharType(':'));
}
template <typename InCharType>
void WriteIdentifier(TStringView<InCharType> Identifier)
{
WriteCommaIfNeeded();
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
WriteStringValue(Identifier);
PrintPolicy::WriteChar(Stream, CharType(':'));
}
void WriteIdentifier(const FText& Identifier)
{
WriteIdentifier(Identifier.ToString()); // Does not copy
}
FORCEINLINE void WriteIdentifier(const FString& Identifier)
{
WriteCommaIfNeeded();
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
WriteStringValue(FStringView(Identifier));
PrintPolicy::WriteChar(Stream, CharType(':'));
}
FORCEINLINE EJsonToken WriteValueOnly(bool Value)
{
PrintPolicy::WriteString(Stream, Value ? TEXTVIEW("true") : TEXTVIEW("false"));
return Value ? EJsonToken::True : EJsonToken::False;
}
FORCEINLINE EJsonToken WriteValueOnly(float Value)
{
PrintPolicy::WriteFloat(Stream, Value);
return EJsonToken::Number;
}
FORCEINLINE EJsonToken WriteValueOnly(double Value)
{
// Specify 17 significant digits, the most that can ever be useful from a double
// In particular, this ensures large integers are written correctly
PrintPolicy::WriteDouble(Stream, Value);
return EJsonToken::Number;
}
FORCEINLINE EJsonToken WriteValueOnly(int32 Value)
{
return WriteValueOnly((int64)Value);
}
FORCEINLINE EJsonToken WriteValueOnly(int64 Value)
{
PrintPolicy::WriteString(Stream, WriteToString<32>(Value));
return EJsonToken::Number;
}
FORCEINLINE EJsonToken WriteValueOnly(uint32 Value)
{
return WriteValueOnly((uint64)Value);
}
FORCEINLINE EJsonToken WriteValueOnly(uint64 Value)
{
PrintPolicy::WriteString(Stream, WriteToString<32>(Value));
return EJsonToken::Number;
}
FORCEINLINE EJsonToken WriteValueOnly(TYPE_OF_NULLPTR)
{
PrintPolicy::WriteString(Stream, TEXTVIEW("null"));
return EJsonToken::Null;
}
FORCEINLINE EJsonToken WriteValueOnly(const TCHAR* Value)
{
WriteStringValue(FStringView(Value));
return EJsonToken::String;
}
FORCEINLINE EJsonToken WriteValueOnly(FStringView Value)
{
WriteStringValue(Value);
return EJsonToken::String;
}
FORCEINLINE EJsonToken WriteValueOnly(FUtf8StringView Value)
{
WriteStringValue(Value);
return EJsonToken::String;
}
virtual void WriteStringValue(FAnsiStringView String)
{
PrintPolicy::WriteChar(Stream, CharType('"'));
WriteEscapedString(String);
PrintPolicy::WriteChar(Stream, CharType('"'));
}
virtual void WriteStringValue(FStringView String)
{
PrintPolicy::WriteChar(Stream, CharType('"'));
WriteEscapedString(String);
PrintPolicy::WriteChar(Stream, CharType('"'));
}
virtual void WriteStringValue(const FString& String)
{
TJsonWriter::WriteStringValue(FStringView(String));
}
virtual void WriteStringValue(FUtf8StringView String)
{
PrintPolicy::WriteChar(Stream, CharType('"'));
WriteEscapedString(String);
PrintPolicy::WriteChar(Stream, CharType('"'));
}
virtual void WriteStringValue(const FUtf8String& String)
{
TJsonWriter::WriteStringValue(FUtf8StringView(String));
}
// WARNING: THIS IS DANGEROUS. Use this only if you know for a fact that the Value is valid JSON!
// Use this to insert the results of a different JSON Writer in.
template <typename InCharType>
void WriteRawJSONValueImpl(FStringView Identifier, TStringView<InCharType> Value)
{
check(Stack.Top() == EJson::Object);
WriteIdentifier(Identifier);
PrintPolicy::WriteSpace(Stream);
PrintPolicy::WriteString(Stream, Value);
PreviousTokenWritten = EJsonToken::String;
}
template <typename InCharType>
void WriteRawJSONValueImpl(TStringView<InCharType> Value)
{
check(CanWriteValueWithoutIdentifier());
WriteCommaIfNeeded();
if (PreviousTokenWritten != EJsonToken::True && PreviousTokenWritten != EJsonToken::False && PreviousTokenWritten != EJsonToken::SquareOpen)
{
PrintPolicy::WriteLineTerminator(Stream);
PrintPolicy::WriteTabs(Stream, IndentLevel);
}
else
{
PrintPolicy::WriteSpace(Stream);
}
PrintPolicy::WriteString(Stream, Value);
PreviousTokenWritten = EJsonToken::String;
}
template<typename InCharType>
void WriteEscapedString(TStringView<InCharType> InView)
{
auto NeedsEscaping = [](InCharType Char) -> bool
{
switch (Char)
{
case CHARTEXT(InCharType, '\\'): return true;
case CHARTEXT(InCharType, '\n'): return true;
case CHARTEXT(InCharType, '\t'): return true;
case CHARTEXT(InCharType, '\b'): return true;
case CHARTEXT(InCharType, '\f'): return true;
case CHARTEXT(InCharType, '\r'): return true;
case CHARTEXT(InCharType, '\"'): return true;
default:
// Must escape control characters or non representable characters
if (HasDestinationJsonStringCharRepresentation<CharType>(Char))
{
return false;
}
else
{
return true;
}
}
};
// Write successive runs of unescaped and escaped characters until the view is exhausted
while (!InView.IsEmpty())
{
// In case we are handed a very large string, avoid checking all of it at once without writing anything
constexpr int32 LongestRun = 2048;
int32 EndIndex = 0;
for (; EndIndex < InView.Len() && EndIndex < LongestRun; ++EndIndex)
{
if (NeedsEscaping(InView[EndIndex]))
{
break;
}
}
if (TStringView<InCharType> Blittable = InView.Left(EndIndex); !Blittable.IsEmpty())
{
PrintPolicy::WriteString(Stream, Blittable);
}
InView.RightChopInline(EndIndex);
for (EndIndex = 0; EndIndex < InView.Len(); ++EndIndex)
{
InCharType Char = InView[EndIndex];
switch (Char)
{
case CHARTEXT(InCharType, '\\'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\\\")); continue;
case CHARTEXT(InCharType, '\n'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\n")); continue;
case CHARTEXT(InCharType, '\t'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\t")); continue;
case CHARTEXT(InCharType, '\b'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\b")); continue;
case CHARTEXT(InCharType, '\f'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\f")); continue;
case CHARTEXT(InCharType, '\r'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\r")); continue;
case CHARTEXT(InCharType, '\"'): PrintPolicy::WriteString(Stream, TEXTVIEW("\\\"")); continue;
default: break;
}
// Must escape control characters or non representable characters
if (HasDestinationJsonStringCharRepresentation<CharType>(Char))
{
break;
}
else
{
TAnsiStringBuilder<8> Builder;
Builder.Appendf("\\u%04x", Char);
PrintPolicy::WriteString(Stream, Builder.ToView());
}
}
InView.RightChopInline(EndIndex);
}
}
FArchive* const Stream;
TArray<EJson> Stack;
EJsonToken PreviousTokenWritten;
int32 IndentLevel;
};
template <class PrintPolicy = TPrettyJsonPrintPolicy<TCHAR>>
class TJsonStringWriter
: public TJsonWriter<typename PrintPolicy::CharType, PrintPolicy>
{
public:
using CharType = typename PrintPolicy::CharType;
using StringType = TString<CharType>;
static TSharedRef<TJsonStringWriter> Create(StringType* const InStream, int32 InitialIndent = 0 )
{
return MakeShareable(new TJsonStringWriter(InStream, InitialIndent));
}
public:
virtual ~TJsonStringWriter()
{
check(this->Stream->Close());
delete this->Stream;
}
virtual bool Close() override
{
OutString->Reset(Bytes.Num()/sizeof(CharType));
for (int32 i = 0; i < Bytes.Num(); i+=sizeof(CharType))
{
CharType* Char = static_cast<CharType*>(static_cast<void*>(&Bytes[i]));
*OutString += *Char;
}
return TJsonWriter<CharType, PrintPolicy>::Close();
}
protected:
TJsonStringWriter(StringType* const InOutString, int32 InitialIndent )
: TJsonWriter<CharType, PrintPolicy>(new FMemoryWriter(Bytes), InitialIndent)
, Bytes()
, OutString(InOutString)
{ }
private:
TArray<uint8> Bytes;
StringType* OutString;
};
template <class CharType = TCHAR, class PrintPolicy = TPrettyJsonPrintPolicy<CharType>>
class TJsonWriterFactory
{
public:
using StringType = TString<CharType>;
static TSharedRef<TJsonWriter<CharType, PrintPolicy>> Create(FArchive* const Stream, int32 InitialIndent = 0)
{
return TJsonWriter< CharType, PrintPolicy >::Create(Stream, InitialIndent);
}
static TSharedRef<TJsonWriter<CharType, PrintPolicy>> Create(StringType* const Stream, int32 InitialIndent = 0)
{
return StaticCastSharedRef<TJsonWriter<CharType, PrintPolicy>>(TJsonStringWriter<PrintPolicy>::Create(Stream, InitialIndent));
}
};