// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Dom/JsonValue.h" #include "Dom/JsonObject.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonTypes.h" #include "Serialization/JsonWriter.h" class Error; class FJsonSerializer { public: enum class EFlags { None = 0, StoreNumbersAsStrings = 1, }; template static bool Deserialize(const TSharedRef>& Reader, TArray>& OutArray, EFlags InOptions = EFlags::None) { return Deserialize(*Reader, OutArray, InOptions); } template static bool Deserialize(TJsonReader& Reader, TArray>& OutArray, EFlags InOptions = EFlags::None) { StackState State; if (!Deserialize(Reader, /*OUT*/State, InOptions)) { return false; } // Empty array is ok. if (State.Type != EJson::Array) { return false; } OutArray = State.Array; return true; } template static bool Deserialize(const TSharedRef>& Reader, TSharedPtr& OutObject, EFlags InOptions = EFlags::None) { return Deserialize(*Reader, OutObject, InOptions); } template static bool Deserialize(TJsonReader& Reader, TSharedPtr& OutObject, EFlags InOptions = EFlags::None) { StackState State; if (!Deserialize(Reader, /*OUT*/State, InOptions)) { return false; } if (!State.Object.IsValid()) { return false; } OutObject = State.Object; return true; } template static bool Deserialize(const TSharedRef>& Reader, TSharedPtr& OutValue, EFlags InOptions = EFlags::None) { return Deserialize(*Reader, OutValue, InOptions); } template static bool Deserialize(TJsonReader& Reader, TSharedPtr& OutValue, EFlags InOptions = EFlags::None) { StackState State; if (!Deserialize(Reader, /*OUT*/State, InOptions)) { return false; } switch (State.Type) { case EJson::Object: if (!State.Object.IsValid()) { return false; } OutValue = MakeShared(State.Object); break; case EJson::Array: OutValue = MakeShared(State.Array); break; case EJson::None: case EJson::Null: case EJson::String: case EJson::Number: case EJson::Boolean: default: // FIXME: would be nice to handle non-composite root values but StackState Deserialize just drops them on the floor return false; } return true; } /** * Serialize the passed array of json values into the writer. * This will effectively serialize all of the values enclosed in [] square brackets. * Example: * - Writer state: [123 * Parameter: Array: ["foo", "bar", "", 456] * Serialization result: [123, ["foo", "bar", "", 456] * * @param Array The json array we are serializing * @param Writer The writer the array is written into. * @param bCloseWriter When set to true the Writer will be closed after the serialization. * @return Returns true if the serialization was successful, false otherwise. */ template static bool Serialize(const TArray>& Array, const TSharedRef>& Writer, bool bCloseWriter = true) { return Serialize(Array, *Writer, bCloseWriter); } /** * Serialize the passed array of json values into the writer. * This will effectively serialize all of the values enclosed in [] square brackets. * Example: * - Writer state: [123 * Parameter: Array: ["foo", "bar", "", 456] * Serialization result: [123, ["foo", "bar", "", 456] * * @param Array The json array we are serializing * @param Writer The writer the array is written into. * @param bCloseWriter When set to true the Writer will be closed after the serialization. * @return Returns true if the serialization was successful, false otherwise. */ template static bool Serialize(const TArray>& Array, TJsonWriter& Writer, bool bCloseWriter = true ) { const TSharedRef StartingElement = MakeShared(Array); return FJsonSerializer::Serialize(StartingElement, Writer, bCloseWriter); } /** * Serialize the passed Json object into the writer. * This will effectively serialize all of the identifier:value pairs of the object enclosed in {} curly brackets. * Example: * - Writer state: [123 * Parameter: Object: {"foo": "bar", "baz": "", "": 456} * Serialization result: [123, {"foo": "bar", "baz": "", "": 456} * * @param Object The json object we are serializing * @param Writer The writer the object is written into. * @param bCloseWriter When set to true the Writer will be closed after the serialization. * @return Returns true if the serialization was successful, false otherwise. */ template static bool Serialize(const TSharedRef& Object, const TSharedRef>& Writer, bool bCloseWriter = true ) { return Serialize(Object, *Writer, bCloseWriter); } /** * Serialize the passed Json object into the writer. * This will effectively serialize all of the identifier:value pairs of the object enclosed in {} brackets. * Example: * - Writer state: [123 * Parameter: Object: {"foo": "bar", "baz": "", "": 456} * Serialization result: [123, {"foo": "bar", "baz": "", "": 456} * * @param Object The json object we are serializing * @param Writer The writer the object is written into. * @param bCloseWriter When set to true the Writer will be closed after the serialization. * @return Returns true if the serialization was successful, false otherwise. */ template static bool Serialize(const TSharedRef& Object, TJsonWriter& Writer, bool bCloseWriter = true) { const TSharedRef StartingElement = MakeShared(Object); return FJsonSerializer::Serialize(StartingElement, Writer, bCloseWriter); } /** * Serialize the passed Json value and identifier into the writer. * Empty string identifiers will be ignored when the writer is not writing inside of a json object and only the value will be serialized. * If the writer is in a state where it's currently writing inside of a json object, then the identifier will always be serialized. * Examples: * - Writer state: { "foo": "bar" * Parameters: Identifier: "" * Value: "baz" * Serialization result: { "foo": "bar", "": "baz" //empty identifier is serialized as a valid key for the key:value pair "":"baz" * * - Writer state: { "foo": ["bar" * Parameters: Identifier: "" * Value: "baz" * Serialization result: { foo: ["bar", "baz" //empty identifier is ignored since we are writing into an array and not an object. * * @param Value The json value we are serializing * @param Identifier The identifier of the value, empty identifiers are ignored outside of json objects. * @param Writer The writer the value and identifier are written into. * @param bCloseWriter When set to true the Writer will be closed after the serialization. * @return Returns true if the serialization was successful, false otherwise. */ template static bool Serialize(const TSharedPtr& Value, const FString& Identifier, const TSharedRef>& Writer, bool bCloseWriter = true) { return Serialize(Value, Identifier, *Writer, bCloseWriter); } /** * Serialize the passed Json value and identifier into the writer. * Empty string identifiers will be ignored when the writer is not writing inside of a json object and only the value will be serialized. * If the writer is in a state where it's currently writing inside of a json object, then the identifier will always be serialized. * Examples: * - Writer state: { "foo": "bar" * Parameters: Identifier: "" * Value: "baz" * Serialization result: { "foo": "bar", "": "baz" //empty identifier is serialized as a valid key for the key:value pair "":"baz" * * - Writer state: { "foo": ["bar" * Parameters: Identifier: "" * Value: "baz" * Serialization result: { foo: ["bar", "baz" //empty identifier is ignored since we are writing into an array and not an object. * * @param Value The json value we are serializing * @param Identifier The identifier of the value, empty identifiers are ignored outside of json objects. * @param Writer The writer the value and identifier are written into. * @param bCloseWriter When set to true the Writer will be closed after the serialization. * @return Returns true if the serialization was successful, false otherwise. */ template static bool Serialize(const TSharedPtr& Value, const FString& Identifier, TJsonWriter& Writer, bool bCloseWriter = true) { const TSharedRef StartingElement = MakeShared(Identifier, Value); return FJsonSerializer::Serialize(StartingElement, Writer, bCloseWriter); } private: struct StackState { EJson Type; FString Identifier; TArray> Array; TSharedPtr Object; }; struct FElement { FElement( const TSharedPtr& InValue ) : Identifier() , Value(InValue) { } FElement( const TSharedRef& Object ) : Identifier() , Value(MakeShared(Object)) { } FElement( const TArray>& Array ) : Identifier() , Value(MakeShared(Array)) { } FElement( const FString& InIdentifier, const TSharedPtr< FJsonValue >& InValue ) : Identifier( InIdentifier ) , Value( InValue ) , bIsKeyValuePair( true ) { } FString Identifier; TSharedPtr< FJsonValue > Value; bool bHasBeenProcessed = false; bool bIsKeyValuePair = false; }; private: template static bool Deserialize(TJsonReader& Reader, StackState& OutStackState, EFlags InOptions) { TArray> ScopeStack; TSharedPtr CurrentState; TSharedPtr NewValue; EJsonNotation Notation; while (Reader.ReadNext(Notation)) { FString Identifier = Reader.GetIdentifier(); NewValue.Reset(); switch( Notation ) { case EJsonNotation::ObjectStart: { if (CurrentState.IsValid()) { ScopeStack.Push(CurrentState.ToSharedRef()); } CurrentState = MakeShared(); CurrentState->Type = EJson::Object; CurrentState->Identifier = Identifier; CurrentState->Object = MakeShared(); } break; case EJsonNotation::ObjectEnd: { if (ScopeStack.Num() > 0) { Identifier = CurrentState->Identifier; NewValue = MakeShared(CurrentState->Object); CurrentState = ScopeStack.Pop(); } } break; case EJsonNotation::ArrayStart: { if (CurrentState.IsValid()) { ScopeStack.Push(CurrentState.ToSharedRef()); } CurrentState = MakeShared(); CurrentState->Type = EJson::Array; CurrentState->Identifier = Identifier; } break; case EJsonNotation::ArrayEnd: { if (ScopeStack.Num() > 0) { Identifier = CurrentState->Identifier; NewValue = MakeShared(CurrentState->Array); CurrentState = ScopeStack.Pop(); } } break; case EJsonNotation::Boolean: NewValue = MakeShared(Reader.GetValueAsBoolean()); break; case EJsonNotation::String: { using StoredCharType = typename TJsonReader::StoredCharType; NewValue = MakeShared>(Reader.StealInternalValueAsString()); } break; case EJsonNotation::Number: if (EnumHasAnyFlags(InOptions, EFlags::StoreNumbersAsStrings)) { using StoredCharType = typename TJsonReader::StoredCharType; NewValue = MakeShared>(Reader.GetValueAsNumberString()); } else { NewValue = MakeShared(Reader.GetValueAsNumber()); } break; case EJsonNotation::Null: NewValue = MakeShared(); break; case EJsonNotation::Error: return false; break; } if (NewValue.IsValid() && CurrentState.IsValid()) { if (CurrentState->Type == EJson::Object) { CurrentState->Object->SetField(Identifier, NewValue); } else { CurrentState->Array.Add(NewValue); } } } if (!CurrentState.IsValid() || !Reader.GetErrorMessage().IsEmpty()) { return false; } OutStackState = *CurrentState.Get(); return true; } template static bool Serialize(const TSharedRef& StartingElement, TJsonWriter& Writer, bool bCloseWriter) { TArray> ElementStack; ElementStack.Push(StartingElement); while (ElementStack.Num() > 0) { TSharedRef Element = ElementStack.Pop(); // Empty keys are valid identifiers only when writing inside an object. const bool bWriteValueOnly = !Element->bIsKeyValuePair || (Element->Identifier.IsEmpty() && Writer.GetCurrentElementType() != EJson::Object); check(Element->Value->Type != EJson::None); switch (Element->Value->Type) { case EJson::Number: { if (bWriteValueOnly) { if (Element->Value->PreferStringRepresentation()) { Writer.WriteRawJSONValue(Element->Value->AsString()); } else { Writer.WriteValue(Element->Value->AsNumber()); } } else { if (Element->Value->PreferStringRepresentation()) { Writer.WriteRawJSONValue(Element->Identifier, Element->Value->AsString()); } else { Writer.WriteValue(Element->Identifier, Element->Value->AsNumber()); } } } break; case EJson::Boolean: { if (bWriteValueOnly) { Writer.WriteValue(Element->Value->AsBool()); } else { Writer.WriteValue(Element->Identifier, Element->Value->AsBool()); } } break; case EJson::String: { if (bWriteValueOnly) { Writer.WriteValue(Element->Value->AsString()); } else { Writer.WriteValue(Element->Identifier, Element->Value->AsString()); } } break; case EJson::Null: { if (bWriteValueOnly) { Writer.WriteNull(); } else { Writer.WriteNull(Element->Identifier); } } break; case EJson::Array: { if (Element->bHasBeenProcessed) { Writer.WriteArrayEnd(); } else { Element->bHasBeenProcessed = true; ElementStack.Push(Element); if (bWriteValueOnly) { Writer.WriteArrayStart(); } else { Writer.WriteArrayStart(Element->Identifier); } TArray> Values = Element->Value->AsArray(); for (int Index = Values.Num() - 1; Index >= 0; --Index) { ElementStack.Push(MakeShared(Values[Index])); } } } break; case EJson::Object: { if (Element->bHasBeenProcessed) { Writer.WriteObjectEnd(); } else { Element->bHasBeenProcessed = true; ElementStack.Push(Element); if (bWriteValueOnly) { Writer.WriteObjectStart(); } else { Writer.WriteObjectStart(Element->Identifier); } TArray Keys; TArray> Values; TSharedPtr ElementObject = Element->Value->AsObject(); ElementObject->Values.GenerateKeyArray(Keys); ElementObject->Values.GenerateValueArray(Values); check(Keys.Num() == Values.Num()); for (int Index = Values.Num() - 1; Index >= 0; --Index) { ElementStack.Push(MakeShared(Keys[Index], Values[Index])); } } } break; case EJson::None: default: UE_LOG(LogJson, Fatal,TEXT("Could not print Json Value, unrecognized type.")); } } if (bCloseWriter) { return Writer.Close(); } else { return true; } } };