Closed rbroggi closed 4 years ago
Could you provide some sample JSON which match your classes, that I can use for a test case?
Thanks, Daniel
Hi Daniel, sure!
So let's assume first this cpp structure:
enum class Color { YELLOW, RED, GREEN, BLUE };
JSONCONS_ENUM_NAME_TRAITS(Color, (YELLOW, "YELLOW"), (RED, "RED"), (GREEN, "GREEN"), (BLUE, "BLUE"));
class Fruit {
private:
JSONCONS_TYPE_TRAITS_FRIEND;
std::string name;
Color color;
};
JSONCONS_ALL_MEMBER_NAME_TRAITS(Fruit,
(name, "name"),
(color, "color"));
class Indument {
private:
JSONCONS_TYPE_TRAITS_FRIEND;
int size;
std::string material;
};
JSONCONS_ALL_MEMBER_NAME_TRAITS(Indument,
(size, "size"),
(material, "material"));
class Basket {
private:
JSONCONS_TYPE_TRAITS_FRIEND;
std::string owner;
std::vector<std::variant<Fruit, Basket>> items;
};
JSONCONS_ALL_MEMBER_NAME_TRAITS(Basket,
(owner, "owner"),
(items, "items"));
I would expect the following json to be compliant (deserializable):
{
"owner": "Rodrigo",
"items": [
{
"name": "banana",
"color": "YELLOW"
},
{
"size": 40,
"material": "wool"
},
{
"name": "apple",
"color": "RED"
},
{
"size": 40,
"material": "cotton"
}
]
}
and the following json to be non-compliant:
{
"owner": "Rodrigo",
"items": [
{
"name": "banana",
"color": "YELLOW"
},
{
"size": 40,
"color": "RED"
},
{
"tittle": "what a feature!"
},
{
"size": 40,
"material": "cotton"
}
]
}
Basically I would be able to have either Indument
or Fruit
in my array.
Of course the idea would be to have variant working with:
the other std::containers as well e.g. (std::unordered_map<std::string, std::variant<int, double>> ).
standalone (like optional)
does that help?
Thank you, Rodrigo
This feature is now supported on master provided that C++17 is detected.
#include <jsoncons/json.hpp>
namespace ns {
enum class Color {yellow, red, green, blue};
inline
std::ostream& operator<<(std::ostream& os, Color val)
{
switch (val)
{
case Color::yellow: os << "yellow"; break;
case Color::red: os << "red"; break;
case Color::green: os << "green"; break;
case Color::blue: os << "blue"; break;
}
return os;
}
class Fruit
{
private:
JSONCONS_TYPE_TRAITS_FRIEND
std::string name_;
Color color_;
public:
friend std::ostream& operator<<(std::ostream& os, const Fruit& val)
{
os << "name: " << val.name_ << ", color: " << val.color_ << "\n";
return os;
}
};
class Fabric
{
private:
JSONCONS_TYPE_TRAITS_FRIEND
int size_;
std::string material_;
public:
friend std::ostream& operator<<(std::ostream& os, const Fabric& val)
{
os << "size: " << val.size_ << ", material: " << val.material_ << "\n";
return os;
}
};
class Basket
{
private:
JSONCONS_TYPE_TRAITS_FRIEND
std::string owner_;
std::vector<std::variant<Fruit, Fabric>> items_;
public:
std::string owner() const
{
return owner_;
}
std::vector<std::variant<Fruit, Fabric>> items() const
{
return items_;
}
};
} // ns
JSONCONS_ENUM_NAME_TRAITS(ns::Color, (yellow, "YELLOW"), (red, "RED"), (green, "GREEN"), (blue, "BLUE"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Fruit,
(name_, "name"),
(color_, "color"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Fabric,
(size_, "size"),
(material_, "material"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Basket,
(owner_, "owner"),
(items_, "items"))
int main()
{
std::string input = R"(
{
"owner": "Rodrigo",
"items": [
{
"name": "banana",
"color": "YELLOW"
},
{
"size": 40,
"material": "wool"
},
{
"name": "apple",
"color": "RED"
},
{
"size": 40,
"material": "cotton"
}
]
}
)";
ns::Basket basket = jsoncons::decode_json<ns::Basket>(input);
std::cout << basket.owner() << "\n\n";
std::cout << "(1)\n";
for (const auto& var : basket.items())
{
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, ns::Fruit>)
std::cout << "Fruit " << arg << '\n';
else if constexpr (std::is_same_v<T, ns::Fabric>)
std::cout << "Fabric " << arg << '\n';
}, var);
}
std::string output;
jsoncons::encode_json(basket, output, jsoncons::indenting::indent);
std::cout << "(2)\n" << output << "\n\n";
}
Output:
Rodrigo
(1)
Fruit name: banana, color: yellow
Fabric size: 28, material: wool
Fruit name: apple, color: red
Fabric size: 28, material: cotton
(2)
{
"items": [
{
"color": "YELLOW",
"name": "banana"
},
{
"material": "wool",
"size": 40
},
{
"color": "RED",
"name": "apple"
},
{
"material": "cotton",
"size": 40
}
],
"owner": "Rodrigo"
}
For classes supported through the convenience macros, e.g. Fruit
and Fabric
, the type selection strategy is the same as for polymorphic types, and is based on the presence of mandatory members in the classes. More generally, the type selection strategy is based on the json_type_traits<Json,T>::is(const Json& j)
function, checking each type in the variant from left to right, and stopping when json_type_traits<Json,T>::is(j)
returns true
.
Now consider
int main()
{
using variant_type = std::variant<int, double, bool, std::string, ns::Color>;
variant_type var1(100);
variant_type var2(10.1);
variant_type var3(false);
variant_type var4(std::string("Hello World"));
variant_type var5(ns::Color::yellow);
std::string buffer1;
jsoncons::encode_json(var1,buffer1);
std::string buffer2;
jsoncons::encode_json(var2,buffer2);
std::string buffer3;
jsoncons::encode_json(var3,buffer3);
std::string buffer4;
jsoncons::encode_json(var4,buffer4);
std::string buffer5;
jsoncons::encode_json(var5,buffer5);
std::cout << "(1) " << buffer1 << "\n";
std::cout << "(2) " << buffer2 << "\n";
std::cout << "(3) " << buffer3 << "\n";
std::cout << "(4) " << buffer4 << "\n";
std::cout << "(5) " << buffer5 << "\n";
auto v1 = jsoncons::decode_json<variant_type>(buffer1);
auto v2 = jsoncons::decode_json<variant_type>(buffer2);
auto v3 = jsoncons::decode_json<variant_type>(buffer3);
auto v4 = jsoncons::decode_json<variant_type>(buffer4);
auto v5 = jsoncons::decode_json<variant_type>(buffer5);
auto visitor = [](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int " << arg << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double " << arg << '\n';
else if constexpr (std::is_same_v<T, bool>)
std::cout << "bool " << arg << '\n';
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "std::string " << arg << '\n';
else if constexpr (std::is_same_v<T, ns::Color>)
std::cout << "ns::Color " << arg << '\n';
};
std::cout << "\n";
std::cout << "(6) ";
std::visit(visitor, v1);
std::cout << "(7) ";
std::visit(visitor, v2);
std::cout << "(8) ";
std::visit(visitor, v3);
std::cout << "(9) ";
std::visit(visitor, v4);
std::cout << "(10) ";
std::visit(visitor, v5);
std::cout << "\n\n";
}
Output:
(1) 100
(2) 10.1
(3) false
(4) "Hello World"
(5) "YELLOW"
(6) int 100
(7) double 10.1
(8) bool false
(9) std::string Hello World
(10) std::string YELLOW
Encode is fine. But when decoding, jsoncons checks if the JSON string "YELLOW" is a std::string
before it checks whether it is an ns::Color
, and since the answer is yes, it goes into the variant as a std::string
.
But if we switch the order of ns::Color
and std::string
in the variant definition, viz.
using variant_type = std::variant<int, double, bool, ns::Color, std::string>;
strings containing the text "YELLOW", "RED", "GREEN", or "BLUE" are detected to be ns::Color
,and the others std::string
.
And the output becomes
(1) 100
(2) 10.1
(3) false
(4) "Hello World"
(5) "YELLOW"
(6) int 100
(7) double 10.1
(8) bool false
(9) std::string Hello World
(10) ns::Color yellow
So: types that are more constrained should appear to the left of types that are less constrained.
Dear Daniel,
While using the library I've realized that it seems the lib does not have built-in support for std::variant (as it has for std::optional for example). I think it could be a handy feature to support something like the piece of code below:
Having std::variant instead of 'polimorphism' has several advantages and I would love seeing jsoncons integrating it as a vocabulary type.
Thank you once again for the amazing product and job,
Rodrigo