Skip to content

Commit 9a22cd2

Browse files
json::merge(): Two little fixes (#1333)
* Specify and test: JSON/TOML as input/output for json::merge * Add Python binding * Sort lines for the TOML equality check * Fix NVCC TOML ios_base::openmode * Revert "Fix NVCC TOML ios_base::openmode" This reverts commit 22f0585. * deactivate json_merging test for nvc++
1 parent 599ac5a commit 9a22cd2

File tree

4 files changed

+116
-14
lines changed

4 files changed

+116
-14
lines changed

include/openPMD/auxiliary/JSON.hpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ namespace openPMD
2828
namespace json
2929
{
3030
/**
31-
* @brief Merge two JSON datasets into one.
31+
* @brief Merge two JSON/TOML datasets into one.
3232
*
3333
* Merging rules:
34-
* 1. If both `defaultValue` and `overwrite` are JSON objects, then the
35-
* resulting JSON object will contain the union of both objects' keys.
36-
* If a key is specified in both objects, the values corresponding to the
37-
* key are merged recursively.
38-
* Keys that point to a null value after this procedure will be pruned.
39-
* 2. In any other case, the JSON dataset `defaultValue` is replaced in its
40-
* entirety with the JSON dataset `overwrite`.
34+
* 1. If both `defaultValue` and `overwrite` are JSON/TOML objects, then the
35+
* resulting JSON/TOML object will contain the union of both objects'
36+
* keys. If a key is specified in both objects, the values corresponding
37+
* to the key are merged recursively. Keys that point to a null value
38+
* after this procedure will be pruned.
39+
* 2. In any other case, the JSON/TOML dataset `defaultValue` is replaced in
40+
* its entirety with the JSON/TOML dataset `overwrite`.
4141
*
4242
* Note that item 2 means that datasets of different type will replace each
4343
* other without error.
@@ -46,15 +46,18 @@ namespace json
4646
*
4747
* Possible use case:
4848
* An application uses openPMD-api and wants to do the following:
49-
* 1. Set some default backend options as JSON parameters.
49+
* 1. Set some default backend options as JSON/TOML parameters.
5050
* 2. Let its users specify custom backend options additionally.
5151
*
5252
* By using the json::merge() function, this application can then allow
5353
* users to overwrite default options, while keeping any other ones.
5454
*
55-
* @param defaultValue
56-
* @param overwrite
57-
* @return std::string
55+
* @param defaultValue A string containing either a JSON or a TOML dataset.
56+
* @param overwrite A string containing either a JSON or TOML dataset (does
57+
* not need to be the same as `defaultValue`).
58+
* @return std::string The merged dataset, according to the above rules. If
59+
* `defaultValue` was a JSON dataset, then as a JSON string, otherwise as a
60+
* TOML string.
5861
*/
5962
std::string
6063
merge(std::string const &defaultValue, std::string const &overwrite);

src/auxiliary/JSON.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,8 +570,21 @@ merge(nlohmann::json &defaultVal, nlohmann::json const &overwrite)
570570

571571
std::string merge(std::string const &defaultValue, std::string const &overwrite)
572572
{
573-
auto res = parseOptions(defaultValue, /* considerFiles = */ false).config;
573+
auto [res, returnFormat] =
574+
parseOptions(defaultValue, /* considerFiles = */ false);
574575
merge(res, parseOptions(overwrite, /* considerFiles = */ false).config);
575-
return res.dump();
576+
switch (returnFormat)
577+
{
578+
case SupportedLanguages::JSON:
579+
return res.dump();
580+
break;
581+
case SupportedLanguages::TOML: {
582+
auto asToml = json::jsonToToml(res);
583+
std::stringstream sstream;
584+
sstream << asToml;
585+
return sstream.str();
586+
}
587+
}
588+
throw std::runtime_error("Unreachable!");
576589
}
577590
} // namespace openPMD::json

src/binding/python/Series.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <pybind11/stl.h>
2424

2525
#include "openPMD/Series.hpp"
26+
#include "openPMD/auxiliary/JSON.hpp"
2627
#include "openPMD/config.hpp"
2728

2829
#if openPMD_HAVE_MPI
@@ -231,4 +232,43 @@ this method.
231232
"write_iterations",
232233
&Series::writeIterations,
233234
py::keep_alive<0, 1>());
235+
236+
m.def(
237+
"merge_json",
238+
&json::merge,
239+
py::arg("default_value") = "{}",
240+
py::arg("overwrite") = "{}",
241+
R"END(
242+
Merge two JSON/TOML datasets into one.
243+
244+
Merging rules:
245+
1. If both `defaultValue` and `overwrite` are JSON/TOML objects, then the
246+
resulting JSON/TOML object will contain the union of both objects'
247+
keys. If a key is specified in both objects, the values corresponding
248+
to the key are merged recursively. Keys that point to a null value
249+
after this procedure will be pruned.
250+
2. In any other case, the JSON/TOML dataset `defaultValue` is replaced in
251+
its entirety with the JSON/TOML dataset `overwrite`.
252+
253+
Note that item 2 means that datasets of different type will replace each
254+
other without error.
255+
It also means that array types will replace each other without any notion
256+
of appending or merging.
257+
258+
Possible use case:
259+
An application uses openPMD-api and wants to do the following:
260+
1. Set some default backend options as JSON/TOML parameters.
261+
2. Let its users specify custom backend options additionally.
262+
263+
By using the json::merge() function, this application can then allow
264+
users to overwrite default options, while keeping any other ones.
265+
266+
Parameters:
267+
* default_value: A string containing either a JSON or a TOML dataset.
268+
* overwrite: A string containing either a JSON or TOML dataset (does
269+
not need to be the same as `defaultValue`).
270+
* returns: The merged dataset, according to the above rules.
271+
If `defaultValue` was a JSON dataset, then as a JSON string,
272+
otherwise as a TOML string.
273+
)END");
234274
}

