// Json serialization engine. Json is used to store emulator settings, as well as for the JDI system (Json Debug Interface). // All errors during deserialization are thrown by exceptions. // During serializing, in theory, there shouldn't be any mistakes (if we are not enemies to ourselves and have not changed, for example, the Value type to some non-standard one). #pragma once class Json { // Foolproof static const int MaxDepth = 255; static const int MaxStringSize = 0x1000; static const int MaxElements = 255; // Deserialize only public: class Value; private: // Recursive destruction of value and all its descendants void DestroyValue(Value* value); static wchar_t* CloneStr(const wchar_t* str); static wchar_t* CloneAnsiStr(const char* str); #pragma region "Serialization Related" // Serialize context struct SerializeContext { uint8_t** ptr; size_t* actualSize; size_t maxSize; }; // Base procedure for generating the text. static void EmitChar(SerializeContext* ctx, uint8_t val, bool sizeOnly); static void EmitText(SerializeContext* ctx, const char* text, bool sizeOnly); // Simple wchar_t to UTF-8 Converter + Escaping static void EmitCodePoint(SerializeContext* ctx, int cp, bool sizeOnly); static void EmitWcharString(SerializeContext* ctx, wchar_t* str, bool sizeOnly); // Indentation static void Indent(SerializeContext* ctx, int depth, bool sizeOnly); #pragma endregion "Serialization Related" #pragma region "De-Serialization Related" // Deserialize struct DeserializeContext { uint8_t* ptr; size_t offset; size_t maxSize; }; static bool IsWhiteSpace(uint8_t value); static bool IsControl(uint8_t value); // Json Token enum class TokenType : char { Unknown = '?', EndOfStream = 'E', ObjectStart = '{', ObjectEnd = '}', ArrayStart = '[', ArrayEnd = ']', Colon = ':', Comma = ',', Int = 'I', Float = 'F', True = 't', False = 'f', Null = 'N', String = 'S', }; class Token { public: TokenType type = TokenType::Unknown; union { uint64_t AsInt; float AsFloat; bool AsBool; wchar_t* AsString; } value; Token() { value.AsString = nullptr; } }; static bool GetLiteral(DeserializeContext* ctx, Token& token); static int FetchCodepoint(DeserializeContext* ctx); static bool GetString(DeserializeContext* ctx, Token& token); static bool IsAllowed(uint8_t val, char* allowed); static bool GetFloat(DeserializeContext* ctx, Token& token); static bool GetInt(DeserializeContext* ctx, Token& token); static void GetToken(Token& token, DeserializeContext* ctx); #pragma endregion "De-Serialization Related" public: // Json Value enum class ValueType : char { Unknown = '?', Object = 'O', Array = 'A', Int = 'I', Float = 'F', String = 'S', Bool = 'B', Null = 'N', }; class Value { char* CloneName(const char* otherName); char* CloneWcharName(const wchar_t* otherName); void DeserializeObject(DeserializeContext* ctx); void DeserializeArray(DeserializeContext* ctx); public: Value* parent = nullptr; ValueType type = ValueType::Unknown; // Name of object members. // For simplicity, we do not support Non-Ansi Json value names (although full use of Utf-8 is allowed in the values) char* name = nullptr; union { float AsFloat; uint64_t AsInt; uint8_t AsUint8; uint16_t AsUint16; uint32_t AsUint32; wchar_t* AsString; bool AsBool; } value; std::list children; Value() { // Only the String value type requires the release of the stored value. value.AsString = nullptr; } Value(Value* _parent) { // Only the String value type requires the release of the stored value. value.AsString = nullptr; this->parent = _parent; } ~Value() { while (!children.empty()) { Value *child = children.back(); children.pop_back(); delete child; } if (type == ValueType::String) { if (value.AsString) { delete[] value.AsString; value.AsString = nullptr; } } if (name != nullptr) { delete[] name; name = nullptr; } } void Serialize(SerializeContext* ctx, int depth, bool sizeOnly); void Deserialize(DeserializeContext* ctx, wchar_t* keyName); // Dynamic modification Value* AddInt(const char* keyName, int _value); Value* AddUInt16(const char* keyName, uint16_t _value); Value* AddUInt32(const char* keyName, uint32_t _value); Value* AddUInt64(const char* keyName, uint64_t _value); Value* AddFloat(const char* keyName, float _value); Value* AddNull(const char* keyName); Value* AddBool(const char* keyName, bool _value); Value* AddString(const char* keyName, const wchar_t* str); Value* AddAnsiString(const char* keyName, const char* str); Value* ReplaceString(const wchar_t* str); Value* AddObject(const char* keyName); Value* AddArray(const char* keyName); Value* AddValue(const char* keyName, Value* value); Value* Add(Value* _parent, Value* other); Value* Replace(Value* _parent, Value* other); // Access Value* ByName(const char* byName); Value* ByType(const ValueType byType); void SetName(const char* newName) { if (name != nullptr) { delete[] name; } name = CloneName(newName); } }; // Deserialized root Value root; // Api ~Json() { DestroyValue(&root); } // The process of serialization is that we recursively walk through all the values, passing in each "running" context // which has the pointer to the generated text and the current text size. void Serialize(void* text, size_t maxTextSize, size_t& actualTextSize); void GetSerializedTextSize(void* text, size_t maxTextSize, size_t& actualTextSize); void Deserialize(void* text, size_t textSize); // Clone void Clone(Json* other); // Merge void Merge(Json* other); };