Показаны сообщения с ярлыком json_dto. Показать все сообщения
Показаны сообщения с ярлыком json_dto. Показать все сообщения

вторник, 23 июля 2024 г.

[prog.c++] Как в json_dto можно сделать так, чтобы элемент в JSON был либо единичным объектом, либо вектором объектов?

В проекте для текущего заказчика возникла потребность сделать так, чтобы элемент в JSON-е мог быть либо одиночным объектом:

{
  "message": {
    ...,
    "extension": { ... }
  }
}

либо же вектором объектов:

{
  "message": {
    ...,
    "extension": [
      { ... },
      { ... },
      { ... }
    ]
  }
}

Сделать это оказалось не сложно, т.к. в проекте для работы с JSON используется весьма низкоуровневый инструмент, в котором, как в RapidJSON, приходится вручную разбираться с типами элементов. Буквально что-то вроде item.SetObject("extension", value) или item.SetArray("extension").PushBack(...).

Но мне стало интересно, а можно ли будет сделать что-то подобное с помощью нашей разработки json_dto.

Мы делали json_dto как раз для того, чтобы не нужно было вручную возиться с SetObject и SetArray, чтобы сериализатор/десериализатор сам разбирался с этой скучной рутиной. А такая высокоуровневость временами выходит боком, когда пытаешься сделать что-то, что изначальным дизайном библиотеки не предусматривалось. Так что я подумал, что это своего рода "вызов" и попробовал сделать такой трюк средствами json_dto.

Выполнить этот трюк удалось без каких-либо проблем и без доработок json_dto, хотя и несколько многословно: достаточно было определить кастомный Reader_Writer.

Есть у нас в json_dto такое понятие, как кастомный Reader_Writer -- это объект с двумя методами, read и write. Именно этот объект берет на себя ответственность за то, чтобы прочитать/записать значение поля объекта посредством RapidJSON. По умолчанию для это делает сам json_dto, но когда нужно что-то кастомизировать, то пользователь может реализовать собственный Reader_Writer и приказать json_dto использовать именно этот Reader_Writer.

Как же это все выглядит?

среда, 21 октября 2020 г.

[prog.c++] json_dto-0.2.11 released

json_dto is a thin wrapper around RapidJSON library we wrote several years ago. It's surprising for us that this small bicycle is used by someone outside our team. Sometimes users show us use-cases we just weren't thinking about. And we add new features to json_dto to fulfill expectations.

The last updates to json_dto add a possibility to customize formatting of (de)serialized values by specifying custom Reader_Writers. For example let's imagine that we have a struct with a `std::map<std::uint32_t, int>` field:

struct example_data {
   std::map<std::uint32_t, int> m_weights;
   ...
};

It's impossible to write a simple (de)serialization code for example_data in the form:

struct example_data {
   std::map<std::uint32_t, int> m_weights;
   ...

   template<typename Io> void json_io(Io & io) {
      io & json_dto::mandatory("weights", m_weights)
         ...
         ;
   }
};

because RapidJSON expects string values as keys, but json_dto (de)serializes `std::uint32_t` as integers. And the naive implementation of `json_io` shown above leads to run-time error during serialization.

Now json_dto allows to use custom formatters for fields to be (de)serialized:

struct simple_reader_writer {
   // Those methods will be used for (de)serializing of map's keys.
   void read(
      json_dto::mutable_map_key_t<std::uint32_t> & key,
      const rapidjson::Value & from) {
      ... // Deserializing key from a string.
   }
   void write(
      const json_dto::mutable_map_key_t<std::uint32_t> & key,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      ... // Serializing key as a string.
   }

   // Those methods will be used for (de)serializing of map's values.
   void read(
      int & v,
      const rapidjson::Value & from) {
      json_dto::read_json_value(v, from); // Just reuse json_dto.
   }
   void write(
      const int & v,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      json_dto::write_json_value(v, to, allocator); // Just reuse json_dto.
   }
};

struct example_data {
   std::map<std::uint32_t, int> m_weights;
   ...

