Вопрос или проблема
Предположим, у нас есть API, который возвращает ответ в формате json в виде
[1,2,3]
Согласно спецификации json, это допустимый Json. Если я попытаюсь десериализовать его в std::vector<int>
с помощью:
#include <cassert>
#include <iostream>
#include <sstream>
#include <cereal/archives/json.hpp>
#include <cereal/types/vector.hpp>
int main() {
std::stringstream ss("[1,2,3]");
cereal::JSONInputArchive ar(ss);
std::vector<int> vec;
ar(vec); // src/main.cpp:42 <-- здесь
assert(vec.size() == 3);
std::cout << vec[0] << ", " << vec[1] << ", " << vec[2] << std::endl;
return 0;
}
Я получаю исключение:
terminate called after throwing an instance of 'cereal::RapidJSONException'
what(): rapidjson internal assertion failure: IsObject()
Aborted (core dumped)
с трассировкой стека, указывающей на:
#0 0x00007ffff7e294a1 in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6
#1 0x000055555555a703 in rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> >::MemberBegin (this=<optimized out>)
at /usr/local/include/cereal/external/rapidjson/document.h:1168
#2 cereal::JSONInputArchive::startNode (this=0x7fffffffdc80)
at /usr/local/include/cereal/archives/json.hpp:610
#3 0x00005555555567c8 in cereal::prologue<std::vector<int, std::allocator<int> >, (cereal::traits::detail::sfinae)0> (ar=...) at /usr/local/include/cereal/archives/json.hpp:851
#4 cereal::InputArchive<cereal::JSONInputArchive, 0u>::process<std::vector<int, std::allocator<int> >&> (head=std::vector длиной 0, емкость 0, this=0x7fffffffdc80)
at /usr/local/include/cereal/cereal.hpp:853
#5 cereal::InputArchive<cereal::JSONInputArchive, 0u>::operator()<std::vector<int, std::allocator<int> >&> (this=0x7fffffffdc80) at /usr/local/include/cereal/cereal.hpp:730
#6 main () at /home/pptaszni/workspace/FooBar/src/main.cpp:42
Обратите внимание, что аналогичный код, где json имеет пару ключ/значение, работает нормально:
int main() {
std::stringstream ss{"{\"value0\":[1,2,3]}"};
cereal::JSONInputArchive ar(ss);
std::vector<int> vec;
ar(cereal::make_nvp( "value0", vec));
std::cout << vec.size() << std::endl;
std::cout << vec[0] << ", " << vec[1] << ", " << vec[2] << std::endl;
return 0;
}
Как десериализовать строку Json в виде [1,2,3]
с помощью Cereal?
Конструкция
cereal::JSONInputArchive ar(ss);
оказалась успешной, что означает, что основной rapidjson::Document
не имеет проблем с таким json. Это можно подтвердить следующим кодом:
int main() {
std::stringstream ss{"[1,2,3]"};
rapidjson::IStreamWrapper isw(ss);
rapidjson::Document doc;
doc.ParseStream<>(isw);
assert(doc.IsArray());
for (size_t i = 0; i < doc.Size(); i++) {
std::cout << doc[i].GetInt() << std::endl;
}
}
Трассировка стека указывает на метод class InputArchive
:
//! Сериализует данные после вызова пролога, затем вызывает эпилог
template <class T> inline
void process( T && head )
{
prologue( *self, head );
self->processImpl( head );
epilogue( *self, head );
}
который выбирает перегрузку prologue( JSONOutputArchive & ar, T const & )
для “всех других типов для JSON архивов”, что, по-видимому, включает в себя std::vector<int>
. Затем мы попадаем в проблемный код:
if(itsIteratorStack.back().value().IsArray())
itsIteratorStack.emplace_back(itsIteratorStack.back().value().Begin(), itsIteratorStack.back().value().End());
else
itsIteratorStack.emplace_back(itsIteratorStack.back().value().MemberBegin(), itsIteratorStack.back().value().MemberEnd());
который выбрасывает исключение, потому что наш itsIteratorStack.back().value()
является ни Array
, ни Object
. Это Number
.
Возможным обходным решением этой проблемы является предоставление пустых реализаций функций prologue
и epilogue
, которые являются лучшими кандидатами для перегрузки, чем шаблоны в заголовке archives/json.hpp
:
namespace cereal {
void prologue(cereal::JSONInputArchive&, const std::vector<int>&){}
void epilogue(cereal::JSONInputArchive&, const std::vector<int>&){}
}
int main() {
std::stringstream ss("[1,2,3]");
cereal::JSONInputArchive ar(ss);
std::vector<int> vec;
ar(vec);
assert(vec.size() == 3);
std::cout << vec[0] << ", " << vec[1] << ", " << vec[2] << std::endl;
return 0;
}
Обмен знаниями вдохновлён этим закрытым вопросом и ответом на проблему с OutputArchive.
Ответ или решение
Для десериализации JSON-массива, представленного в следующем виде: [1,2,3]
, с использованием библиотеки Cereal, возникает проблема, так как библиотека ожидает, что данные будут обернуты в объект с парой ключ/значение. В данной статье мы подробно разберем, как обойти это ограничение и успешно десериализовать массив в std::vector<int>
.
Проблема
При попытке десериализации массива std::vector<int>
с помощью класса cereal::JSONInputArchive
, возникает исключение cereal::RapidJSONException
, связанное с тем, что библиотека RapidJSON пытается обработать входные данные как объект, тогда как в действительности это массив. В случае, если вы предоставите JSON-строку в формате { "value": [1, 2, 3] }
, десериализация проходит успешно, так как RapidJSON ожидает объект и корректно обрабатывает его.
Решение
Одним из способов обойти это ограничение является переопределение поведения сериализации и десериализации для std::vector<int>
, создавая пустые реализации функций prologue
и epilogue
. Это позволит Cereal корректно обрабатывать JSON-массив напрямую.
Пошаговая реализация:
- Определение пустых функций
prologue
иepilogue
: Вам нужно переопределить эти функции в пространстве именcereal
, чтобы они не выполняли никаких операций и не вызывали ошибку.
#include <cassert>
#include <iostream>
#include <sstream>
#include <cereal/archives/json.hpp>
#include <cereal/types/vector.hpp>
namespace cereal {
void prologue(cereal::JSONInputArchive&, const std::vector<int>&){}
void epilogue(cereal::JSONInputArchive&, const std::vector<int>&){}
}
- Десериализация массива: Теперь вы можете написать основной код для десериализации вашего JSON-массива.
int main() {
std::stringstream ss("[1,2,3]");
cereal::JSONInputArchive ar(ss);
std::vector<int> vec;
ar(vec); // Десериализация успешна
assert(vec.size() == 3); // Проверка корректности изменений
std::cout << vec[0] << ", " << vec[1] << ", " << vec[2] << std::endl; // Выводим элементы вектора
return 0;
}
Заключение
Используя предложенные изменения, теперь вы можете без проблем десериализовать JSON-массивы в std::vector<int>
с помощью библиотеки Cereal. Это решение позволяет избежать сложностей, связанных с ожиданием наличия ключ/значение в JSON-структуре и расширяет возможности работы с данными, получаемыми из API.
Библиотека Cereal в сочетании с такими изменениями открывает новые горизонты для разработки, позволяя обрабатывать чистые данные JSON, как это часто востребовано в современных приложениях и сервисах.
Если у вас будут возникать дополнительные вопросы по работе с Cereal или другими аспектами C++, не стесняйтесь обращаться за помощью.