LCOV - code coverage report
Current view: top level - spb-proto-compiler/dumper/json - dumper.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.0 % 275 264
Test Date: 2025-12-05 17:50:15 Functions: 100.0 % 26 26

            Line data    Source code
       1              : /***************************************************************************\
       2              : * Name        : json dumper                                                 *
       3              : * Description : generate C++ src files for json de/serialization            *
       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              : #include "dumper.h"
      12              : #include "ast/ast.h"
      13              : #include "ast/proto-field.h"
      14              : #include "ast/proto-file.h"
      15              : #include "io/file.h"
      16              : #include "parser/parser.h"
      17              : #include "template-h.h"
      18              : #include <algorithm>
      19              : #include <array>
      20              : #include <cctype>
      21              : #include <cstdint>
      22              : #include <map>
      23              : #include <set>
      24              : #include <spb/json/deserialize.hpp>
      25              : #include <sstream>
      26              : #include <stdexcept>
      27              : #include <string>
      28              : #include <string_view>
      29              : 
      30              : using namespace std::literals;
      31              : 
      32              : namespace
      33              : {
      34              : 
      35          555 : auto replace( std::string_view input, std::string_view what, std::string_view with ) -> std::string
      36              : {
      37          555 :     auto result = std::string( input );
      38          555 :     auto pos    = size_t{ };
      39              : 
      40         1631 :     while( ( pos = result.find( what, pos ) ) != std::string::npos )
      41              :     {
      42         1076 :         result.replace( pos, what.size( ), with );
      43         1076 :         pos += with.size( );
      44              :     }
      45              : 
      46          555 :     return result;
      47            0 : }
      48              : 
      49          531 : void dump_prototypes( std::ostream & stream, std::string_view type )
      50              : {
      51          531 :     stream << replace( file_json_header_template, "$", type );
      52          531 : }
      53              : 
      54         2480 : auto json_name_from_options( const proto_options & options ) -> std::string_view
      55              : {
      56         2480 :     if( auto p_option = options.find( "json_name" ); p_option != options.end( ) )
      57              :     {
      58            0 :         return p_option->second;
      59              :     }
      60              : 
      61         2480 :     return ""sv;
      62              : }
      63              : 
      64         1266 : auto convert_to_camelCase( std::string_view input ) -> std::string
      65              : {
      66         1266 :     auto result = std::string( input );
      67         1266 :     if( !result.empty( ) )
      68              :     {
      69         1266 :         result[ 0 ] = char( std::tolower( result[ 0 ] ) );
      70              :     }
      71              : 
      72         1266 :     if( input.find( '_' ) == std::string::npos )
      73              :     {
      74          797 :         return result;
      75              :     }
      76              : 
      77          469 :     auto index = 0U;
      78          469 :     auto up    = false;
      79         7624 :     for( auto c : input )
      80              :     {
      81         7155 :         if( c == '_' )
      82              :         {
      83          703 :             up = true;
      84              :         }
      85              :         else
      86              :         {
      87         6452 :             result[ index ] = char( ( up && index > 0 ) ? std::toupper( c ) : std::tolower( c ) );
      88         6452 :             index += 1;
      89         6452 :             up = false;
      90              :         }
      91              :     }
      92          469 :     result.resize( index );
      93          469 :     return result;
      94            0 : }
      95              : 
      96         1214 : auto json_field_name( const proto_base & field ) -> std::string
      97              : {
      98         1214 :     if( const auto result = json_name_from_options( field.options ); !result.empty( ) )
      99              :     {
     100            0 :         return std::string( result );
     101              :     }
     102              : 
     103         2428 :     return std::string( field.name );
     104              : }
     105              : 
     106         1266 : auto json_field_name_or_camelCase( const proto_base & field ) -> std::string
     107              : {
     108         1266 :     if( const auto result = json_name_from_options( field.options ); !result.empty( ) )
     109              :     {
     110            0 :         return std::string( result );
     111              :     }
     112              : 
     113         1266 :     return convert_to_camelCase( field.name );
     114              : }
     115              : 
     116          448 : void dump_prototypes( std::ostream & stream, const proto_message & message,
     117              :                       std::string_view parent )
     118              : {
     119         1344 :     const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name );
     120          448 :     dump_prototypes( stream, message_with_parent );
     121          448 : }
     122              : 
     123           83 : void dump_prototypes( std::ostream & stream, const proto_enum & my_enum, std::string_view parent )
     124              : {
     125          249 :     const auto enum_with_parent = std::string( parent ) + "::" + std::string( my_enum.name );
     126           83 :     dump_prototypes( stream, enum_with_parent );
     127           83 : }
     128              : 
     129           75 : void dump_prototypes( std::ostream & stream, const proto_enums & enums, std::string_view parent )
     130              : {
     131          158 :     for( const auto & my_enum : enums )
     132              :     {
     133           83 :         dump_prototypes( stream, my_enum, parent );
     134              :     }
     135           75 : }
     136              : 
     137           63 : void dump_prototypes( std::ostream & stream, const proto_messages & messages,
     138              :                       std::string_view parent )
     139              : {
     140          511 :     for( const auto & message : messages )
     141              :     {
     142          448 :         dump_prototypes( stream, message, parent );
     143              :     }
     144              : 
     145          511 :     for( const auto & message : messages )
     146              :     {
     147          448 :         if( message.messages.empty( ) )
     148              :         {
     149          397 :             continue;
     150              :         }
     151          153 :         const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name );
     152           51 :         dump_prototypes( stream, message.messages, message_with_parent );
     153           51 :     }
     154              : 
     155          511 :     for( const auto & message : messages )
     156              :     {
     157          448 :         if( message.enums.empty( ) )
     158              :         {
     159          385 :             continue;
     160              :         }
     161          189 :         const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name );
     162           63 :         dump_prototypes( stream, message.enums, message_with_parent );
     163           63 :     }
     164           63 : }
     165              : 
     166           12 : void dump_prototypes( std::ostream & stream, const proto_file & file )
     167              : {
     168           12 :     const auto package_name = file.package.name.empty( )
     169           12 :         ? std::string( )
     170           12 :         : "::" + replace( file.package.name, ".", "::" );
     171           12 :     dump_prototypes( stream, file.package.messages, package_name );
     172           12 :     dump_prototypes( stream, file.package.enums, package_name );
     173           12 : }
     174              : 
     175           12 : void dump_cpp_includes( std::ostream & stream, std::string_view header_file_path )
     176              : {
     177              :     stream << "#include \"" << header_file_path << "\"\n"
     178              :            << "#include <spb/json.hpp>\n"
     179              :               "#include <system_error>\n"
     180           12 :               "#include <type_traits>\n\n";
     181           12 : }
     182              : 
     183          555 : void dump_cpp_close_namespace( std::ostream & stream, std::string_view name )
     184              : {
     185          555 :     stream << "} // namespace " << name << "\n";
     186          555 : }
     187              : 
     188          555 : void dump_cpp_open_namespace( std::ostream & stream, std::string_view name )
     189              : {
     190          555 :     stream << "namespace " << name << "\n{\n";
     191          555 : }
     192              : 
     193            4 : void dump_cpp_serialize_value( std::ostream & stream, const proto_oneof & oneof )
     194              : {
     195            4 :     stream << "\t{\n\t\tconst auto index = value." << oneof.name << ".index( );\n";
     196            4 :     stream << "\t\tswitch( index )\n\t\t{\n";
     197           15 :     for( size_t i = 0; i < oneof.fields.size( ); ++i )
     198              :     {
     199           11 :         stream << "\t\t\tcase " << i << ":\n\t\t\t\treturn stream.serialize( \""
     200           22 :                << json_field_name( oneof.fields[ i ] ) << "\"sv, std::get< " << i << " >( value."
     201           11 :                << oneof.name << ") );\n";
     202              :     }
     203            4 :     stream << "\t\t}\n\t}\n\n";
     204            4 : }
     205              : 
     206           83 : void dump_cpp_serialize_value( std::ostream & stream, const proto_enum & my_enum,
     207              :                                std::string_view full_name )
     208              : {
     209           83 :     if( my_enum.fields.empty( ) )
     210              :     {
     211            0 :         stream << "void serialize_value( detail::ostream &, const " << full_name << " & )\n{\n";
     212            0 :         stream << "\treturn ;\n}\n\n";
     213            0 :         return;
     214              :     }
     215              : 
     216              :     stream << "void serialize_value( detail::ostream & stream, const " << full_name
     217           83 :            << " & value )\n{\n";
     218           83 :     stream << "\tswitch( value )\n\t{\n";
     219              : 
     220           83 :     std::set< int32_t > numbers_taken;
     221          521 :     for( const auto & field : my_enum.fields )
     222              :     {
     223          438 :         if( !numbers_taken.insert( field.number ).second )
     224              :         {
     225            4 :             continue;
     226              :         }
     227              : 
     228              :         stream << "\tcase " << full_name << "::" << field.name
     229          434 :                << ":\n\t\treturn stream.serialize( \"" << field.name << "\"sv);\n";
     230              :     }
     231              :     stream << "\tdefault:\n\t\tthrow std::system_error( std::make_error_code( "
     232           83 :               "std::errc::invalid_argument ) );\n";
     233           83 :     stream << "\t}\n}\n\n";
     234           83 : }
     235              : 
     236           83 : void dump_cpp_deserialize_value( std::ostream & stream, const proto_enum & my_enum,
     237              :                                  std::string_view full_name )
     238              : {
     239           83 :     if( my_enum.fields.empty( ) )
     240              :     {
     241            0 :         stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n";
     242            0 :         stream << "\n}\n\n";
     243            0 :         return;
     244              :     }
     245              : 
     246           83 :     size_t key_size_min = UINT32_MAX;
     247           83 :     size_t key_size_max = 0;
     248              : 
     249           83 :     auto name_map = std::multimap< uint32_t, std::string_view >( );
     250          521 :     for( const auto & field : my_enum.fields )
     251              :     {
     252          438 :         name_map.emplace( spb::json::detail::djb2_hash( field.name ), field.name );
     253          438 :         key_size_min = std::min( key_size_min, field.name.size( ) );
     254          438 :         key_size_max = std::max( key_size_max, field.name.size( ) );
     255              :     }
     256              : 
     257              :     stream << "void deserialize_value( detail::istream & stream, " << full_name
     258           83 :            << " & value )\n{\n";
     259           83 :     stream << "\tauto enum_value = stream.deserialize_string_or_int( " << key_size_min << ", "
     260           83 :            << key_size_max << " );\n";
     261           83 :     stream << "\tstd::visit( detail::overloaded{\n\t\t[&]( std::string_view enum_str )\n\t\t{\n";
     262           83 :     stream << "\t\t\tconst auto enum_hash = djb2_hash( enum_str );\n";
     263           83 :     stream << "\t\t\tswitch( enum_hash )\n\t\t\t{\n";
     264           83 :     auto last_hash = name_map.begin( )->first + 1;
     265           83 :     auto put_break = false;
     266          521 :     for( const auto & [ hash, name ] : name_map )
     267              :     {
     268          438 :         if( hash != last_hash )
     269              :         {
     270          438 :             last_hash = hash;
     271          438 :             if( put_break )
     272              :             {
     273          355 :                 stream << "\t\t\t\tbreak ;\n";
     274              :             }
     275          438 :             put_break = true;
     276          438 :             stream << "\t\t\tcase detail::djb2_hash( \"" << name << "\"sv ):\n";
     277              :         }
     278              :         stream << "\t\t\t\tif( enum_str == \"" << name << "\"sv ){\n\t\t\t\t\tvalue = " << full_name
     279          438 :                << "::" << name << ";\n\t\t\t\t\treturn ;\t\t\t\t}\n";
     280              :     }
     281           83 :     stream << "\t\t\t\tbreak ;\n";
     282              :     stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( "
     283           83 :               "std::errc::invalid_argument ) );\n";
     284              :     stream << "\t\t},\n\t\t[&]( int32_t enum_int )\n\t\t{\n\t\t\tswitch( " << full_name
     285           83 :            << "( enum_int ) )\n\t\t\t{\n";
     286           83 :     std::set< int32_t > numbers_taken;
     287          521 :     for( const auto & field : my_enum.fields )
     288              :     {
     289          438 :         if( !numbers_taken.insert( field.number ).second )
     290              :         {
     291            4 :             continue;
     292              :         }
     293              : 
     294          434 :         stream << "\t\t\tcase " << full_name << "::" << field.name << ":\n";
     295              :     }
     296           83 :     stream << "\t\t\t\tvalue = " << full_name << "( enum_int );\n\t\t\t\treturn ;\n";
     297              :     stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( "
     298           83 :               "std::errc::invalid_argument ) );\n";
     299           83 :     stream << "\t\t}\n\t}, enum_value );\n}\n\n";
     300           83 : }
     301              : 
     302          448 : void dump_cpp_serialize_value( std::ostream & stream, const proto_message & message,
     303              :                                std::string_view full_name )
     304              : {
     305          448 :     if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) )
     306              :     {
     307              :         stream << "void serialize_value( detail::ostream & , const " << full_name
     308            9 :                << " & )\n{\n}\n\n";
     309            9 :         return;
     310              :     }
     311              : 
     312              :     stream << "void serialize_value( detail::ostream & stream, const " << full_name
     313          439 :            << " & value )\n{\n";
     314         1642 :     for( const auto & field : message.fields )
     315              :     {
     316         1203 :         stream << "\tstream.serialize( \"" << json_field_name( field ) << "\"sv, value."
     317         1203 :                << field.name << " );\n";
     318              :     }
     319          491 :     for( const auto & map : message.maps )
     320              :     {
     321           52 :         stream << "\tstream.serialize( \"" << map.name << "\"sv, value." << map.name << " );\n";
     322              :     }
     323          443 :     for( const auto & oneof : message.oneofs )
     324              :     {
     325            4 :         dump_cpp_serialize_value( stream, oneof );
     326              :     }
     327          439 :     stream << "}\n";
     328              : }
     329              : 
     330          448 : void dump_cpp_deserialize_value( std::ostream & stream, const proto_message & message,
     331              :                                  std::string_view full_name )
     332              : {
     333          448 :     if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) )
     334              :     {
     335            9 :         stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n";
     336            9 :         stream << "\n}\n\n";
     337            9 :         return;
     338              :     }
     339              : 
     340              :     //- json deserializer needs to accept both camelCase (parsed_name) and the original field name
     341              :     struct one_field
     342              :     {
     343              :         std::string parsed_name;
     344              :         std::string_view name;
     345              :         size_t oneof_index = SIZE_MAX;
     346              :         std::string_view bitfield;
     347              :     };
     348              : 
     349          439 :     size_t key_size_min = UINT32_MAX;
     350          439 :     size_t key_size_max = 0;
     351              : 
     352          439 :     auto name_map = std::multimap< uint32_t, one_field >( );
     353         1642 :     for( const auto & field : message.fields )
     354              :     {
     355         1203 :         key_size_min = std::min( key_size_min, field.name.size( ) );
     356         1203 :         key_size_max = std::max( key_size_max, field.name.size( ) );
     357              : 
     358         1203 :         const auto field_name = json_field_name_or_camelCase( field );
     359         1203 :         name_map.emplace( spb::json::detail::djb2_hash( field_name ),
     360         2406 :                           one_field{
     361              :                               .parsed_name = field_name,
     362              :                               .name        = field.name,
     363              :                               .bitfield    = field.bit_field,
     364              :                           } );
     365         1203 :         if( field_name != field.name )
     366              :         {
     367          482 :             key_size_min = std::min( key_size_min, field_name.size( ) );
     368          482 :             key_size_max = std::max( key_size_max, field_name.size( ) );
     369              : 
     370          482 :             name_map.emplace( spb::json::detail::djb2_hash( field.name ),
     371         1446 :                               one_field{
     372          964 :                                   .parsed_name = std::string( field.name ),
     373              :                                   .name        = field.name,
     374              :                                   .bitfield    = field.bit_field,
     375              :                               } );
     376              :         }
     377         1203 :     }
     378          491 :     for( const auto & field : message.maps )
     379              :     {
     380           52 :         key_size_min = std::min( key_size_min, field.name.size( ) );
     381           52 :         key_size_max = std::max( key_size_max, field.name.size( ) );
     382              : 
     383           52 :         const auto field_name = json_field_name_or_camelCase( field );
     384           52 :         name_map.emplace( spb::json::detail::djb2_hash( field_name ),
     385          104 :                           one_field{
     386              :                               .parsed_name = field_name,
     387              :                               .name        = field.name,
     388              :                           } );
     389           52 :         if( field_name != field.name )
     390              :         {
     391           48 :             key_size_min = std::min( key_size_min, field_name.size( ) );
     392           48 :             key_size_max = std::max( key_size_max, field_name.size( ) );
     393              : 
     394           48 :             name_map.emplace(
     395           48 :                 spb::json::detail::djb2_hash( field.name ),
     396          192 :                 one_field{ .parsed_name = std::string( field.name ), .name = field.name } );
     397              :         }
     398           52 :     }
     399          443 :     for( const auto & oneof : message.oneofs )
     400              :     {
     401           15 :         for( size_t i = 0; i < oneof.fields.size( ); ++i )
     402              :         {
     403           11 :             key_size_min = std::min( key_size_min, oneof.fields[ i ].name.size( ) );
     404           11 :             key_size_max = std::max( key_size_max, oneof.fields[ i ].name.size( ) );
     405              : 
     406           11 :             const auto field_name = json_field_name_or_camelCase( oneof.fields[ i ] );
     407           11 :             name_map.emplace( spb::json::detail::djb2_hash( field_name ),
     408           22 :                               one_field{
     409              :                                   .parsed_name = field_name,
     410              :                                   .name        = oneof.name,
     411              :                                   .oneof_index = i,
     412              :                               } );
     413           11 :             if( field_name != oneof.fields[ i ].name )
     414              :             {
     415            8 :                 key_size_min = std::min( key_size_min, field_name.size( ) );
     416            8 :                 key_size_max = std::max( key_size_max, field_name.size( ) );
     417              : 
     418            8 :                 name_map.emplace( spb::json::detail::djb2_hash( oneof.fields[ i ].name ),
     419           24 :                                   one_field{
     420           16 :                                       .parsed_name = std::string( oneof.fields[ i ].name ),
     421              :                                       .name        = oneof.name,
     422              :                                       .oneof_index = i,
     423              :                                   } );
     424              :             }
     425           11 :         }
     426              :     }
     427              : 
     428              :     stream << "void deserialize_value( detail::istream & stream, " << full_name
     429          439 :            << " & value )\n{\n";
     430          439 :     stream << "\tauto key = stream.deserialize_key( " << key_size_min << ", " << key_size_max
     431          439 :            << " );\n";
     432          439 :     stream << "\tswitch( djb2_hash( key ) )\n\t{\n";
     433              : 
     434          439 :     auto last_hash = name_map.begin( )->first + 1;
     435          439 :     auto put_break = false;
     436         2243 :     for( const auto & [ hash, field ] : name_map )
     437              :     {
     438         1804 :         if( hash != last_hash )
     439              :         {
     440         1803 :             if( put_break )
     441              :             {
     442         1364 :                 stream << "\t\t\t\tbreak;\n";
     443              :             }
     444         1803 :             put_break = true;
     445         1803 :             last_hash = hash;
     446         1803 :             stream << "\t\tcase detail::djb2_hash( \"" << field.parsed_name << "\"sv ):\n";
     447              :         }
     448         1804 :         stream << "\t\t\tif( key == \"" << field.parsed_name << "\"sv )\n\t\t\t{\n";
     449         1804 :         if( field.oneof_index == SIZE_MAX )
     450              :         {
     451         1785 :             if( !field.bitfield.empty( ) )
     452              :             {
     453              : 
     454              :                 stream << "\t\t\t\tvalue." << field.name
     455              :                        << " = stream.deserialize_bitfield< decltype( value." << field.name
     456           50 :                        << " ) >( " << field.bitfield << " );\n\t\t\t\treturn ;\n";
     457              :             }
     458              :             else
     459              :             {
     460         1735 :                 stream << "\t\t\t\treturn stream.deserialize( value." << field.name << " );\n";
     461              :             }
     462              :         }
     463              :         else
     464              :         {
     465           19 :             stream << "\t\t\t\treturn stream.deserialize_variant<" << field.oneof_index
     466           19 :                    << ">( value." << field.name << " );\n";
     467              :         }
     468         1804 :         stream << "\t\t\t}\n";
     469              :     }
     470          439 :     stream << "\t\t\tbreak;\n\t}\n\treturn stream.skip_value( );\n}\n";
     471          439 : }
     472              : 
     473           83 : void dump_cpp_enum( std::ostream & stream, const proto_enum & my_enum, std::string_view parent )
     474              : {
     475          249 :     const auto full_name = std::string( parent ) + "::" + std::string( my_enum.name );
     476           83 :     dump_cpp_open_namespace( stream, "detail" );
     477           83 :     dump_cpp_serialize_value( stream, my_enum, full_name );
     478           83 :     dump_cpp_deserialize_value( stream, my_enum, full_name );
     479           83 :     dump_cpp_close_namespace( stream, "detail" );
     480           83 : }
     481              : 
     482          460 : void dump_cpp_enums( std::ostream & stream, const proto_enums & enums, std::string_view parent )
     483              : {
     484          543 :     for( const auto & my_enum : enums )
     485              :     {
     486           83 :         dump_cpp_enum( stream, my_enum, parent );
     487              :     }
     488          460 : }
     489              : 
     490              : void dump_cpp_messages( std::ostream & stream, const proto_messages & messages,
     491              :                         std::string_view parent );
     492              : 
     493          448 : void dump_cpp_message( std::ostream & stream, const proto_message & message,
     494              :                        std::string_view parent )
     495              : {
     496         1344 :     const auto full_name = std::string( parent ) + "::" + std::string( message.name );
     497              : 
     498          448 :     dump_cpp_enums( stream, message.enums, full_name );
     499              : 
     500          448 :     dump_cpp_open_namespace( stream, "detail" );
     501          448 :     dump_cpp_serialize_value( stream, message, full_name );
     502          448 :     dump_cpp_deserialize_value( stream, message, full_name );
     503          448 :     dump_cpp_close_namespace( stream, "detail" );
     504              : 
     505          448 :     dump_cpp_messages( stream, message.messages, full_name );
     506          448 : }
     507              : 
     508          460 : void dump_cpp_messages( std::ostream & stream, const proto_messages & messages,
     509              :                         std::string_view parent )
     510              : {
     511          908 :     for( const auto & message : messages )
     512              :     {
     513          448 :         dump_cpp_message( stream, message, parent );
     514              :     }
     515          460 : }
     516              : 
     517           12 : void dump_cpp( std::ostream & stream, const proto_file & file )
     518              : {
     519           12 :     const auto str_namespace = file.package.name.empty( )
     520           12 :         ? std::string( )
     521           12 :         : "::" + replace( file.package.name, ".", "::" );
     522           12 :     dump_cpp_enums( stream, file.package.enums, str_namespace );
     523           12 :     dump_cpp_messages( stream, file.package.messages, str_namespace );
     524           12 : }
     525              : 
     526              : }// namespace
     527              : 
     528           12 : void dump_json_header( const proto_file & file, std::ostream & stream )
     529              : {
     530           12 :     dump_cpp_open_namespace( stream, "spb::json::detail" );
     531           12 :     stream << "struct ostream;\nstruct istream;\n";
     532           12 :     dump_prototypes( stream, file );
     533           12 :     dump_cpp_close_namespace( stream, "spb::json::detail" );
     534           12 : }
     535              : 
     536           12 : void dump_json_cpp( const proto_file & file, const std::filesystem::path & header_file,
     537              :                     std::ostream & stream )
     538              : {
     539           12 :     dump_cpp_includes( stream, header_file.string( ) );
     540           12 :     dump_cpp_open_namespace( stream, "spb::json" );
     541           12 :     dump_cpp( stream, file );
     542           12 :     dump_cpp_close_namespace( stream, "spb::json" );
     543           12 : }
        

Generated by: LCOV version 2.0-1