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
sizestate, but I don't see how it can be avoided either. \$\endgroup\$