オブジェクトをJSONにできるだけ自動的にシリアライズおよびデシリアライズする方法が必要です。
Serialize:私にとって理想的な方法は、インスタンスJSONSerialize()を呼び出すと、オブジェクトのすべてのパブリックプロパティを_"name_of_property": "value"
_として持つJSONオブジェクトの文字列を返すことです。プリミティブな値の場合、それは簡単です。オブジェクトの場合、各JSONSerialize()またはToString()などを呼び出して、すべてのパブリックプロパティを再帰的にシリアル化する必要があります。コレクションの場合は、正しく動作する必要があります(ベクター/配列だけで問題ありません)。
デシリアライズ:指定されたオブジェクトのインスタンスを作成し(犬としましょう)、JSONDeserialize(json_string)
を呼び出します。これにより、すべてのパブリックプロパティが満たされ、プロパティはプリミティブではなく、必要なコレクションでもありません。
例は次のように実行されます。
_Dog *d1 = new Dog();
d1->name = "myDog";
string serialized = d1->JSONSerialize();
Dog *d2 = new Dog();
d2->JSONDeserialize(serialized);
std::cout << d2->name; // This will print "myDog"
_
またはそのように:
_Dog *d1 = new Dog();
d1->name = "myDog";
string serialized = JSONSerializer.Serialize(d1);
Dog *d2 = JSONSerializer.Deserialize(serialized, Dog);
std::cout << d2->name; // This will print "myDog"
_
どうすれば簡単にこれを実行できますか?
そのためには、C/C++言語でのリフレクションが必要ですが、それは存在しません。クラス(メンバー、継承された基本クラス)の構造を記述するメタデータが必要です。現時点では、C/C++コンパイラは、ビルドされたバイナリでその情報を自動的に提供しません。
私は同じ考えを持っていて、この情報を得るために GCC XML プロジェクトを使用しました。クラス構造を記述するXMLデータを出力します。私はプロジェクトを構築しましたが、この page でいくつかの重要なポイントを説明しています。
シリアル化は簡単ですが、割り当てられたバッファを使用する複雑なデータ構造の実装(std :: string、std :: mapなど)を処理する必要があります。逆シリアル化はより複雑で、すべてのメンバーとvtableへの参照を含むオブジェクトを再構築する必要があります。
たとえば、次のようにシリアル化できます。
// Random class initialization
com::class1* aObject = new com::class1();
for (int i=0; i<10; i++){
aObject->setData(i,i);
}
aObject->pdata = new char[7];
for (int i=0; i<7; i++){
aObject->pdata[i] = 7-i;
}
// dictionary initialization
cjson::dictionary aDict("./data/dictionary.xml");
// json transformation
std::string aJson = aDict.toJson<com::class1>(aObject);
// print encoded class
cout << aJson << std::endl ;
データをデシリアライズするには、次のように機能します。
// decode the object
com::class1* aDecodedObject = aDict.fromJson<com::class1>(aJson);
// modify data
aDecodedObject->setData(4,22);
// json transformation
aJson = aDict.toJson<com::class1>(aDecodedObject);
// print encoded class
cout << aJson << std::endl ;
Ouptuts:
>:~/cjson$ ./main
{"_index":54,"_inner": {"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,4,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
{"_index":54,"_inner":{"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,22,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
>:~/cjson$
通常、これらの実装はコンパイラに依存し(ABI仕様など)、動作するために外部記述(GCCXML出力)が必要であり、プロジェクトへの統合は実際には簡単ではありません。
C++にはリフレクションはありません。本当です。ただし、コンパイラが必要なメタデータを提供できない場合は、自分で提供できます。
プロパティ構造体を作成することから始めましょう:
template<typename Class, typename T>
struct PropertyImpl {
constexpr PropertyImpl(T Class::*aMember, const char* aName) : member{aMember}, name{aName} {}
using Type = T;
T Class::*member;
const char* name;
};
template<typename Class, typename T>
constexpr auto property(T Class::*member, const char* name) {
return PropertyImpl<Class, T>{member, name};
}
もちろん、メンバーへのポインターの代わりにセッターとゲッターを使用するproperty
を使用して、シリアル化する計算値のプロパティのみを読み取ることもできます。 C++ 17を使用する場合は、さらに拡張して、ラムダで機能するプロパティを作成できます。
さて、これでコンパイル時イントロスペクションシステムのビルディングブロックができました。
クラスDog
で、メタデータを追加します。
struct Dog {
std::string barkType;
std::string color;
int weight = 0;
bool operator==(const Dog& rhs) const {
return std::tie(barkType, color, weight) == std::tie(rhs.barkType, rhs.color, rhs.weight);
}
constexpr static auto properties = std::make_Tuple(
property(&Dog::barkType, "barkType"),
property(&Dog::color, "color"),
property(&Dog::weight, "weight")
);
};
そのリストを繰り返す必要があります。タプルを反復するには、多くの方法がありますが、私が好む方法はこれです:
template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
using unpack_t = int[];
(void)unpack_t{(static_cast<void>(f(std::integral_constant<T, S>{})), 0)..., 0};
}
コンパイラでC++ 17フォールド式が使用可能な場合、for_sequence
は次のように簡略化できます。
template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
(static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}
これにより、整数シーケンスの各定数に対して関数が呼び出されます。
この方法が機能しないか、コンパイラに問題が発生する場合は、常に array expansion trick を使用できます。
目的のメタデータとツールが揃ったので、プロパティを反復処理してシリアル化を解除できます。
// unserialize function
template<typename T>
T fromJson(const Json::Value& data) {
T object;
// We first get the number of properties
constexpr auto nbProperties = std::Tuple_size<decltype(T::properties)>::value;
// We iterate on the index sequence of size `nbProperties`
for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
// get the property
constexpr auto property = std::get<i>(T::properties);
// get the type of the property
using Type = typename decltype(property)::Type;
// set the value to the member
// you can also replace `asAny` by `fromJson` to recursively serialize
object.*(property.member) = Json::asAny<Type>(data[property.name]);
});
return object;
}
シリアル化の場合:
template<typename T>
Json::Value toJson(const T& object) {
Json::Value data;
// We first get the number of properties
constexpr auto nbProperties = std::Tuple_size<decltype(T::properties)>::value;
// We iterate on the index sequence of size `nbProperties`
for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
// get the property
constexpr auto property = std::get<i>(T::properties);
// set the value to the member
data[property.name] = object.*(property.member);
});
return data;
}
再帰的なシリアル化と非シリアル化が必要な場合は、asAny
をfromJson
に置き換えることができます。
これで、次のような関数を使用できます。
Dog dog;
dog.color = "green";
dog.barkType = "whaf";
dog.weight = 30;
Json::Value jsonDog = toJson(dog); // produces {"color":"green", "barkType":"whaf", "weight": 30}
auto dog2 = fromJson<Dog>(jsonDog);
std::cout << std::boolalpha << (dog == dog2) << std::endl; // pass the test, both dog are equal!
できた!ランタイムリフレクションの必要はなく、C++ 14の良さだけです!
このコードはいくつかの改善から恩恵を受ける可能性があり、もちろんいくつかの調整を加えてC++ 11で動作する可能性があります。
asAny
関数を書く必要があることに注意してください。これは、単にJson::Value
と右呼び出しas...
関数、または別のfromJson
。
この回答のさまざまなコードスニペットから作成された 完全な例 を次に示します。気軽に使用してください。
コメントで言及したように、このコードはmsvcでは機能しません。互換性のあるコードが必要な場合は、この質問を参照してください。 メンバーへのポインター:GCCで動作しますが、VS2015では動作しません
そのような簡単なものはありますか??ありがとう:))
C++は、コンパイルされたコードにクラスメンバー名を保存せず、どのメンバー(変数/メソッド)クラスが含まれているかを(実行時に)発見する方法はありません。つまり、構造体のメンバーを反復処理することはできません。そのようなメカニズムがないため、すべてのオブジェクトに対して「JSONserialize」を自動的に作成することはできません。
ただし、jsonライブラリを使用してオブジェクトをシリアル化できますが、クラスごとにシリアル化/逆シリアル化コードを自分で記述する必要があります。それとも、すべてのシリアライズ可能なオブジェクトの構造体の代わりに使用される QVariantMap のようなシリアライズ可能なクラスを作成する必要があります。
つまり、すべてのシリアル化可能なオブジェクトに特定の型を使用する(または、クラスごとにシリアル化ルーチンを自分で記述する)ことで問題ない場合は、それを行うことができます。ただし、可能なすべてのクラスを自動的にシリアル化する場合は、忘れてください。この機能が重要な場合は、別の言語を試してください。
誰かがまだこのニーズを持っている(私が持っている)場合、私はこの問題に対処するために自分でライブラリを書きました。 こちらを参照 。クラス内のすべてのフィールドを記述する必要があるという点で完全に自動化されているわけではありませんが、C++にはリフレクションがないため、取得できるものに近いものです。
まだ言及されていませんが、それは私の検索結果の最初でした: https://github.com/nlohmann/json
リストされている特典:
また、MIT License。
正直言って、まだ使用していませんが、いくつかの経験を通じて、本当によくできたC++ライブラリに出会ったときを判断するコツがあります。
jsoncons C++ヘッダーのみのライブラリは、JSONテキストとC++オブジェクト間の変換もサポートしています。デコードおよびエンコードは、json_type_traitsが定義されているすべてのC++クラスに対して定義されます。標準ライブラリコンテナはすでにサポートされており、json_type_traitsはjsoncons名前空間のユーザータイプに特化できます。
以下に例を示します。
#include <iostream>
#include <jsoncons/json.hpp>
namespace ns {
enum class hiking_experience {beginner,intermediate,advanced};
class hiking_reputon
{
std::string rater_;
hiking_experience assertion_;
std::string rated_;
double rating_;
public:
hiking_reputon(const std::string& rater,
hiking_experience assertion,
const std::string& rated,
double rating)
: rater_(rater), assertion_(assertion), rated_(rated), rating_(rating)
{
}
const std::string& rater() const {return rater_;}
hiking_experience assertion() const {return assertion_;}
const std::string& rated() const {return rated_;}
double rating() const {return rating_;}
};
class hiking_reputation
{
std::string application_;
std::vector<hiking_reputon> reputons_;
public:
hiking_reputation(const std::string& application,
const std::vector<hiking_reputon>& reputons)
: application_(application),
reputons_(reputons)
{}
const std::string& application() const { return application_;}
const std::vector<hiking_reputon>& reputons() const { return reputons_;}
};
} // namespace ns
// Declare the traits using convenience macros. Specify which data members need to be serialized.
JSONCONS_ENUM_TRAITS_DECL(ns::hiking_experience, beginner, intermediate, advanced)
JSONCONS_GETTER_CTOR_TRAITS_DECL(ns::hiking_reputon, rater, assertion, rated, rating)
JSONCONS_GETTER_CTOR_TRAITS_DECL(ns::hiking_reputation, application, reputons)
using namespace jsoncons; // for convenience
int main()
{
std::string data = R"(
{
"application": "hiking",
"reputons": [
{
"rater": "HikingAsylum",
"assertion": "advanced",
"rated": "Marilyn C",
"rating": 0.90
}
]
}
)";
// Decode the string of data into a c++ structure
ns::hiking_reputation v = decode_json<ns::hiking_reputation>(data);
// Iterate over reputons array value
std::cout << "(1)\n";
for (const auto& item : v.reputons())
{
std::cout << item.rated() << ", " << item.rating() << "\n";
}
// Encode the c++ structure into a string
std::string s;
encode_json<ns::hiking_reputation>(v, s, indenting::indent);
std::cout << "(2)\n";
std::cout << s << "\n";
}
出力:
(1)
Marilyn C, 0.9
(2)
{
"application": "hiking",
"reputons": [
{
"assertion": "advanced",
"rated": "Marilyn C",
"rater": "HikingAsylum",
"rating": 0.9
}
]
}
quicktype を使用すると、JSONサンプルデータからC++シリアライザーとデシリアライザーを生成できます。
たとえば、サンプルJSONがある場合:
{
"breed": "Boxer",
"age": 5,
"tail_length": 6.5
}
クイックタイプ 生成:
#include "json.hpp"
namespace quicktype {
using nlohmann::json;
struct Dog {
int64_t age;
std::string breed;
double tail_length;
};
inline json get_untyped(const json &j, const char *property) {
if (j.find(property) != j.end()) {
return j.at(property).get<json>();
}
return json();
}
}
namespace nlohmann {
inline void from_json(const json& _j, struct quicktype::Dog& _x) {
_x.age = _j.at("age").get<int64_t>();
_x.breed = _j.at("breed").get<std::string>();
_x.tail_length = _j.at("tail_length").get<double>();
}
inline void to_json(json& _j, const struct quicktype::Dog& _x) {
_j = json{{"age", _x.age}, {"breed", _x.breed}, {"tail_length", _x.tail_length}};
}
}
Dog JSONデータを解析するには、上記のコードを含めて、 Boost および json.hpp をインストールしてから実行します。
Dog dog = nlohmann::json::parse(jsonString);
ThorsSerializer の使用
Dog *d1 = new Dog();
d1->name = "myDog";
std::stringstream stream << ThorsAnvil::Serialize::jsonExport(d1);
string serialized = stream.str();
Dog *d2 = nullptr;
stream >> ThorsAnvil::Serialize::jsonImport(d2);
std::cout << d2->name; // This will print "myDog"
それはあなたのオリジナルにかなり近いと思います。
わずかな設定があります。クラスがSerializableであることを宣言する必要があります。
#include "ThorSerialize/Traits.h"
#include "ThorSerialize/JsonThor.h"
struct Dog
{
std::string name;
};
// Declare the "Dog" class is Serializable; Serialize the member "name"
ThorsAnvil_MakeTrait(Dog, name);
他のコーディングは必要ありません。
完全な例は次のとおりです。
json_dto を試してください。ヘッダーのみで使いやすいです。
簡単な例:
struct message_t
{
std::string m_from;
std::string m_text;
// Entry point for json_dto.
template < typename JSON_IO >
void
json_io( JSON_IO & io )
{
io
& json_dto::mandatory( "from", m_from )
& json_dto::mandatory( "text", m_text );
}
};
これは、変換可能toおよびfromJSON:
{ "from" : "json_dto", "text" : "Hello world!" }