LCOV - code coverage report
Current view: top level - spb/json - serialize.hpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 99.5 % 189 188
Test Date: 2026-05-30 19:41:37 Functions: 90.4 % 542 490

            Line data    Source code
       1              : /***************************************************************************\
       2              : * Name        : serialize library for json                                  *
       3              : * Description : all json serialization functions                            *
       4              : * Author      : antonin.kriz@gmail.com                                      *
       5              : * ------------------------------------------------------------------------- *
       6              : * This is free software; you can redistribute it and/or modify it under the *
       7              : * terms of the MIT license. A copy of the license can be found in the file  *
       8              : * "LICENSE" at the root of this distribution.                               *
       9              : \***************************************************************************/
      10              : 
      11              : #pragma once
      12              : 
      13              : #include "../concepts.h"
      14              : 
      15              : #include "../to_from_chars.h"
      16              : #include "base64.h"
      17              : #include "field.hpp"
      18              : #include "spb/json/deserialize.hpp"
      19              : #include "spb/utf8.h"
      20              : #include <algorithm>
      21              : #include <array>
      22              : #include <cctype>
      23              : #include <charconv>
      24              : #include <cinttypes>
      25              : #include <cstddef>
      26              : #include <cstring>
      27              : #include <map>
      28              : #include <memory>
      29              : #include <spb/io/io.hpp>
      30              : #include <stdexcept>
      31              : #include <string>
      32              : #include <string_view>
      33              : #include <sys/types.h>
      34              : #include <type_traits>
      35              : 
      36              : namespace spb::json::detail
      37              : {
      38              : struct ostream
      39              : {
      40              :   private:
      41              :     size_t bytes_written = 0;
      42              :     spb::io::writer on_write;
      43              : 
      44              :   public:
      45              :     //- flag if put ',' before value
      46              :     bool put_comma = false;
      47              : 
      48              :     /**
      49              :      * @brief Construct a new ostream object
      50              :      *
      51              :      * @param writer if null, stream will skip all writes but will still count number of written
      52              :      * chars
      53              :      */
      54         7251 :     explicit ostream(spb::io::writer writer) noexcept : on_write(writer)
      55              :     {
      56         7251 :     }
      57              : 
      58      8760015 :     void write(char c) noexcept
      59              :     {
      60      8760015 :         if (on_write)
      61      4379015 :             on_write(&c, sizeof(c));
      62              : 
      63      8760015 :         bytes_written += sizeof(c);
      64      8760015 :     }
      65              : 
      66           20 :     void write_unicode(uint32_t codepoint)
      67              :     {
      68           20 :         if (codepoint <= 0xffff)
      69              :         {
      70           15 :             char buffer[8] = {};
      71           15 :             auto size = snprintf(buffer, sizeof(buffer), "\\u%04" PRIx32, codepoint);
      72           15 :             return write(std::string_view(buffer, size));
      73              :         }
      74            5 :         if (codepoint <= 0x10FFFF)
      75              :         {
      76            5 :             codepoint -= 0x10000;
      77              : 
      78            5 :             auto high = static_cast<uint16_t>((codepoint >> 10) + 0xD800);
      79            5 :             auto low = static_cast<uint16_t>((codepoint & 0x3FF) + 0xDC00);
      80            5 :             char buffer[16] = {};
      81            5 :             auto size = snprintf(buffer, sizeof(buffer), "\\u%04" PRIx16 "\\u%04" PRIx16, high, low);
      82            5 :             return write(std::string_view(buffer, size));
      83              :         }
      84            0 :         throw std::invalid_argument("invalid utf8");
      85              :     }
      86              : 
      87         5805 :     void write(std::string_view str)
      88              :     {
      89         5805 :         if (on_write)
      90         2383 :             on_write(str.data(), str.size());
      91              : 
      92         5805 :         bytes_written += str.size();
      93         5805 :     }
      94              : 
      95         2363 :     void write_escaped(std::string_view str)
      96              :     {
      97         2363 :         if (!has_escape_chars(str))
      98              :         {
      99         2319 :             write(str);
     100         2319 :             return;
     101              :         }
     102              : 
     103              :         using namespace std::literals;
     104           44 :         uint32_t codepoint = 0;
     105           44 :         uint32_t state = spb::detail::utf8::ok;
     106           44 :         bool decoding_utf8 = false;
     107          285 :         for (uint8_t c : str)
     108              :         {
     109          241 :             if (decoding_utf8)
     110              :             {
     111           30 :                 if (spb::detail::utf8::decode_point(&state, &codepoint, c) == spb::detail::utf8::ok)
     112              :                 {
     113           15 :                     write_unicode(codepoint);
     114           15 :                     decoding_utf8 = false;
     115              :                 }
     116           30 :                 continue;
     117              :             }
     118          211 :             if (is_escape(c))
     119              :             {
     120          128 :                 switch (c)
     121              :                 {
     122           21 :                 case '"':
     123           21 :                     write(R"(\")"sv);
     124           21 :                     break;
     125           14 :                 case '\\':
     126           14 :                     write(R"(\\)"sv);
     127           14 :                     break;
     128            9 :                 case '\b':
     129            9 :                     write(R"(\b)"sv);
     130            9 :                     break;
     131            9 :                 case '\f':
     132            9 :                     write(R"(\f)"sv);
     133            9 :                     break;
     134           14 :                 case '\n':
     135           14 :                     write(R"(\n)"sv);
     136           14 :                     break;
     137           14 :                 case '\r':
     138           14 :                     write(R"(\r)"sv);
     139           14 :                     break;
     140           26 :                 case '\t':
     141           26 :                     write(R"(\t)"sv);
     142           26 :                     break;
     143           21 :                 default:
     144           21 :                     decoding_utf8 = true;
     145           21 :                     if (spb::detail::utf8::decode_point(&state, &codepoint, c) == spb::detail::utf8::ok)
     146              :                     {
     147            5 :                         write_unicode(codepoint);
     148            5 :                         decoding_utf8 = false;
     149              :                     }
     150              :                 }
     151              :             }
     152              :             else
     153              :             {
     154           83 :                 write(c);
     155              :             }
     156              :         }
     157           44 :         if (state != spb::detail::utf8::ok)
     158              :         {
     159            1 :             throw std::runtime_error("invalid utf8");
     160              :         }
     161              :     }
     162              : 
     163              :     void serialize(const auto &value, const field_attributes &field = {});
     164              :     void serialize(std::string_view value);
     165              : 
     166         4656 :     [[nodiscard]] auto size() const noexcept -> size_t
     167              :     {
     168         4656 :         return bytes_written;
     169              :     }
     170              : 
     171              :   private:
     172        11618 :     static auto is_escape(uint8_t c) -> bool
     173              :     {
     174              :         static constexpr std::string_view escape_chars = "\\\"\b\f\n\r\t<>";
     175        11618 :         return c <= 0x1f || c >= 0x7f || escape_chars.find(c) != std::string_view::npos;
     176              :     }
     177              : 
     178         2363 :     static auto has_escape_chars(std::string_view str) -> bool
     179              :     {
     180         2363 :         return std::any_of(str.begin(), str.end(), is_escape);
     181              :     }
     182              : };
     183              : 
     184              : using namespace std::literals;
     185              : 
     186         5118 : static inline void serialize_key(ostream &stream, std::string_view key)
     187              : {
     188         5118 :     if (stream.put_comma)
     189          690 :         stream.write(',');
     190              : 
     191         5118 :     stream.put_comma = true;
     192              : 
     193         5118 :     if (key.empty())
     194         3322 :         return;
     195              : 
     196         1796 :     stream.write('"');
     197         1796 :     stream.write_escaped(key);
     198         1796 :     stream.write(R"(":)"sv);
     199              : }
     200              : 
     201              : static inline void serialize(ostream &stream, const bool &value, const field_attributes &field);
     202              : static inline void serialize(ostream &stream, const spb::detail::proto_field_int_or_float auto &value,
     203              :                              const field_attributes &field);
     204              : static inline void serialize(ostream &stream, const spb::detail::proto_message auto &value,
     205              :                              const field_attributes &field);
     206              : static inline void serialize(ostream &stream, const spb::detail::proto_enum auto &value,
     207              :                              const field_attributes &field);
     208              : static inline void serialize(ostream &stream, const spb::detail::proto_field_string auto &value,
     209              :                              const field_attributes &field);
     210              : static inline void serialize(ostream &stream, const spb::detail::proto_field_bytes auto &value,
     211              :                              const field_attributes &field);
     212              : static inline void serialize(ostream &stream, const spb::detail::proto_label_repeated auto &value,
     213              :                              const field_attributes &field);
     214              : static inline void serialize(ostream &stream, const spb::detail::proto_label_repeated_fixed_size auto &value,
     215              :                              const field_attributes &field);
     216              : template <typename keyT, typename valueT>
     217              : static inline void serialize(ostream &stream, const std::map<keyT, valueT> &map,
     218              :                              const field_attributes &field);
     219              : 
     220              : static inline void serialize(ostream &stream, bool value);
     221              : static inline void serialize(ostream &stream, spb::detail::proto_field_int_or_float auto value);
     222              : static inline void serialize(ostream &stream, const spb::detail::proto_field_string auto &value);
     223              : 
     224           76 : static inline void serialize(ostream &stream, bool value)
     225              : {
     226           76 :     stream.write(value ? "true"sv : "false"sv);
     227           76 : }
     228              : 
     229          567 : static inline void serialize(ostream &stream, const std::string_view &value)
     230              : {
     231          567 :     stream.write('"');
     232          567 :     stream.write_escaped(value);
     233          566 :     stream.write('"');
     234          566 : }
     235              : 
     236          463 : static inline void serialize(ostream &stream, const spb::detail::proto_field_string auto &value)
     237              : {
     238          463 :     serialize(stream, std::string_view(value.data(), value.size()));
     239          462 : }
     240              : 
     241         1479 : static inline void serialize(ostream &stream, spb::detail::proto_field_int_or_float auto value)
     242              : {
     243         1479 :     auto buffer = std::array<char, 32>();
     244              : 
     245         3448 :     auto result = spb_std_emu::to_chars(buffer.data(), buffer.data() + sizeof(buffer), value);
     246         4437 :     stream.write(std::string_view(buffer.data(), static_cast<size_t>(result.ptr - buffer.data())));
     247         1479 : }
     248              : 
     249           76 : static inline void serialize(ostream &stream, const bool &value, const field_attributes &field)
     250              : {
     251           76 :     serialize_key(stream, field.name);
     252           76 :     serialize(stream, value);
     253           76 : }
     254              : 
     255         1471 : static inline void serialize(ostream &stream, const spb::detail::proto_field_int_or_float auto &value,
     256              :                              const field_attributes &field)
     257              : {
     258         1471 :     serialize_key(stream, field.name);
     259         1471 :     serialize(stream, value);
     260         1471 : }
     261              : 
     262          476 : static inline void serialize(ostream &stream, const spb::detail::proto_field_string auto &value,
     263              :                              const field_attributes &field)
     264              : {
     265          476 :     if (value.empty())
     266            5 :         return;
     267              : 
     268          471 :     if (field.max_size && value.size() > field.max_size) [[unlikely]]
     269            8 :         throw std::length_error("string is too large");
     270              : 
     271          463 :     serialize_key(stream, field.name);
     272          463 :     serialize(stream, value);
     273              : }
     274              : 
     275          324 : static inline void serialize(ostream &stream, const spb::detail::proto_field_bytes auto &value,
     276              :                              const field_attributes &field)
     277              : {
     278          324 :     if (value.empty())
     279           15 :         return;
     280              : 
     281          309 :     if (field.max_size && value.size() > field.max_size) [[unlikely]]
     282            8 :         throw std::length_error("bytes is too large");
     283              : 
     284          301 :     serialize_key(stream, field.name);
     285          301 :     stream.write('"');
     286          301 :     base64_encode(stream, value);
     287          301 :     stream.write('"');
     288              : }
     289          839 : static inline void serialize(ostream &stream, const spb::detail::proto_label_repeated auto &value,
     290              :                              const field_attributes &field)
     291              : {
     292          839 :     if (value.empty())
     293          270 :         return;
     294              : 
     295          569 :     if (field.max_count && value.size() > field.max_count) [[unlikely]]
     296           17 :         throw std::length_error("repeated is too large");
     297              : 
     298          552 :     serialize_key(stream, field.name);
     299          552 :     stream.write('[');
     300          552 :     stream.put_comma = false;
     301         1731 :     for (const auto &v : value)
     302              :     {
     303              :         if constexpr (std::is_same_v<typename std::decay_t<decltype(value)>::value_type, bool>)
     304              :         {
     305           34 :             serialize(stream, bool(v), {});
     306              :         }
     307              :         else
     308              :         {
     309         1145 :             serialize(stream, v, {});
     310              :         }
     311              :     }
     312          552 :     stream.write(']');
     313          552 :     stream.put_comma = true;
     314              : }
     315              : 
     316           10 : static inline void serialize(ostream &stream, const spb::detail::proto_label_repeated_fixed_size auto &value,
     317              :                              const field_attributes &field)
     318              : {
     319           10 :     serialize_key(stream, field.name);
     320           10 :     stream.write('[');
     321           10 :     stream.put_comma = false;
     322           60 :     for (size_t i = 0; i < value.size(); i++)
     323              :     {
     324              :         if constexpr (std::is_same_v<typename std::decay_t<decltype(value)>::value_type, bool>)
     325              :         {
     326              :             serialize(stream, bool(value[i]));
     327              :         }
     328              :         else
     329              :         {
     330           40 :             serialize(stream, value[i], {});
     331              :         }
     332              :     }
     333           10 :     stream.write(']');
     334           10 :     stream.put_comma = true;
     335           10 : }
     336              : 
     337              : static constexpr field_attributes no_name = {};
     338              : 
     339              : template <typename keyT, typename valueT>
     340           20 : static inline void serialize(ostream &stream, const std::map<keyT, valueT> &map,
     341              :                              const field_attributes &field)
     342              : {
     343           20 :     if (map.empty())
     344            8 :         return;
     345              : 
     346           12 :     serialize_key(stream, field.name);
     347           12 :     stream.write('{');
     348           12 :     stream.put_comma = false;
     349           26 :     for (auto &[map_key, map_value] : map)
     350              :     {
     351              :         if constexpr (std::is_same_v<keyT, std::string_view> || std::is_same_v<keyT, std::string>)
     352              :         {
     353            6 :             serialize_key(stream, map_key);
     354              :         }
     355              :         else
     356              :         {
     357            8 :             if (stream.put_comma)
     358            2 :                 stream.write(',');
     359              : 
     360            8 :             stream.write('"');
     361            8 :             serialize(stream, map_key);
     362            8 :             stream.write(R"(":)"sv);
     363              :         }
     364           14 :         stream.put_comma = false;
     365           14 :         serialize(stream, map_value, no_name);
     366           14 :         stream.put_comma = true;
     367              :     }
     368           12 :     stream.write('}');
     369           12 :     stream.put_comma = true;
     370              : }
     371              : 
     372          615 : static inline void serialize(ostream &stream, const spb::detail::proto_label_optional auto &p_value,
     373              :                              const field_attributes &field)
     374              : {
     375          615 :     if (p_value.has_value())
     376          513 :         return serialize(stream, *p_value, field);
     377              : }
     378              : 
     379              : template <typename T>
     380              : static inline void serialize(ostream &stream, const std::unique_ptr<T> &p_value,
     381              :                              const field_attributes &field)
     382              : {
     383              :     if (p_value)
     384              :         return serialize(stream, *p_value, field);
     385              : }
     386              : 
     387         2123 : static inline void serialize(ostream &stream, const spb::detail::proto_message auto &value,
     388              :                              const field_attributes &field)
     389              : {
     390         2123 :     serialize_key(stream, field.name);
     391         2123 :     stream.write('{');
     392         2123 :     stream.put_comma = false;
     393              : 
     394              :     //
     395              :     //- serialize_value is generated by the spb-protoc
     396              :     //
     397         2123 :     serialize_value(stream, value);
     398         2090 :     stream.write('}');
     399         2090 :     stream.put_comma = true;
     400         2090 : }
     401              : 
     402          104 : static inline void serialize(ostream &stream, const spb::detail::proto_enum auto &value,
     403              :                              const field_attributes &field)
     404              : {
     405          104 :     serialize_key(stream, field.name);
     406              : 
     407              :     //
     408              :     //- serialize_value is generated by the spb-protoc
     409              :     //
     410          104 :     serialize_value(stream, value);
     411          104 : }
     412              : 
     413         2129 : static inline auto serialize(const auto &value, spb::io::writer on_write) -> size_t
     414              : {
     415         2129 :     auto stream = ostream(on_write);
     416         2129 :     serialize(stream, value, no_name);
     417         4190 :     return stream.size();
     418              : }
     419              : 
     420         2183 : void ostream::serialize(const auto &value, const field_attributes &field)
     421              : {
     422         2183 :     detail::serialize(*this, value, field);
     423         2150 : }
     424              : 
     425          104 : inline void ostream::serialize(std::string_view value)
     426              : {
     427          104 :     detail::serialize(*this, value);
     428          104 : }
     429              : 
     430              : } // namespace spb::json::detail
        

Generated by: LCOV version 2.0-1