   template<typename Io> void json_io(Io & io) {
      io & json_dto::mandatory(
              // Explicit specification of custom Reader_Writer for that field.
              // The usage of apply_to_content_t tells that custom formatter
              // should be applied to every item of the container.
              json_dto::apply_to_content_t<simple_reader_writer>{},
              "weights", m_weights)
         ...
         ;
   }
};

So now keys of `example_data::m_weights` will be (de)serialized as strings.

But custom Reader_Writers allow to go further. Let's imaging that `example_data` contains yet another `std::map<uint32_t, int>`:

struct example_data {
   std::map<std::uint32_t, int> m_weights;
   std::map<std::uint32_t, int> m_colors;
   ...
};

where keys of `example_data::m_colors` should be represented in the form `#xxxxxx`, where `xxxxxx` is hexadecimal representation.

We can create another custom Reader_Writer:

struct color_hex_reader_writer {
   // Those methods will be used for (de)serializing of map's keys.
   void read(
      json_dto::mutable_map_key_t<std::uint32_t> & key,
      const rapidjson::Value & from) {
      ... // Deserializing key from a string.
   }
   void write(
      const json_dto::mutable_map_key_t<std::uint32_t> & key,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      ... // Serializing key as a string.
   }

   // Those methods will be used for (de)serializing of map's values.
   void read(
      int & v,
      const rapidjson::Value & from) {
      json_dto::read_json_value(v, from); // Just reuse json_dto.
   }
   void write(
      const int & v,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      json_dto::write_json_value(v, to, allocator); // Just reuse json_dto.
   }
};

and use it for (de)serialization of `example_data::m_colors`:

struct example_data {
   std::map<std::uint32_t, int> m_weights;
   std::map<std::uint32_t, int> m_colors;
   ...

   template<typename Io> void json_io(Io & io) {
      io & json_dto::mandatory(
              json_dto::apply_to_content_t<simple_reader_writer>{},
              "weights", m_weights)
         & json_dto::mandatory(
              json_dto::apply_to_content_t<color_hex_reader_writer>{},
              "colors", m_colors)
         ...
         ;
   }
};

The full working example can be seen here.

PS. I don't like to answer questions like "Is json_dto better than nlohmann::json, cereal or any other similar library?" We started to use RapidJSON several years ago and we didn't know about many of the JSON-libraries well known today. At some time we decided to simplify our marriage with RapidJSON and wrote json_dto. Since then it's easier for us to continue to use json_dto than to switch to any other library. So if you are happy with nlohmann::json there is no need to see for something else. But if you have to stay out of nlohmann::json for any reason there is json_dto ;)

PPS. We love reinventing bikes and we know how to do it: RESTinio and SObjectizer are also out of our garage.

четверг, 28 марта 2019 г.

[prog.c++] Как работать с STL-ными контейнерами без include-ов описаний этих контейнеров

Давеча мы обновили свою тоненькую обертку над RapidJSON. Очередным добавлением в json_dto стала поддержка STL-ных контейнеров (std::deque, std::list, std::forward_list, std::set, std::multiset, std::unordered_set, std::unordered_multiset, std::map, std::multimap, std::unordered_map, std::unordered_multimap). И добавляя эту поддержку нам нужно было решить, как это сделать с минимальными накладными расходами.

Идти по простому пути, т.е. делать в json_dto какой-нибудь #include <set>, а потом перегружать часть внутренних функций json_dto для std::set и std::multiset, очень не хотелось. Во-первых, это сильно увеличивает объем нашей собственной работы. Во-вторых, это увеличивает время компиляции проектов, в которых json_dto используется.

Поэтому мы пошли по пути современного C++: т.е. шаблонная магия и SFINAE во все поля :) Под катом несколько слов об этом для тех, кому тема современного C++ интересна.

пятница, 10 августа 2018 г.

[prog.c++] json_dto-0.2.6: небольшое, но довольно важное обновление

Мы сегодня зафиксировали свежую версию 0.2.6 своей легковесной библиотеки json_dto. Там одна новая фича, но, думается, достаточно важная для определенных сценариев работы с JSON-документами.

