Reputation: 2146
I'm trying to serialize/deserialize std::map<int, MyObject>
using https://github.com/nlohmann/json
It says here that the keys for the map must be able to generate std::string
. How can I achieve that?
Basic usage for implementing conversion to/from my custom object MyObject
would be:
using nlohmann::json;
namespace ns {
void to_json(json& j, const MyObject& p) {
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
}
void from_json(const json& j, MyObject& p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}
} // namespace ns
however what should I do for the map? The last thing I want to do is implement void to_json(json& j, const std::map<int, MyObject>& p)
and void from_json(const json& j, std::map<int, MyObject>& p)
. Since the library already supports std::map
, it should do everything for me, I just need to convert the int
to std::string
. How can I do just that?
Upvotes: 1
Views: 6524
Reputation: 169
Here's a generic solution: define to_json()
and from_json()
function templates for arbitrary std::map
as follows:
template <typename KTy, typename VTy> inline
void to_json(nlohmann::json& j, const std::map<KTy, VTy>& m)
{
using std::to_string;
for (const auto& e : m)
{
j[to_string(e.first)] = e.second;
}
}
template <typename KTy, typename VTy> inline
void from_json(const nlohmann::json& j, std::map<KTy, VTy>& m)
{
for (const auto& e : j.items())
{
KeyTy k;
from_string(e.key(), k);
auto v = e.value().get<VTy>();
m[k] = v;
}
}
Now we have define a way to convert string values to our map's key_type
("deserialize" the keys). For example, it can be achieved with a from_string()
function template like this:
// A generic implementation (stub).
template <typename Ty> inline
void from_string(const std::string& key, Ty& value)
{
value = key;
}
template <> inline
void from_string(const std::string& key, int& value)
{
char* endPtr = nullptr;
val = std::strtol(key.c_str(), &endPtr, 10);
}
// Add more specializations for other key types here.
Having these functions in place, and to
/from_json()
overloads for custom types (from the original question), one can convert std::map
to and from JSON the natural way:
nlohmann::json j;
std::map<int, MyObject> myMap;
// ...
myMap = j.at("inputs").get<std::map<int, MyObject>>();
j = myMap;
Upvotes: 0
Reputation: 169028
The wording in the readme is clear as to why this doesn't work (emphasis mine):
Likewise, any associative key-value containers ... whose keys can construct an
std::string
and whose values can be used to construct JSON values ... can be used to create a JSON object.
This is why using int
as a key doesn't work with this library; an std::string
cannot be constructed from an int
.
The natural follow-up question is why this isn't allowed. This is something you would have to ask the author of the library for a definitive answer.
We could speculate that it's because there is no one way to convert an integral to a string. Perhaps assume a base 10 representation with no leading zeros would be a reasonable choice, but there is no compelling reason why this must be the only choice.
What about hex-encoded strings, or scientific notation, or thousands separators, or any possible number of other options?
Another possible reason is that integral keys suggest a sparse array, so it may be unclear whether you are requesting an object or an array be produced in the output.
A possible solution would be to build a helper that converts any map to an std::map<std::string, TValue>
by running the keys through std::to_string()
:
// Copy variant
template <typename T>
std::map<std::string, typename T::mapped_type> to_string_keyed_map(T const & input) {
std::map<std::string, typename T::mapped_type> output;
for (auto const & pair : input) {
output.emplace(std::to_string(pair.first), pair.second);
}
return output;
}
// Move variant
template <typename T>
std::map<std::string, typename T::mapped_type> to_string_keyed_map(T && input) {
std::map<std::string, typename T::mapped_type> output;
for (auto & pair : input) {
output.emplace(std::to_string(pair.first), std::move(pair.second));
}
return output;
}
Upvotes: 2