Don't use picojson (it has problems), use Lohmann's JSON for Modern C++.

This commit is contained in:
Alexander Shaduri
2018-01-13 20:09:04 +00:00
parent f87003b192
commit 59b3a8a8e4
11 changed files with 15008 additions and 1247 deletions
-7
View File
@@ -44,13 +44,6 @@ Port to std::filesystem (gcc 8; mingw not yet supported).
Use std::from_chars() in string_is_numeric_impl_classic_locale() (gcc 8)
rmn -> json
+Rename config to gsmartcontrol2
+Remove the old "config migration" code.
Add picojson license.
picojson doesn't have uint64_t, maybe use another library?
use ScopedLocale.
Check licenses - do we still use boost, bsd?
Update license_hz.txt
Relicense under GPL3 only.
@@ -368,6 +368,8 @@ namespace {
data_type def_value = rconfig::get_default_data<data_type>(path);
if (def_value != data) {
rconfig::set_data(path, data);
} else {
rconfig::unset_data(path);
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2017 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2 -2
View File
@@ -1,7 +1,7 @@
AM_CPPFLAGS = $(all_includes)
METASOURCES = AUTO
noinst_HEADERS = picojson.h
noinst_HEADERS = json.hpp
noinst_DATA = picojson_version.txt
noinst_DATA = LICENSE.MIT json_version.txt
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
JSON for Modern C++
version 3.0.1
https://github.com/nlohmann/json
File diff suppressed because it is too large Load Diff
@@ -1,2 +0,0 @@
https://github.com/kazuho/picojson
picojson git master 2018-01-03
+3
View File
@@ -28,6 +28,9 @@
#include "loadsave.h"
// TODO gtkmm4: Port this to Glib::SignalTimeout
namespace rconfig {
+95 -70
View File
@@ -19,38 +19,41 @@
#include "hz/debug.h"
#include "hz/string_algo.h"
#include "json/picojson.h"
#include "json/json.hpp"
namespace rconfig {
using json = nlohmann::json;
using namespace std::string_literals;
namespace impl {
inline std::unique_ptr<picojson::value> config_node; ///< Node for serializable branch
inline std::unique_ptr<picojson::value> default_node; ///< Node for default branch
inline std::unique_ptr<json> config_node; ///< Node for serializable branch
inline std::unique_ptr<json> default_node; ///< Node for default branch
template<typename T>
inline void set_node_data(picojson::value& root, const std::string& path, T&& value)
inline void set_node_data(json& root, const std::string& path, T&& value)
{
std::vector<std::string> components;
hz::string_split(path, '/', components, true);
picojson::value* curr = &root;
json* curr = &root;
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
const std::string& comp_name = components[comp_index];
// we can't have non-object values in the middle of a path
if (!curr->is<picojson::object>()) {
throw std::runtime_error(std::string("Cannot set node data \"" + path + "\", component \"" + comp_name + "\" is not an object."));
if (!curr->is_object()) {
throw std::runtime_error("Cannot set node data \""s + path + "\", component \"" + comp_name + "\" is not an object.");
}
auto& curr_obj = curr->get<picojson::object>();
if (auto iter = curr_obj.find(comp_name); iter != curr_obj.end()) { // path component exists
picojson::value& jval = iter->second;
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
json& jval = iter.value();
if (comp_index + 1 == components.size()) { // it's the "value" component
jval = picojson::value(std::forward<T>(value));
jval = json(std::forward<T>(value));
break;
}
// continue to the next component
@@ -58,10 +61,10 @@ namespace impl {
} else { // path component doesn't exist
if (comp_index + 1 == components.size()) { // it's the "value" component
curr->get<picojson::object>()[comp_name] = picojson::value(std::forward<T>(value));
(*curr)[comp_name] = json(std::forward<T>(value));
break;
}
curr = &(curr->get<picojson::object>()[comp_name] = picojson::value(picojson::object()));
curr = &((*curr)[comp_name] = json::object());
}
}
}
@@ -69,26 +72,22 @@ namespace impl {
template<typename T>
bool get_node_data(picojson::value& root, const std::string& path, T& value)
bool get_node_data(json& root, const std::string& path, T& value)
{
std::vector<std::string> components;
hz::string_split(path, '/', components, true);
picojson::value* curr = &root;
json* curr = &root;
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
const std::string& comp_name = components[comp_index];
if (!curr->is<picojson::object>()) { // we can't have non-object values in the middle of a path
throw std::runtime_error(std::string("Cannot get node data \"" + path + "\", component \"" + comp_name + "\" is not an object."));
if (!curr->is_object()) { // we can't have non-object values in the middle of a path
throw std::runtime_error("Cannot get node data \""s + path + "\", component \"" + comp_name + "\" is not an object.");
}
auto& curr_obj = curr->get<picojson::object>();
if (auto iter = curr_obj.find(comp_name); iter != curr_obj.end()) { // path component exists
picojson::value& jval = iter->second;
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
json& jval = iter.value();
if (comp_index + 1 == components.size()) { // it's the "value" component
if (!jval.is<T>()) {
throw std::runtime_error(std::string("Cannot get node data \"" + path + "\", type mismatch."));
}
value = jval.get<T>();
value = jval.get<T>(); // may throw json::type_error
return true;
}
// continue to the next component
@@ -102,6 +101,36 @@ namespace impl {
}
inline void unset_node_data(json& root, const std::string& path)
{
std::vector<std::string> components;
hz::string_split(path, '/', components, true);
json* curr = &root;
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
const std::string& comp_name = components[comp_index];
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
json& jval = iter.value();
if (comp_index + 1 == components.size()) { // it's the "value" component
curr->erase(comp_name);
break;
}
if (!jval.is_object()) { // we can't have non-object values in the middle of a path
debug_out_error("rconfig", "Component \""s + comp_name + "\" in path \"" + path + "\" is not an object, removing it.");
curr->erase(comp_name);
break;
}
// continue to the next component
curr = &jval;
} else { // path component doesn't exist
break;
}
}
}
}
@@ -109,14 +138,14 @@ namespace impl {
/// Clear user config
inline void clear_config()
{
impl::config_node = std::make_unique<picojson::value>(picojson::object());
impl::config_node = std::make_unique<json>(json::object());
}
/// Clear defaults
inline void clear_defaults()
{
impl::default_node = std::make_unique<picojson::value>(picojson::object());
impl::default_node = std::make_unique<json>(json::object());
}
@@ -135,7 +164,7 @@ inline bool init_root()
/// Get the config branch node
inline picojson::value& get_config_branch()
inline json& get_config_branch()
{
init_root();
return *impl::config_node;
@@ -144,7 +173,7 @@ inline picojson::value& get_config_branch()
/// Get the default branch node
inline picojson::value& get_default_branch()
inline json& get_default_branch()
{
init_root();
return *impl::default_node;
@@ -154,18 +183,18 @@ inline picojson::value& get_default_branch()
/// Set the data in path
template<typename T>
void set_data(const std::string& path, T data)
bool set_data(const std::string& path, T data)
{
impl::set_node_data(get_config_branch(), path, std::move(data));
}
/// picojson has only int64_t, add int for convenience.
template<>
inline void set_data<int>(const std::string& path, int data)
{
set_data<int64_t>(path, int64_t(data));
// Since config is loaded from a user file, it may contain some invalid
// nodes. Don't abort, print warnings.
try {
impl::set_node_data(get_config_branch(), path, std::move(data));
}
catch (std::exception& e) {
debug_out_error("rconfig", e.what());
return false;
}
return true;
}
@@ -174,73 +203,69 @@ inline void set_data<int>(const std::string& path, int data)
template<typename T>
void set_default_data(const std::string& path, T data)
{
// Default data branch must always be valid, so abort if anything is wrong.
impl::set_node_data(get_default_branch(), path, std::move(data));
}
/// picojson has only int64_t, add int for convenience.
template<>
inline void set_default_data<int>(const std::string& path, int data)
{
set_default_data<int64_t>(path, int64_t(data));
}
/// Get the data from config. If no such node exists, look it up in defaults.
template<typename T>
T get_data(const std::string& path)
{
T data = {};
if (!impl::get_node_data(get_config_branch(), path, data)) {
if (!impl::get_node_data(get_default_branch(), path, data)) {
throw std::runtime_error(std::string("No such node: ") + path);
}
bool found = false;
try {
// This can possibly throw because the user config file is incorrect
found = impl::get_node_data(get_config_branch(), path, data);
}
catch(std::exception& e) {
debug_out_error("rconfig", e.what());
}
// This can throw only for errors within the program.
if (!found && !impl::get_node_data(get_default_branch(), path, data)) {
throw std::runtime_error("No such node: "s + path);
}
return data;
}
/// picojson has only int64_t, add int for convenience.
template<>
inline int get_data<int>(const std::string& path)
{
return static_cast<int>(get_data<int64_t>(path));
}
/// Get the data from defaults.
template<typename T>
T get_default_data(const std::string& path)
{
T data = {};
// This can throw only for errors within the program.
if (!impl::get_node_data(get_default_branch(), path, data)) {
throw std::runtime_error(std::string("No such node: ") + path);
throw std::runtime_error("No such node: "s + path);
}
return data;
}
/// picojson has only int64_t, add int for convenience.
template<>
inline int get_default_data<int>(const std::string& path)
/// Unset data in config path
inline void unset_data(const std::string& path)
{
return static_cast<int>(get_default_data<int64_t>(path));
impl::unset_node_data(get_config_branch(), path);
}
inline void dump_config()
/// Dump config to debug output
inline void dump_config(bool print_defaults = false)
{
debug_begin();
debug_out_dump("rconfig", "Config:\n" + get_config_branch().serialize() + "\n");
debug_out_dump("rconfig", "Config:\n" + get_config_branch().dump(4) + "\n");
debug_end();
debug_begin();
debug_out_dump("rconfig", "Defaults:\n" + get_default_branch().serialize() + "\n");
debug_end();
if (print_defaults) {
debug_begin();
debug_out_dump("rconfig", "Defaults:\n" + get_default_branch().dump(4) + "\n");
debug_end();
}
}
+8 -6
View File
@@ -30,15 +30,17 @@ inline bool load_from_file(const std::string& file)
// Don't use std::fstream, it's not safe with localized filenames on win32.
hz::File f(file);
std::string json;
if (!f.get_contents(json)) { // this warns
std::string json_str;
if (!f.get_contents(json_str)) { // this warns
debug_print_error("rconfig", "load_from_file(): Unable to read from file \"%s\".\n", file.c_str());
return false;
}
std::string error = picojson::parse(get_config_branch(), json);
if (!error.empty()) {
debug_out_warn("rconfig", "Cannot load config file \"" << file << "\": " + error + "\n");
try {
get_config_branch() = json::parse(json_str);
}
catch (json::parse_error& e) {
debug_out_warn("rconfig", "Cannot load config file \"" << file << "\": " << e.what() << "\n");
return false;
}
return true;
@@ -49,7 +51,7 @@ inline bool load_from_file(const std::string& file)
/// Save the "/config" branch to a file.
inline bool save_to_file(const std::string& file)
{
std::string json = get_config_branch().serialize();
std::string json = get_config_branch().dump(4);
hz::File f(file);
if (!f.put_contents(json)) {