mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-26 16:24:35 +01:00
Fix #9088: refactor JSON code to use a more modern C++ library
- Removed dependency on jansson - Implemented nlohmann JSON for Modern C++ - Moved generic GetString, etc., helper functions out of ObjectJsonHelpers.hpp to Json.hpp - Added GetEnum helper function - Added AsObject and AsArray helper functions - Removed GetStringArray, etc., helper functions as they're not needed with the better language features - Added second GetFlags definition that allows specifying inverted flags - this is a bit cleaner than previous hacks - Comments!
This commit is contained in:
@@ -15,9 +15,8 @@
|
||||
|
||||
namespace Json
|
||||
{
|
||||
json_t* ReadFromFile(const utf8* path, size_t maxSize)
|
||||
json_t ReadFromFile(const utf8* path, size_t maxSize)
|
||||
{
|
||||
json_t* json = nullptr;
|
||||
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN);
|
||||
|
||||
size_t fileLength = static_cast<size_t>(fs.GetLength());
|
||||
@@ -26,42 +25,101 @@ namespace Json
|
||||
throw IOException("Json file too large.");
|
||||
}
|
||||
|
||||
utf8* fileData = Memory::Allocate<utf8>(fileLength + 1);
|
||||
fs.Read(fileData, fileLength);
|
||||
fileData[fileLength] = '\0';
|
||||
auto fileData = std::string(static_cast<size_t>(fileLength) + 1, '\0');
|
||||
fs.Read(static_cast<void*>(fileData.data()), fileLength);
|
||||
|
||||
json_error_t jsonLoadError;
|
||||
json = json_loads(fileData, 0, &jsonLoadError);
|
||||
Memory::Free(fileData);
|
||||
json_t json;
|
||||
|
||||
if (json == nullptr)
|
||||
try
|
||||
{
|
||||
throw JsonException(&jsonLoadError);
|
||||
json = json_t::parse(fileData, nullptr, true, true);
|
||||
}
|
||||
catch (const json_t::exception& e)
|
||||
{
|
||||
throw JsonException(String::Format("Unable to parse JSON file (%s)\n\t%s", path, e.what()));
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void WriteToFile(const utf8* path, const json_t* json, size_t flags)
|
||||
void WriteToFile(const utf8* path, const json_t& jsonData, int indentSize)
|
||||
{
|
||||
// Serialise JSON
|
||||
const char* jsonOutput = json_dumps(json, flags);
|
||||
std::string jsonOutput = jsonData.dump(indentSize);
|
||||
|
||||
// Write to file
|
||||
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE);
|
||||
size_t jsonOutputSize = String::SizeOf(jsonOutput);
|
||||
fs.Write(jsonOutput, jsonOutputSize);
|
||||
fs.Write(jsonOutput.data(), jsonOutput.size());
|
||||
}
|
||||
|
||||
json_t* FromString(std::string_view raw)
|
||||
json_t FromString(std::string_view raw)
|
||||
{
|
||||
json_t* root;
|
||||
json_error_t error;
|
||||
root = json_loadb(raw.data(), raw.size(), 0, &error);
|
||||
if (root == nullptr)
|
||||
json_t json;
|
||||
|
||||
try
|
||||
{
|
||||
throw JsonException(&error);
|
||||
json = json_t::parse(raw, nullptr, true, true);
|
||||
}
|
||||
return root;
|
||||
catch (const json_t::exception& e)
|
||||
{
|
||||
log_error("Unable to parse JSON string (%s)\n\t%s", raw, e.what());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
json_t FromVector(const std::vector<uint8_t>& vec)
|
||||
{
|
||||
json_t json;
|
||||
|
||||
try
|
||||
{
|
||||
json = json_t::parse(vec.begin(), vec.end());
|
||||
}
|
||||
catch (const json_t::exception& e)
|
||||
{
|
||||
log_error("Unable to parse JSON vector (%s)\n\t%s", vec.data(), e.what());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
std::string GetString(const json_t& jsonObj, const std::string& defaultValue)
|
||||
{
|
||||
return jsonObj.is_string() ? jsonObj.get<std::string>() : defaultValue;
|
||||
}
|
||||
|
||||
bool GetBoolean(const json_t& jsonObj, bool defaultValue)
|
||||
{
|
||||
return jsonObj.is_boolean() ? jsonObj.get<bool>() : defaultValue;
|
||||
}
|
||||
|
||||
json_t AsObject(const json_t& jsonObj)
|
||||
{
|
||||
return jsonObj.is_object() ? jsonObj : json_t::object();
|
||||
}
|
||||
|
||||
json_t AsArray(const json_t& jsonObj)
|
||||
{
|
||||
if (jsonObj.is_array())
|
||||
{
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
json_t retVal = json_t::array();
|
||||
|
||||
if (jsonObj.is_object())
|
||||
{
|
||||
for (const auto& jItem : jsonObj)
|
||||
{
|
||||
retVal.push_back(jItem);
|
||||
}
|
||||
}
|
||||
else if (!jsonObj.is_null())
|
||||
{
|
||||
retVal.push_back(jsonObj);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
} // namespace Json
|
||||
|
||||
@@ -11,36 +11,181 @@
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include <jansson.h>
|
||||
#include <stdexcept>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
using json_t = nlohmann::json;
|
||||
|
||||
namespace Json
|
||||
{
|
||||
// Don't try to load JSON files that exceed 64 MiB
|
||||
constexpr uint64_t MAX_JSON_SIZE = 64 * 1024 * 1024;
|
||||
|
||||
json_t* ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE);
|
||||
void WriteToFile(const utf8* path, const json_t* json, size_t flags = 0);
|
||||
/**
|
||||
* Read JSON file and parse contents
|
||||
* @param path Path to the source file
|
||||
* @param maxSize Max file size in bytes allowed
|
||||
* @return A JSON representation of the file
|
||||
* @note This function will throw an exception if the JSON file cannot be parsed
|
||||
*/
|
||||
json_t ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE);
|
||||
|
||||
json_t* FromString(std::string_view raw);
|
||||
/**
|
||||
* Read JSON file and parse the contents
|
||||
* @param path Path to the destination file
|
||||
* @param jsonData A JSON object
|
||||
* @param indentSize The number of spaces in an indent, or removes whitespace on -1
|
||||
*/
|
||||
void WriteToFile(const utf8* path, const json_t& jsonData, int indentSize = 4);
|
||||
|
||||
/**
|
||||
* Parse JSON from a string
|
||||
* @param raw JSON string
|
||||
* @return A JSON representation of the string
|
||||
* @note This function will throw an exception if the JSON string cannot be parsed
|
||||
*/
|
||||
json_t FromString(std::string_view raw);
|
||||
|
||||
/**
|
||||
* Parse JSON from a vector of characters
|
||||
* @param vec Vector of characters containing JSON
|
||||
* @return A JSON representation of the vector
|
||||
* @note This function will throw an exception if the JSON vector cannot be parsed
|
||||
*/
|
||||
json_t FromVector(const std::vector<uint8_t>& vec);
|
||||
|
||||
/**
|
||||
* Explicit type conversion between a JSON object and a compatible number value
|
||||
* @param T Destination numeric type
|
||||
* @param jsonObj JSON object holding the value
|
||||
* @param defaultValue Default value to return if the JSON object is not a number type
|
||||
* @return Copy of the JSON value converted to the given type
|
||||
*/
|
||||
template<typename T> T GetNumber(const json_t& jsonObj, T defaultValue = 0)
|
||||
{
|
||||
static_assert(std::is_arithmetic<T>::value, "GetNumber template parameter must be arithmetic");
|
||||
|
||||
return jsonObj.is_number() ? jsonObj.get<T>() : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit type conversion between a JSON object and a compatible enum value
|
||||
* @param T Destination enum
|
||||
* @param jsonObj JSON object holding the value
|
||||
* @param defaultValue Default value to return if the JSON object is not an enum type
|
||||
* @return Copy of the JSON value converted to the given enum type
|
||||
*/
|
||||
template<typename T> T GetEnum(const json_t& jsonObj, T defaultValue)
|
||||
{
|
||||
static_assert(std::is_enum<T>::value, "GetEnum template parameter must be an enum");
|
||||
|
||||
return jsonObj.is_number_integer() ? jsonObj.get<T>() : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit type conversion between a JSON object and an std::string
|
||||
* @param jsonObj JSON object holding the value
|
||||
* @param defaultValue Default value to return if the JSON object is not a string
|
||||
* @return Copy of the JSON value converted to std::string
|
||||
*/
|
||||
std::string GetString(const json_t& jsonObj, const std::string& defaultValue = std::string());
|
||||
|
||||
/**
|
||||
* Explicit type conversion between a JSON object and a boolean
|
||||
* @param jsonObj JSON object holding the value
|
||||
* @param defaultValue Default value to return if the JSON object is not a boolean
|
||||
* @return Copy of the JSON value converted to bool
|
||||
*/
|
||||
bool GetBoolean(const json_t& jsonObj, bool defaultValue = false);
|
||||
|
||||
/**
|
||||
* Ensures a given JSON object is an object type
|
||||
* @param jsonObj JSON object
|
||||
* @return The JSON object if it is an object type, or an empty object otherwise
|
||||
*/
|
||||
json_t AsObject(const json_t& jsonObj);
|
||||
|
||||
/**
|
||||
* Ensures a given JSON object is an array type
|
||||
* @param jsonObj JSON object
|
||||
* @return The JSON object if it is an array type, or an array containing the JSON object otherwise
|
||||
*/
|
||||
json_t AsArray(const json_t& jsonObj);
|
||||
|
||||
/**
|
||||
* Helper function to convert a json object and an initializer list to binary flags
|
||||
* @param T Type to return
|
||||
* @param jsonObj JSON object containing boolean values
|
||||
* @param list List of pairs of keys and bits to enable if that key in the object is true
|
||||
* @return Value with relevant bits flipped
|
||||
*/
|
||||
template<typename T> T GetFlags(const json_t& jsonObj, std::initializer_list<std::pair<std::string, T>> list)
|
||||
{
|
||||
static_assert(std::is_convertible<T, int>::value, "GetFlags template parameter must be integral or a weak enum");
|
||||
|
||||
T flags{};
|
||||
for (const auto& item : list)
|
||||
{
|
||||
if (jsonObj.contains(item.first) && Json::GetBoolean(jsonObj[item.first]))
|
||||
{
|
||||
flags = static_cast<T>(flags | item.second);
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the GetFlags function to allow for inverted values
|
||||
*/
|
||||
enum class FlagType : uint8_t
|
||||
{
|
||||
// Flag is turned on if the key is true
|
||||
Normal,
|
||||
// Flag is turned on if the key is false
|
||||
Inverted
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to convert a json object and an initializer list to binary flags
|
||||
* @param T Type to return
|
||||
* @param jsonObj JSON object containing boolean values
|
||||
* @param list List of tuples of keys, bits to change and flag type
|
||||
* @return Value with relevant bits flipped
|
||||
* @note FLAG_NORMAL behaves like the other GetFlags function, but FLAG_INVERTED will turn the flag on when false
|
||||
*/
|
||||
template<typename T> T GetFlags(const json_t& jsonObj, std::initializer_list<std::tuple<std::string, T, FlagType>> list)
|
||||
{
|
||||
static_assert(std::is_convertible<T, int>::value, "GetFlags template parameter must be integral or a weak enum");
|
||||
|
||||
T flags{};
|
||||
for (const auto& item : list)
|
||||
{
|
||||
if (std::get<2>(item) == FlagType::Normal)
|
||||
{
|
||||
if (jsonObj.contains(std::get<0>(item)) && Json::GetBoolean(jsonObj[std::get<0>(item)]))
|
||||
{
|
||||
flags = static_cast<T>(flags | std::get<1>(item));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the json flag doesn't exist, assume it's false
|
||||
if (!jsonObj.contains(std::get<0>(item)) || !Json::GetBoolean(jsonObj[std::get<0>(item)]))
|
||||
{
|
||||
flags = static_cast<T>(flags | std::get<1>(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
} // namespace Json
|
||||
|
||||
class JsonException final : public std::runtime_error
|
||||
{
|
||||
private:
|
||||
json_error_t _jsonError = {};
|
||||
|
||||
public:
|
||||
explicit JsonException(const std::string& message)
|
||||
: std::runtime_error(message)
|
||||
{
|
||||
}
|
||||
|
||||
explicit JsonException(const json_error_t* jsonError)
|
||||
: JsonException(std::string(jsonError->text))
|
||||
{
|
||||
_jsonError = *jsonError;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user