Дело в том, что мы json_dto делали для себя, для ситуаций, когда JSON-документ хранил внутри единственный сериализованный объект. Т.е. мы имели дело с JSON-документами вида: {"field":...}. А вот на днях один из пользователей json_dto обратил наше внимание на ситуацию, когда JSON-документ представляет из себя вектор из объектов. Т.е. что-то вроде: [{"field":...}, {"field":...}, {"field":...}]. В такой ситуации json_dto оказывался бесполезным.

Этот же пользователь поделился своими соображениями о том, как он бы хотел использовать json_dto в такой ситуации. Он бы хотел иметь возможность написать json_dto::from_json<std::vector<Data>>(json_document) и чтобы результатом был экземпляр std::vector с объектами типа Data внутри.

Собственно, именно это мы и добавили в версию 0.2.6. Теперь можно писать вот так:

#include <json_dto/pub.hpp>

#include <iostream>
#include <algorithm>

struct data_t {
   std::string m_key;
   int m_value;

   template<typename Json_Io>
   void json_io(Json_Io & io) {
      io & json_dto::mandatory("key", m_key)
         & json_dto::mandatory("value", m_value);
   }
};

int main() {
   const std::string json_data{
      R"JSON(
         [{"key":"first", "value":32},
          {"key":"second", "value":15},
          {"key":"third", "value":80}]
      )JSON"
   };

   auto data = json_dto::from_json< std::vector<data_t> >(json_data);
   std::sort(data.begin(), data.end(),
      [](const auto & a, const auto & b) { return a.m_value < b.m_value; });

   std::cout << "Sorted data: " << json_dto::to_json(data) << std::endl;
}

И получать при этом вполне ожидаемый результат:

Sorted data: [{"key":"second","value":15},{"key":"first","value":32},{"key":"third","value":80}]

В общем, кто хочет легко и удобно работать с JSON-ом, то не стесняемся, берем json_dto, пользуемся, делимся впечатлениями. Взять json_dto можно на BitBucket-е или на GitHub-e (со временем версия 0.2.6 подтянется и на vcpkg).

Кстати говоря, с теми, кому json_dto интересен, можно было бы обсудить вот еще что: в принципе, в описанном выше сценарии вовсе необязательно работать только с std::vector<Data>. Запросто может быть использован и std::deque<Data>, std::list<Data> или даже std::array<Data, N>. Если кому-то хотелось бы видеть в json_dto поддержку и других STL-левских контейнеров, а не только std::vector, то дайте нам знать. Постараемся эту поддержку добавить. Ну а если это никому не интересно, то пусть все остается как есть.

среда, 2 мая 2018 г.

[prog.c++] Небольшое обновление json_dto-0.2

Мы выкатили небольшое обновление своей легковесной библиотеки для упрощения работы с JSON и RapidJSON в C++ -- json_dto-0.2.4.

В версии 0.2.4 мы отвязали json_dto от особенностей работы RapidJSON с std::string и зависимости от RAPIDJSON_HAS_STDSTRING. Теперь json_dto нормально работает с std::string даже если RAPIDJSON_HAS_STDSTRING не определен.

Еще имеет смысл отметить добавление поддержки CMake для json_dto. А так же создание зеркала json_dto на github (думаю, что тем, кто может стягивать зависимости только с github-а, это должно понравится).

В общем, еще один наш инструмент стал еще лучше. Кто еще не пробовал -- рекомендуем ;)

пятница, 8 сентября 2017 г.

[prog.c++] json_dto-0.2

Мы сегодня обновили свою небольшую библиотеку json_dto, которая служит хоть и тонкой, но очень полезной оберткой над такой замечательной штукой, как rapidjson. Мы сделали json_dto где-то года полтора назад, взяв идеи из Boost.Serialization, для уменьшения объема писанины при работе с JSON в C++. Например, json_dto позволяет писать вот так:

class User {
public :
   /* ... */

   template<typename JSON_IO>
   void json_io(JSON_IO & io) {
      io & json_dto::mandatory("id", _id)
         & json_dto::mandatory("name", _name)
         & json_dto::mandatory("birthday", _birthday)
         & json_dto::mandatory("phone", _phone);
   }
};

вместо того, чтобы писать вот такие простыни (взято отсюда как пример несложного кода):