web-dev-qa-db-fra.com

Sérialisation JSON C++

Je veux un moyen de sérialiser et de désérialiser des objets en JSON, aussi automatiquement que possible.

Sérialiser: Pour moi, le moyen idéal est que, si j'appelle dans une instance, JSONSerialize (), il retourne une chaîne avec un objet JSON contenant toutes les propriétés publiques de l'objet sous la forme "name_of_property": "value". C’est simple, pour les objets, il convient d’appeler JSONSerialize () ou ToString () ou quelque chose du genre pour sérialiser récursivement toutes les propriétés publiques . Pour les collections, il doit également se comporter correctement les vecteurs/tableaux seront ok).

Deserialize : Créez simplement une instance de l'objet donné (disons un chien) et appelez JSONDeserialize(json_string), qui remplira toutes les propriétés publiques en créant les objets nécessaires au cas où les propriétés ne seraient pas des primitives ou les collections nécessaires. .

Un exemple devrait fonctionner comme ça:

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"

Ou comme ça:

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"

Comment puis-je retirer ceci facilement?

35
Vicenç Gascó

Pour cela, vous avez besoin d'une réflexion en langage C/C++, cela n'existe pas. Vous devez disposer de métadonnées décrivant la structure de vos classes (membres, classes de base héritées). Pour le moment, les compilateurs C/C++ ne fournissent pas automatiquement ces informations dans des fichiers binaires construits.

J'avais la même idée en tête et j'ai utilisé GCC XML project pour obtenir cette information. Il génère des données XML décrivant les structures de classe. J'ai construit un projet et j'explique quelques points clés dans cette page :

La sérialisation est facile, mais nous devons traiter avec des implémentations complexes de structure de données (std :: string, std :: map par exemple) qui jouent avec les tampons alloués. La désérialisation est plus complexe et vous devez reconstruire votre objet avec tous ses membres, ainsi que des références à vtables ... une implémentation pénible.

Par exemple, vous pouvez sérialiser comme ceci:

    // 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 ;

Pour désérialiser les données, cela fonctionne comme ça:

    // 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$ 

Habituellement, ces implémentations dépendent du compilateur (spécification ABI, par exemple) et nécessitent une description externe pour fonctionner (sortie GCCXML). Elles ne sont donc pas faciles à intégrer aux projets.

8
JBV06

Il n'y a pas de reflet en C++. Vrai. Mais si le compilateur ne peut pas vous fournir les métadonnées dont vous avez besoin, vous pouvez les fournir vous-même.

Commençons par créer une structure de propriété:

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};
}

Bien sûr, vous pouvez aussi avoir une property qui prend un séparateur et un getter au lieu d'un pointeur sur membre, et peut-être ne lire que les propriétés de la valeur calculée que vous souhaitez sérialiser. Si vous utilisez C++ 17, vous pouvez l'étendre davantage pour créer une propriété qui fonctionne avec lambdas.

Ok, nous avons maintenant la pierre angulaire de notre système d’introspection au moment de la compilation.

Maintenant, dans votre classe Dog, ajoutez vos métadonnées:

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")
    );
};

Nous devrons parcourir cette liste. Pour itérer sur un tuple, il y a plusieurs façons, mais celle que je préfère est la suivante:

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};
}

Si des expressions de pliage C++ 17 sont disponibles dans votre compilateur, vous pouvez simplifier for_sequence en:

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>{})), ...);
}

Cela appellera une fonction pour chaque constante de la séquence entière.

Si cette méthode ne fonctionne pas ou pose des problèmes à votre compilateur, vous pouvez toujours utiliser l'astuce extension du tableau .

Maintenant que vous avez les métadonnées et les outils souhaités, vous pouvez parcourir les propriétés pour désérialiser:

// 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;
}

Et pour sérialiser:

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;
}

Si vous souhaitez une sérialisation et une désérialisation récursives, vous pouvez remplacer asAny par fromJson.

Maintenant, vous pouvez utiliser vos fonctions comme ceci:

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!

Terminé! Pas besoin de réflexion à l'exécution, juste de la bonté C++ 14!

Ce code pourrait bénéficier d'améliorations et pourrait bien sûr fonctionner avec C++ 11 avec quelques ajustements.

Notez qu'il faudrait écrire la fonction asAny. C'est juste une fonction qui prend un Json::Value et appelle la bonne fonction as..., ou une autre fromJson.

Voici un exemple de travail complet réalisé à partir des divers extraits de code de cette réponse. Sentez-vous libre de l'utiliser.

Comme mentionné dans les commentaires, ce code ne fonctionnera pas avec msvc. Veuillez vous référer à cette question si vous voulez un code compatible: Pointeur vers membre: fonctionne dans GCC mais pas dans VS2015

