3
\$\begingroup\$

Experimenting with using std::format.

I have some code that will serialize C++ structures into Json/Bson/Yaml. But it is pretty verbose. So I was trying to experiment with std::format and defining a user formatter.

Wrapping an object in TA::Ser{} allows you to serialize it. The default behavior of {} is to serialize to JSON in a single line. But it can be customized with:

{:<Type>?<Form>?<IndentSize>?}

Type: Optional, Default Json
   J    => Json
   B    => Bson
   Y    => Yaml

Form: Optional (Does not apply to Bson), Default Stream
   -    => Stream (Json/Yaml on a single line)
   *    => Config (Json/Yaml pretty print)

IndentSize: Optional (Does not apply to Bson or Stream output), Default 4
   int: => Tab size

Test Harness:

  #include "ThorSerialize/Format.h"
  #include "ThorSerialize/Traits.h"
  #include "ThorSerialize/JsonThor.h"
  #include <print>
  #include <iostream>

  struct Data
  {
      std::string             name;
      int                     age;
  };

  ThorsAnvil_MakeTrait(Data, name, age);

  int main()
  {
      using ThorsAnvil::Serialize::jsonExporter;
      using ThorsAnvil::Serialize::PrinterConfig;
      using ThorsAnvil::Serialize::OutputType;

      Data data{"Loki", 45};

      std::cout << "Val >" << jsonExporter(data, PrinterConfig{OutputType::Stream}) << "<\n";
      std::println("Val >{:J-}<", TA::Ser{data});
      std::println("Val >{}<", TA::Ser{data});
      std::cout << "Val >" << jsonExporter(data) << "<\n";
      std::println("Val >{:J*}<", TA::Ser{data});
  }

The output:

Val >{"name":"Loki","age":45}<
Val >{"name":"Loki","age":45}<
Val >{"name":"Loki","age":45}<
Val >
{
    "name": "Loki",
    "age": 45
}<
Val >
{
    "name": "Loki",
    "age": 45
}<

Then ThorSerialize/Format.h

  #ifndef THORSANVIL_SERIALIZE_FORMAT_H
  #define THORSANVIL_SERIALIZE_FORMAT_H

  #include <sstream>
  #include <format>
  #include <string>

  #include "ThorSerialize/JsonThor.h"
  #include "ThorSerialize/BsonThor.h"
  #include "ThorSerialize/YamlThor.h"

  // The wrapper class we specialize for printing.
  namespace TA
  {
      template<typename T>
      struct Ser
      {
          T const& value;
          Ser(T const& value)
              : value(value)
          {}
          Ser(Ser const&)         = default;
          Ser(Ser&&)              = default;
      };
  };

  /*
   * Specialization of std::formatter<> for TA::Ser<T>
   * This is used by std::format etc.
   */
  template<typename T>
  struct std::formatter<TA::Ser<T>>
  {
      enum Format {Json, Bson, Yaml};
      Format                              form = Json;
      ThorsAnvil::Serialize::OutputType   type = ThorsAnvil::Serialize::OutputType::Stream;
      std::size_t                         size = 4;

      // std::isdigit() is not constexpr so does not work
      // Had to write this method
      constexpr bool isDigit(char val) {
          return val >= '0' && val <= '9';
      }
      // parse the inside of the '{}' to extract formatting info
      constexpr auto parse(std::format_parse_context& ctx)
      {
          auto loop = ctx.begin();
          if (*loop != '}') {
              switch (*loop) {
                  case 'J':   form = Json; ++loop;    break;
                  case 'B':   form = Bson; ++loop;    break;
                  case 'Y':   form = Yaml; ++loop;    break;
                  default:
              }
          }
          if (*loop != '}') {
              switch (*loop) {
                  case '-':   type = ThorsAnvil::Serialize::OutputType::Stream;   ++loop; break;
                  case '*':   type = ThorsAnvil::Serialize::OutputType::Config;   ++loop; break;
                  default:
              }
          }
          if (isDigit(*loop)) {
              size = 0;
              for (;isDigit(*loop); ++loop) {
                  size = size * 10 + (*loop - '0');
              }
          }
          return loop;
      }

      // Serialize obj.value into a string
      auto format(const TA::Ser<T>& obj, std::format_context& ctx) const
      {
          ThorsAnvil::Serialize::PrinterConfig config{type, size};

          std::stringstream ser;
          switch (form) {
              case Json:  ser << ThorsAnvil::Serialize::jsonExporter(obj.value, config);break;
              case Bson:  ser << ThorsAnvil::Serialize::bsonExporter(obj.value, config);break;
              case Yaml:  ser << ThorsAnvil::Serialize::yamlExporter(obj.value, config);break;
          }

          std::string const& out = ser.str();
          return std::copy(std::begin(out), std::end(out), ctx.out());
      }
  };

  #endif
\$\endgroup\$
2
  • \$\begingroup\$ Ew, I don't like that size state, but I don't see how it can be avoided either. \$\endgroup\$ Commented 17 hours ago
  • \$\begingroup\$ @MaartenBodewes What's the issue with the size state? \$\endgroup\$ Commented 16 hours ago

1 Answer 1

3
\$\begingroup\$

It looks quite nice, but there are a few thing to consider:

  1. For pretty-printing, it might be useful to allow requesting an additional static indentation. Who knows what will surround it?

  2. You might not get a std::format_context, but some other std::basic_format_context<OutputIt, char>. Allowed and should be accommodated.

  3. Better do return ctx.advance_to(std::copy([...])), just in case the context doesn't use something like a backinserter iterator.

\$\endgroup\$
3
  • \$\begingroup\$ I like the idea of adding extra indenting. So I need to templitize to the format() function to allow for alternative context types. Do you have any references that I can read (googling was not very helpful). \$\endgroup\$ Commented 16 hours ago
  • \$\begingroup\$ Mostly, I looked up what cppreference had about the details (quite sketchy), and whatever my google-fu found, like this answer from Barry. \$\endgroup\$ Commented 14 hours ago
  • \$\begingroup\$ OK. The cppreference page looks good: en.cppreference.com/w/cpp/utility/format/formatter.html \$\endgroup\$ Commented 12 hours ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.