Подтвердить что ты не робот

Сериализация С++ JSON

Мне нужен способ сериализации и десериализации объектов в JSON, как можно более автоматический.

Сериализация: Для меня идеальным способом является то, что если я вызываю экземпляр JSONSerialize(), он возвращает строку с объектом JSON, которая имеет все общедоступные свойства объекта как "name_of_property": "value". Для тех значений, которые являются примитивами, это просто, для объектов, которые он должен попытаться вызвать на каждом JSONSerialize() или ToString() или что-то подобное, чтобы рекурсивно сериализовать все общедоступные свойства. Для коллекций он также должен вести себя корректно (только векторы/массивы будут в порядке).

Deserialize: просто создайте экземпляр данного объекта (скажем, собаку) и вызовите 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"

Как я могу легко снять это?

4b9b3361

Ответ 1

Для этого вам нужно отразить на языке C/С++, который не существует. Вам нужно иметь метаданные, описывающие структуру ваших классов (членов, унаследованных базовых классов). На данный момент компиляторы C/С++ автоматически не предоставляют эту информацию в встроенных двоичных файлах.

У меня была такая же идея, и я использовал GCC XML для получения этой информации. Он выводит XML-данные, описывающие структуры классов. Я построил проект, и я объясняю некоторые ключевые моменты на этой странице :

Сериализация проста, но нам приходится иметь дело со сложными реализацией структуры данных (например, std::string, std:: map), которые играют с выделенными буферами. Deserialization является более сложным, и вам нужно перестроить свой объект со всеми его членами, а также ссылки на vtables... мучительную реализацию.

Например, вы можете сериализовать так:

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

выходы:

>:~/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), поэтому не очень легко интегрироваться в проекты.

Ответ 2

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

Если в вашем компиляторе доступно 17 кратных выражений C++, то 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>{})), ...);
}

Это вызовет функцию для каждой константы в целочисленной последовательности.

Если этот метод не работает или создает проблемы для вашего компилятора, вы всегда можете использовать трюк расширения массива.

Теперь, когда у вас есть нужные метаданные и инструменты, вы можете перебирать свойства для десериализации:

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

Ответ 3

Есть ли что-нибудь, такое простое? СПАСИБО:))

С++ не сохраняет имена членов класса в скомпилированном коде, и нет способа обнаружить (во время выполнения), какие классы (переменные/методы) содержат. Другими словами, вы не можете перебирать элементы структуры. Поскольку нет такого механизма, вы не сможете автоматически создавать "JSONserialize" для каждого объекта.

Вы можете, однако, использовать любую библиотеку json для сериализации объектов, НО вам придется писать код сериализации/десериализации самостоятельно для каждого класса. Либо это, либо вам придется создавать сериализуемый класс, похожий на QVariantMap, который будет использоваться вместо структур для всех сериализуемых объектов.

Другими словами, если вы используете определенный тип для всех сериализуемых объектов (или записываете процедуры сериализации самостоятельно для каждого класса), это можно сделать. Однако, если вы хотите автоматически сериализовать все возможные классы, вам следует забыть об этом. Если эта функция важна для вас, попробуйте другой язык.

Ответ 4

Если у кого-то еще есть эта потребность (у меня есть), я сам написал библиотеку для решения этой проблемы. См. здесь. Это не полностью автоматизировано, так как вы должны описывать все поля в своих классах, но это так близко, как мы можем получить, поскольку С++ не имеет отражения.

Ответ 5

Используя quicktype, вы можете генерировать сериализаторы и десериализаторы С++ из данных образца JSON.

Например, учитывая образец JSON:

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

quicktype генерирует:

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

Ответ 6

Еще не упомянуто, хотя это было первое в моем результатах поиска: https://github.com/nlohmann/json

Перки перечислены:

  • Интуитивный синтаксис (отлично смотрится!)
  • Один заголовочный файл для включения, ничего другого
  • Смешно протестировано

Кроме того, он под лицензией MIT.

Я буду честен: мне еще нужно его использовать, но, используя некоторый опыт, у меня есть умение определять, когда я сталкиваюсь с действительно хорошо сделанной библиотекой c++.

Ответ 7

Библиотека только для заголовков jsoncons C++ также поддерживает преобразование между текстом JSON и объектами C++. Декодирование и кодирование определены для всех классов C++, для которых определены json_type_traits. Контейнеры стандартной библиотеки уже поддерживаются, и 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
        }
    ]
}

Ответ 8

Использование 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);

Никакое другое кодирование не требуется.

Вы можете найти полные примеры:

Ответ 9

Попробуйте https://github.com/xyz347/x2struct. Он поддерживает сериализацию и десериализацию между json/xml/bson и структурой C++. Это пример:

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

вывод:

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

все, что вам нужно сделать, это

  1. включите x2struct.hpp в ваш исходный файл
  2. добавить макрос XTOSTRUCT в конце определения структуры.
  3. вызовите x2struct :: X :: loadjson для десериализации строки json в структуру C++ или вызовите x2struct :: X :: tojson для сериализации структуры C++ в json

такой же как xml и bson support vector/set/map

Ответ 10

Попробуйте 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 );
  }
};

Это будет возможность преобразования в JSON и из него:

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

Ответ 11

Если вы находитесь на платформе linux, вы можете напрямую использовать библиотеку json.h для сериализации. Вот пример кода, с которым я столкнулся:)

**

Json Serializer

**