63
Guillaume Racicot

Est-ce que quelque chose, facile comme ça, existe ?? MERCI :))

C++ ne stocke pas les noms des membres de la classe dans le code compilé, et il n'y a aucun moyen de découvrir (au moment de l'exécution) les membres (variables/méthodes) de la classe. En d'autres termes, vous ne pouvez pas parcourir les membres d'une structure. En l'absence d'un tel mécanisme, vous ne pourrez pas créer automatiquement "JSONserialize" pour chaque objet.

Vous pouvez cependant utiliser n'importe quelle bibliothèque JSON pour sérialiser des objets, MAIS vous devrez écrire vous-même le code de sérialisation/désérialisation pour chaque classe. Soit cela, soit vous devrez créer une classe sérialisable similaire à QVariantMap qui sera utilisée à la place des structures pour tous les objets sérialisables. 

En d'autres termes, si vous acceptez d'utiliser un type spécifique pour tous les objets sérialisables (ou d'écrire vous-même des routines de sérialisation pour chaque classe), vous pouvez le faire . Cependant, si vous souhaitez sérialiser automatiquement toutes les classes possibles, vous devez oublie ça. Si cette fonctionnalité est importante pour vous, essayez une autre langue.

7
SigTerm

Au cas où quelqu'un aurait encore ce besoin (moi aussi), j'ai moi-même écrit une bibliothèque pour traiter ce problème. Vois ici . Ce n'est pas complètement automatique en ce sens que vous devez décrire tous les champs de vos classes, mais c'est aussi proche que ce que nous pouvons obtenir car C++ manque de réflexion.

3
Siyuan Ren

Pas encore mentionné, bien qu'il s'agisse du premier résultat de recherche: https://github.com/nlohmann/json

Avantages énumérés:

  • Syntaxe intuitive (fière allure!)
  • Fichier d'en-tête unique à inclure, rien d'autre
  • Ridiculement testé

En outre, il est sous la licence MIT.

Je serai honnête: je ne l’ai pas encore utilisé, mais grâce à une certaine expérience, j’ai le don de déterminer quand je tomberai sur une bibliothèque c ++ vraiment bien faite.

1
Andrew

Avec quicktype , vous pouvez générer des sérialiseurs et des desaliseurs C++ à partir des exemples de données JSON.

Par exemple, à l'aide de l'exemple de code JSON:

{
  "breed": "Boxer",
  "age": 5,
  "tail_length": 6.5
}

quicktype génère:

#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}};
    }
}

Pour analyser les données Dog JSON, incluez le code ci-dessus, installez Boost et json.hpp , puis faites:

Dog dog = nlohmann::json::parse(jsonString);
1
David Siegel

Utiliser 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"

Je pense que c'est assez proche de votre original.
Il y a un peu de mise en place. Vous devez déclarer que votre classe est sérialisable.

#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);

Aucun autre codage n'est requis.

Des exemples complets peuvent être trouvés:

0
Martin York

Essayez https://github.com/xyz347/x2struct . Il supporte la sérialisation et la désérialisation entre json/xml/bson et la structure C++. Voici un exemple:

#include <string>  
#include <iostream>  
#include "x2struct.hpp"  // include x2struct.hpp

using namespace std;  

struct example {  
    int a;  
    string b;  
    XTOSTRUCT(O(a,b));  // add XTOSTRUCT at the end of struct define
};  

int main(int argc, char*argv[]) {  
    example e;  
    x2struct::X::loadjson("{\"a\":123, \"b\":\"hello\"}", e, false); // json to c++ struct
    cout<<"a:"<<e.a<<endl;
    cout<<"b:"<<e.b<<endl;
    string jstr=x2struct::X::tojson(e);// c++ struct to json  
    cout<<jstr<<endl;  
    return 0;  
}

la sortie est:

a:123
b:hello
{"a":123,"b":"hello"}

tout ce que vous devez faire c'est 

  1. lance make pour générer libx2struct.a 
  2. inclure x2struct.hpp dans votre fichier source 
  3. ajoutez la macro XTOSTRUCT à la fin de la définition de structure.
  4. appelez x2struct :: X :: loadjson pour désérialiser la chaîne JSON en c ++ struct ou appelez x2struct :: X :: tojson pour sérialiser la structure c ++ en json

idem que xml et bsonsupport vector/set/map

0
xyz347

Essayez json_dto ..__ C'est un en-tête et facile à utiliser. 

Exemple simple:

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 );
  }
};

Ce sera convertible à et à partir de JSON:

{ "from" : "json_dto", "text" : "Hello world!" }
0
kola