programing

C++ JSON 시리얼화

newstyles 2023. 3. 20. 21:43

C++ JSON 시리얼화

개체를 JSON으로 가능한 한 자동으로 직렬화 및 직렬화 해제하는 방법을 원합니다.

시리얼라이즈:이상적인 방법은 인스턴스 JSONserialize()를 호출하면 개체의 모든 공용 속성을 가진 문자열을 반환하는 것입니다."name_of_property": "value". 하기 위해 각 JSON ToString 이러한 원시적인 값은 간단합니다.개체에서는 모든 퍼블릭속성을 재귀적으로 시리얼화하기 위해 각 JSON Serialize() 또는 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++에는 반사가 없습니다.맞아요. 하지만 컴파일러가 당신에게 필요한 메타데이터를 제공할 수 없다면 당신이 직접 제공할 수 있어요.

먼저 속성 구조를 만듭니다.

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 폴드 은 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에서는 작동하지 않습니다.

그러기 위해서는 존재하지 않는 C/C++에 반영이 필요합니다.클래스 구조(구성원, 상속된 기본 클래스)를 설명하는 메타 데이터가 있어야 합니다.현재 C/C++ 컴파일러는 빌트 바이너리에서 이 정보를 자동으로 제공하지 않습니다.

저도 같은 생각을 하고 있었고, 이 정보를 얻기 위해 GCC XML 프로젝트를 사용했습니다.클래스 구조를 설명하는 XML 데이터를 출력합니다.프로젝트를 작성했습니다.이 페이지에서 몇 가지 중요한 점을 설명하겠습니다.

시리얼화는 간단하지만 할당된 버퍼로 재생되는 복잡한 데이터 구조 구현(std::string, std::map 등)에 대처해야 합니다.디시리얼라이제이션은 더 복잡하기 때문에 오브젝트의 모든 멤버와 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 출력등), 프로젝트에 통합하는 것은 그다지 간단하지 않습니다.

그렇게 쉬운 게 있나요?감사:)

C++는 컴파일된 코드에 클래스 멤버 이름을 저장하지 않으며, 실행 시 어떤 멤버(변수/메서드) 클래스가 포함되어 있는지 검색할 수 없습니다.즉, 구조체의 구성원을 통해 반복할 수 없습니다.이러한 메커니즘이 없기 때문에 모든 개체에 대해 "JSONserialize"를 자동으로 생성할 수 없습니다.

그러나 임의의 json 라이브러리를 사용하여 개체를 직렬화할 수 있지만 모든 클래스에 대해 직렬화/직렬화 코드를 직접 작성해야 합니다.또는 QVariantMap과 유사한 직렬화 가능 클래스를 만들어야 합니다. 이 클래스는 모든 직렬화 가능한 개체에 대한 구조체 대신 사용됩니다.

즉, 모든 직렬화 가능한 개체에 대해 특정 유형을 사용하는 것이 좋거나 모든 클래스에 대해 직접 직렬화 루틴을 작성하는 것이 좋습니다.그러나 가능한 모든 클래스를 자동으로 직렬화하려면 이 작업을 생략해야 합니다.이 기능이 중요한 경우 다른 언어를 사용해 보십시오.

Quicktype을 사용하면 JSON 샘플 데이터에서 C++ 시리얼라이저 및 역직렬라이저를 생성할 수 있습니다.

예를 들어 샘플 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);

만약 누군가가 아직 이러한 요구를 가지고 있다면, 나는 이 문제에 대처하기 위해 직접 도서관을 썼다.여기 보세요.수업의 모든 필드를 설명해야 한다는 점에서 완전히 자동적인 것은 아니지만, C++에는 반영이 부족하기 때문에 얻을 수 있는 것은 거의 없습니다.

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!" }

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_ALL_CTOR_GETTER_TRAITS(ns::hiking_reputon, rater, assertion, rated, rating)
JSONCONS_ALL_CTOR_GETTER_TRAITS(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
        }
    ]
}

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"

나는 그것이 당신의 원본에 꽤 가깝다고 생각합니다.
약간의 설정이 있습니다.클래스가 직렬화 가능함을 선언해야 합니다.

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

다른 코딩은 필요하지 않습니다.

완전한 예는 다음과 같습니다.

아직 언급되지 않았습니다만, 제 검색 결과로는 처음이었습니다.https://github.com/nlohmann/json

나열된 특전:

  • 직관적인 구문(외관상 보기 좋음)
  • 포함할 단일 헤더 파일, 다른 파일 없음
  • 불합리한 테스트

또한 MIT 사용허가를 받고 있습니다.

솔직히 말하면:아직 사용해 본 적은 없지만, 어느 정도 경험을 통해 잘 만들어진 c++ 도서관을 언제 발견할지 결정하는 재주가 있습니다.

이것은 Qt: https://github.com/carlonluca/lqobjectserializer을 사용한 시도입니다.JSON은 다음과 같습니다.

{"menu": {
    "header": "SVG Viewer",
    "items": [
        {"id": "Open"},
        {"id": "OpenNew", "label": "Open New"},
        null,
        {"id": "ZoomIn", "label": "Zoom In"},
        {"id": "ZoomOut", "label": "Zoom Out"},
        {"id": "OriginalView", "label": "Original View"},
        null,
        {"id": "Quality"},
        {"id": "Pause"},
        {"id": "Mute"},
        null,
        {"id": "Find", "label": "Find..."},
        {"id": "FindAgain", "label": "Find Again"},
        {"id": "Copy"},
        {"id": "CopyAgain", "label": "Copy Again"},
        {"id": "CopySVG", "label": "Copy SVG"},
        {"id": "ViewSVG", "label": "View SVG"},
        {"id": "ViewSource", "label": "View Source"},
        {"id": "SaveAs", "label": "Save As"},
        null,
        {"id": "Help"},
        {"id": "About", "label": "About Adobe CVG Viewer..."}
    ]
}}

는 다음과 같은 클래스를 선언함으로써 역직렬화할 수 있습니다.

L_BEGIN_CLASS(Item)
L_RW_PROP(QString, id, setId, QString())
L_RW_PROP(QString, label, setLabel, QString())
L_END_CLASS

L_BEGIN_CLASS(Menu)
L_RW_PROP(QString, header, setHeader)
L_RW_PROP_ARRAY_WITH_ADDER(Item*, items, setItems)
L_END_CLASS

L_BEGIN_CLASS(MenuRoot)
L_RW_PROP(Menu*, menu, setMenu, nullptr)
L_END_CLASS

및 쓰기:

LDeserializer<MenuRoot> deserializer;
QScopedPointer<MenuRoot> g(deserializer.deserialize(jsonString));

메타 객체에 대한 매핑도 한 번 주입해야 합니다.

QHash<QString, QMetaObject> factory {
    { QSL("Item*"), Item::staticMetaObject },
    { QSL("Menu*"), Menu::staticMetaObject }
};

이걸 피할 방법을 찾고 있어요

언급URL : https://stackoverflow.com/questions/17549906/c-json-serialization