Есть ли способ указать более простую JSON (де-) сериализацию для std::map, используя Cereal / C++?

Проект, над которым я работаю, представляет собой приложение C++, которое управляет большим количеством пользовательских аппаратных устройств. Приложение имеет интерфейс сокета / порта для клиента (например, графический интерфейс). У каждого типа устройства есть своя четко определенная схема JSON, и мы можем сериализовать их с помощью Cereal.

Но приложению также необходимо проанализировать входящие JSON-запросы от клиента. Одна часть запроса задает параметры фильтра устройства, примерно аналогично предложению SQL WHERE, в котором все выражения объединены в AND. Например:

"filter": { "type": "sensor", "status": "critical" }

Это указывает на то, что клиент хочет выполнить операцию на каждом "сенсорном" устройстве с "критическим" состоянием. На первый взгляд казалось, что реализация C++ для параметров фильтра будет std::map. Но когда мы экспериментировали с использованием Cereal для десериализации объекта, это не удалось. И когда мы сериализуем жестко закодированную карту фильтра, это выглядит так:

"filter": [
   { "key": "type", "value": "sensor" },
   { "key": "status", "value": "critical" }
]

Теперь я могу понять, почему Cereal поддерживает этот вид многословной сериализации карты. В конце концов, ключ карты может быть нестроковым типом. Но в этом случае ключ является строкой.

Я не очень хочу переписывать нашу спецификацию интерфейса и заставлять наших клиентов генерировать явно не-идиоматический JSON просто для удовлетворения Cereal. Я новичок в Cereal, и мы застряли в этом вопросе. Есть ли способ сказать Cereal для анализа этого фильтра как std::map? Или, может быть, я спрашиваю это неправильно. Есть ли какой-нибудь другой контейнер stl, в который мы должны десериализоваться?

1 ответ

Решение

Позвольте мне сначала рассказать, почему хлопья дают более многословный стиль, чем тот, который вы можете пожелать. cereal написан для работы с произвольными архивами сериализации и использует промежуточный подход для их удовлетворения. Представьте, что тип ключа является чем-то более сложным, чем строковый или арифметический тип - как мы можем сериализовать его простым "key" : "value" путь?

Также обратите внимание, что зерновые ожидают быть прародителем любых данных, которые они считывают.


Это, как говорится, то, что вы хотите, вполне возможно с хлопьями, но есть несколько препятствий:

Самым большим препятствием, которое необходимо преодолеть, является тот факт, что желаемый входной поток сериализует некоторое неизвестное количество пар имя-значение внутри объекта JSON, а не массива JSON. cereal был разработан для использования массивов JSON при работе с контейнерами, которые могут содержать переменное число элементов, поскольку это имело наибольшее значение с учетом используемого им базового анализатора rapidjson.

Во-вторых, зерновые в настоящее время не ожидают, что имя в паре имя-значение будет фактически загружено в память - они просто используют их в качестве организационного инструмента.


Таким образом, мы можем быстро разобраться, вот полностью работающее решение (может быть сделано более элегантно) для вашей проблемы с минимальными изменениями для зерновых (на самом деле используется изменение, которое запланировано для зерновых 1.1, текущая версия - 1.0):

Добавить эту функцию в JSONInputArchive:

//! Retrieves the current node name
/*! @return nullptr if no name exists */
const char * getNodeName() const
{
  return itsIteratorStack.back().name();
}

Затем вы можете написать специализацию сериализации для std::map (или неупорядоченный, в зависимости от того, что вы предпочитаете) для пары строк. Убедитесь, что поместили это в cereal пространство имен, чтобы его мог найти компилятор. Этот код должен существовать где-то в ваших собственных файлах:

namespace cereal
{
  //! Saving for std::map<std::string, std::string>
  template <class Archive, class C, class A> inline
  void save( Archive & ar, std::map<std::string, std::string, C, A> const & map )
  {
    for( const auto & i : map )
      ar( cereal::make_nvp( i.first, i.second ) );
  }

  //! Loading for std::map<std::string, std::string>
  template <class Archive, class C, class A> inline
  void load( Archive & ar, std::map<std::string, std::string, C, A> & map )
  {
    map.clear();

    auto hint = map.begin();
    while( true )
    {
      const auto namePtr = ar.getNodeName();

      if( !namePtr )
        break;

      std::string key = namePtr;
      std::string value; ar( value );
      hint = map.emplace_hint( hint, std::move( key ), std::move( value ) );
    }
  }
} // namespace cereal

Это не самое элегантное решение, но оно работает хорошо. Я оставил все шаблонно, но то, что я написал выше, будет работать только с архивами JSON с учетом внесенных изменений. Добавление аналога getNodeName() к XML-архиву, скорее всего, и там будет работать, но, очевидно, это не имеет смысла для двоичных архивов.

Чтобы сделать это чистым, вы хотели бы поставить enable_if вокруг этого для архивов это работает с. Вам также необходимо изменить архивы JSON в зерновых для работы с объектами JSON переменного размера. Чтобы получить представление о том, как это сделать, посмотрите, как злаки устанавливают состояние в архиве, когда получают SizeTag сериализовать. По сути, вам нужно заставить архив не открывать массив, а вместо этого открывать объект, а затем создавать свою собственную версию loadSize() что бы увидеть насколько велик объект (это было бы Member на быстром языке).


Чтобы увидеть вышеизложенное в действии, запустите этот код:

int main()
{
  std::stringstream ss;
  {
    cereal::JSONOutputArchive ar(ss);
    std::map<std::string, std::string> filter = {{"type", "sensor"}, {"status", "critical"}};

    ar( CEREAL_NVP(filter) );
  }

  std::cout << ss.str() << std::endl;

  {
    cereal::JSONInputArchive ar(ss);
    cereal::JSONOutputArchive ar2(std::cout);

    std::map<std::string, std::string> filter;

    ar( CEREAL_NVP(filter) );
    ar2( CEREAL_NVP(filter) );
  }

  std::cout << std::endl;
  return 0;
}

и вы получите:

{
    "filter": {
        "status": "critical",
        "type": "sensor"
    }
}
{
    "filter": {
        "status": "critical",
        "type": "sensor"
    }
}
Другие вопросы по тегам