test/JSONTest.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55

66
#include <catch2/catch.hpp>
77

8+
#include <algorithm>
89
#include <fstream>
10+
#include <sstream>
11+
#include <string>
912
#include <variant>
13+
#include <vector>
1014

1115
using namespace openPMD;
1216

@@ -122,6 +126,7 @@ TEST_CASE("json_parsing", "[auxiliary]")
122126
REQUIRE(jsonUpper.dump() == jsonLower.dump());
123127
}
124128

129+
#if !__NVCOMPILER // see https://github.com/ToruNiina/toml11/issues/205
125130
TEST_CASE("json_merging", "auxiliary")
126131
{
127132
std::string defaultVal = R"END(
@@ -174,7 +179,48 @@ TEST_CASE("json_merging", "auxiliary")
174179
REQUIRE(
175180
json::merge(defaultVal, overwrite) ==
176181
json::parseOptions(expect, false).config.dump());
182+
183+
{
184+
// The TOML library doesn't guarantee a specific order of output
185+
// so we need to sort lines to compare with expected results
186+
auto sort_lines = [](std::string const &s) -> std::vector<std::string> {
187+
std::vector<std::string> v;
188+
std::istringstream sstream(s);
189+
for (std::string line; std::getline(sstream, line);
190+
line = std::string())
191+
{
192+
v.push_back(std::move(line));
193+
}
194+
std::sort(v.begin(), v.end());
195+
return v;
196+
};
197+
std::string leftJson = R"({"left": "val"})";
198+
std::string rightJson = R"({"right": "val"})";
199+
std::string leftToml = R"(left = "val")";
200+
std::string rightToml = R"(right = "val")";
201+
202+
std::string resJson =
203+
nlohmann::json::parse(R"({"left": "val", "right": "val"})").dump();
204+
std::vector<std::string> resToml = [&sort_lines]() {
205+
constexpr char const *raw = R"(
206+
left = "val"
207+
right = "val"
208+
)";
209+
std::istringstream istream(
210+
raw, std::ios_base::binary | std::ios_base::in);
211+
toml::value tomlVal = toml::parse(istream);
212+
std::stringstream sstream;
213+
sstream << tomlVal;
214+
return sort_lines(sstream.str());
215+
}();
216+
217+
REQUIRE(json::merge(leftJson, rightJson) == resJson);
218+
REQUIRE(json::merge(leftJson, rightToml) == resJson);
219+
REQUIRE(sort_lines(json::merge(leftToml, rightJson)) == resToml);
220+
REQUIRE(sort_lines(json::merge(leftToml, rightToml)) == resToml);
221+
}
177222
}
223+
#endif
178224

179225
/*
180226
* This tests two things about the /data/snapshot attribute:

0 commit comments

Comments
 (0)