aboutsummaryrefslogblamecommitdiffstats
path: root/library/cpp/protobuf/json/ut/proto2json_ut.cpp
blob: f19558e60a448c8a402dadbcd5230f17a686d5f6 (plain) (tree)
1
2
3
4
5
6
7
8
9

                  
                                                 
 

                                         
 
                                                 
 
                                                  
                                  
                                





                                 
                 

                                  
                                        




                                                                          



































                                                                                                       
                                                   

                   














                                                                          
                                    


                                 
                                                                                     





                                                             
                                         


                                                        
                                                                                              





                                                                 
                                  






                                                          
 






                                                                
 
















                                                                            
                                                   
                   
                     
 
                              






                                                      
 






                                                            
 
















                                                                            
                                                   
                   
                     
 
                               


                                                                      
         

                                                              
         






                                                                    
 



















                                                                                    
                                                            
                            
                     
 
                                   






                                                           
 






                                                            
 
















                                                                                 
                                                   
                   
                          
 
                                   






                                                           
 






                                                            
 
















                                                                                 
                                                   
                   
                          
 
                                    








                                                  
         

                                                              

         





                                                                    
 












                                                               
                                                            
                            
                                                  

                                   
                                                              
                                                     

         
                                     
                                   
                                                                    
                                                     

                          
 
                             






                                                        
 

                                                                  
 






                                                      
 

                                                                  
 






                                                               
 

                                                                  
 






                                                               
 

                                                                  
 












                                                                   
                                         













                                                                        
                                                   
                   


                                                                        
 

                                                                  












































                                                                                                                     
                               
 
                                             


                                                     
 


                                                          
 
                                           




                                                                          
 

                                                                  
 


                                                                      
                                                   
                   


                                                                          
 

                                                                  









                                                                                                  
                                 
 
                           

                                                      
 

                                       
 

                                                                
 

                                                      
 



                                                                                
 

                                                                     
 

                                                      
 



                                                                   
 

                                                                     
 

                                                      
 



                                                                           
 


                                                                     
 









                                                      
 
                                 

                                                    
 



                                                                 
 

                                                                     
 

                                                   
 



                                                                 
 
                                                                     
     
 
 
                                

                                                    
 


                                 
 

                                                                     
 

                                                    
 



                                                                        
 

                                                                     
 

                                                    
 



                                                                     
 

                                                                     
 

                                                    
 



                                                                     
 

                                                                     
 

                                                    
 



                                                                     
 



                                                                     
 



                                                                     
 



                                                                     
 



                                                                     
 

                                                                     
 
































































                                                                          

                                               
 




                                                                        
 

                                                                     
 

                                               
 




                                                                     
 

                                                                     
 



                                                                                       
 






                                    
 

                                                                     
 














                                                                     











                                                                                                              
 
                               
 
                      
                                                              
 
                                                                              
 
                      
                                                           
 
                                                                              
 
                      
                   
 


                                        
 
                                                                                                                                         
 

                                                                 
 












                                                                
 
                              
                   
 


                                        
 
                                                                                         
 


                                                                 
 
                                                            
 
                         
                   
 


                                        
 
                                                                                         
 
                                                         
 
                                                            
 













                                                                                  
                   
 


























                                                                                                  
                  
                         
 




                                                                                               



                                                                      
                  
















                                                                                                              
























                                                                                              
                    
#include "json.h"
#include "proto.h"

#include <library/cpp/protobuf/json/ut/test.pb.h>

#include <library/cpp/json/json_value.h>
#include <library/cpp/json/json_reader.h>
#include <library/cpp/json/json_writer.h>

#include <library/cpp/protobuf/json/proto2json.h>

#include <library/cpp/testing/unittest/registar.h>

#include <util/generic/hash_set.h>
#include <util/generic/string.h>
#include <util/generic/ylimits.h>

#include <util/stream/str.h>

#include <util/system/defaults.h>
#include <util/system/yassert.h>

#include <limits>

using namespace NProtobufJson;
using namespace NProtobufJsonTest;

Y_UNIT_TEST_SUITE(TProto2JsonFlatTest) {
    Y_UNIT_TEST(TestFlatDefault) {
        using namespace ::google::protobuf;
        TFlatDefault proto;
        NJson::TJsonValue json;
        TProto2JsonConfig cfg;
        cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyDefault);
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, cfg));
#define DEFINE_FIELD(name, value)                                                                     \
    {                                                                                                 \
        auto descr = proto.GetMetadata().descriptor->FindFieldByName(#name);                          \
        UNIT_ASSERT(descr);                                                                           \
        UNIT_ASSERT(json.Has(#name));                                                                 \
        switch (descr->cpp_type()) {                                                                  \
            case FieldDescriptor::CPPTYPE_INT32:                                                      \
                UNIT_ASSERT(descr->default_value_int32() == json[#name].GetIntegerRobust());          \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_INT64:                                                      \
                UNIT_ASSERT(descr->default_value_int64() == json[#name].GetIntegerRobust());          \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_UINT32:                                                     \
                UNIT_ASSERT(descr->default_value_uint32() == json[#name].GetUIntegerRobust());        \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_UINT64:                                                     \
                UNIT_ASSERT(descr->default_value_uint32() == json[#name].GetUIntegerRobust());        \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_DOUBLE:                                                     \
                UNIT_ASSERT(descr->default_value_double() == json[#name].GetDoubleRobust());          \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_FLOAT:                                                      \
                UNIT_ASSERT(descr->default_value_float() == json[#name].GetDoubleRobust());           \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_BOOL:                                                       \
                UNIT_ASSERT(descr->default_value_bool() == json[#name].GetBooleanRobust());           \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_ENUM:                                                       \
                UNIT_ASSERT(descr->default_value_enum()->number() == json[#name].GetIntegerRobust()); \
                break;                                                                                \
            case FieldDescriptor::CPPTYPE_STRING:                                                     \
                UNIT_ASSERT(descr->default_value_string() == json[#name].GetStringRobust());          \
                break;                                                                                \
            default:                                                                                  \
                break;                                                                                \
        }                                                                                             \
    }
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD
    }

    Y_UNIT_TEST(TestOneOfDefault) {
        using namespace ::google::protobuf;
        TFlatOneOfDefault proto;
        NJson::TJsonValue json;
        TProto2JsonConfig cfg;
        cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyDefault);
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, cfg));

        UNIT_ASSERT(!json.Has("ChoiceOne"));
        UNIT_ASSERT(!json.Has("ChoiceTwo"));

        proto.SetChoiceOne("one");
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, cfg));
        UNIT_ASSERT_EQUAL("one", json["ChoiceOne"].GetStringRobust());
    }

    Y_UNIT_TEST(TestNameGenerator) {
        TNameGeneratorType proto;
        proto.SetField(42);

        TProto2JsonConfig cfg;
        cfg.SetNameGenerator([](const NProtoBuf::FieldDescriptor&) { return "42"; });

        TStringStream str;
        Proto2Json(proto, str, cfg);

        UNIT_ASSERT_STRINGS_EQUAL(R"({"42":42})", str.Str());
    }

    Y_UNIT_TEST(TestEnumValueGenerator) {
        TEnumValueGeneratorType proto;
        proto.SetEnum(TEnumValueGeneratorType::ENUM_42);

        TProto2JsonConfig cfg;
        cfg.SetEnumValueGenerator([](const NProtoBuf::EnumValueDescriptor&) { return "42"; });

        TStringStream str;
        Proto2Json(proto, str, cfg);

        UNIT_ASSERT_STRINGS_EQUAL(R"({"Enum":"42"})", str.Str());
    }

    Y_UNIT_TEST(TestFlatOptional){
        {TFlatOptional proto;
    FillFlatProto(&proto);
    const NJson::TJsonValue& modelJson = CreateFlatJson();
    {
        NJson::TJsonValue json;
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        TStringStream jsonStream;
        NJson::TJsonValue json;
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
        UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    } // streamed
}

    // Try to skip each field
#define DEFINE_FIELD(name, value)                                          \
    {                                                                      \
        THashSet<TString> skippedField;                                    \
        skippedField.insert(#name);                                        \
        TFlatOptional proto;                                               \
        FillFlatProto(&proto, skippedField);                               \
        const NJson::TJsonValue& modelJson = CreateFlatJson(skippedField); \
        NJson::TJsonValue json;                                            \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));                 \
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                          \
        {                                                                  \
            TStringStream jsonStream;                                      \
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));       \
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));                 \
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                      \
        }                                                                  \
    }
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD
} // TestFlatOptional

Y_UNIT_TEST(TestFlatRequired){
    {TFlatRequired proto;
FillFlatProto(&proto);
const NJson::TJsonValue& modelJson = CreateFlatJson();
{
    NJson::TJsonValue json;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
    UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
}

{
    TStringStream jsonStream;
    NJson::TJsonValue json;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
    UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
    UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
} // streamed
}

// Try to skip each field
#define DEFINE_FIELD(name, value)                                          \
    {                                                                      \
        THashSet<TString> skippedField;                                    \
        skippedField.insert(#name);                                        \
        TFlatRequired proto;                                               \
        FillFlatProto(&proto, skippedField);                               \
        const NJson::TJsonValue& modelJson = CreateFlatJson(skippedField); \
        NJson::TJsonValue json;                                            \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));                 \
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                          \
        {                                                                  \
            TStringStream jsonStream;                                      \
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));       \
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));                 \
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                      \
        }                                                                  \
    }
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD
} // TestFlatRequired

Y_UNIT_TEST(TestFlatRepeated) {
    {
        TFlatRepeated proto;
        FillRepeatedProto(&proto);
        const NJson::TJsonValue& modelJson = CreateRepeatedFlatJson();
        {
            NJson::TJsonValue json;
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
        }

        {
            TStringStream jsonStream;
            NJson::TJsonValue json;
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
        } // streamed
    }

    TProto2JsonConfig config;
    config.SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeySkip);

    // Try to skip each field
#define DEFINE_REPEATED_FIELD(name, ...)                                           \
    {                                                                              \
        THashSet<TString> skippedField;                                            \
        skippedField.insert(#name);                                                \
        TFlatRepeated proto;                                                       \
        FillRepeatedProto(&proto, skippedField);                                   \
        const NJson::TJsonValue& modelJson = CreateRepeatedFlatJson(skippedField); \
        NJson::TJsonValue json;                                                    \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));                 \
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                                  \
        {                                                                          \
            TStringStream jsonStream;                                              \
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream, config));       \
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));                         \
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                              \
        }                                                                          \
    }
#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
#undef DEFINE_REPEATED_FIELD
} // TestFlatRepeated

Y_UNIT_TEST(TestCompositeOptional){
    {TCompositeOptional proto;
FillCompositeProto(&proto);
const NJson::TJsonValue& modelJson = CreateCompositeJson();
{
    NJson::TJsonValue json;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
    UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
}

{
    TStringStream jsonStream;
    NJson::TJsonValue json;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
    UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
    UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
} // streamed
}

// Try to skip each field
#define DEFINE_FIELD(name, value)                                               \
    {                                                                           \
        THashSet<TString> skippedField;                                         \
        skippedField.insert(#name);                                             \
        TCompositeOptional proto;                                               \
        FillCompositeProto(&proto, skippedField);                               \
        const NJson::TJsonValue& modelJson = CreateCompositeJson(skippedField); \
        NJson::TJsonValue json;                                                 \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));                      \
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                               \
        {                                                                       \
            TStringStream jsonStream;                                           \
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));            \
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));                      \
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                           \
        }                                                                       \
    }
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD
} // TestCompositeOptional

Y_UNIT_TEST(TestCompositeRequired){
    {TCompositeRequired proto;
FillCompositeProto(&proto);
const NJson::TJsonValue& modelJson = CreateCompositeJson();
{
    NJson::TJsonValue json;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
    UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
}

{
    TStringStream jsonStream;
    NJson::TJsonValue json;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
    UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
    UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
} // streamed
}

// Try to skip each field
#define DEFINE_FIELD(name, value)                                               \
    {                                                                           \
        THashSet<TString> skippedField;                                         \
        skippedField.insert(#name);                                             \
        TCompositeRequired proto;                                               \
        FillCompositeProto(&proto, skippedField);                               \
        const NJson::TJsonValue& modelJson = CreateCompositeJson(skippedField); \
        NJson::TJsonValue json;                                                 \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));                      \
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                               \
        {                                                                       \
            TStringStream jsonStream;                                           \
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));            \
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));                      \
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);                           \
        }                                                                       \
    }
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD
} // TestCompositeRequired

Y_UNIT_TEST(TestCompositeRepeated) {
    {
        TFlatOptional partProto;
        FillFlatProto(&partProto);
        TCompositeRepeated proto;
        proto.AddPart()->CopyFrom(partProto);

        NJson::TJsonValue modelJson;
        NJson::TJsonValue modelArray;
        modelArray.AppendValue(CreateFlatJson());
        modelJson.InsertValue("Part", modelArray);
        {
            NJson::TJsonValue json;
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
        }

        {
            TStringStream jsonStream;
            NJson::TJsonValue json;
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
        } // streamed
    }

    {
        // Array of messages with each field skipped
        TCompositeRepeated proto;
        NJson::TJsonValue modelArray;

#define DEFINE_REPEATED_FIELD(name, ...)                      \
    {                                                         \
        THashSet<TString> skippedField;                       \
        skippedField.insert(#name);                           \
        TFlatOptional partProto;                              \
        FillFlatProto(&partProto, skippedField);              \
        proto.AddPart()->CopyFrom(partProto);                 \
        modelArray.AppendValue(CreateFlatJson(skippedField)); \
    }
#include <library/cpp/protobuf/json/ut/repeated_fields.incl>
#undef DEFINE_REPEATED_FIELD

        NJson::TJsonValue modelJson;
        modelJson.InsertValue("Part", modelArray);

        {
            NJson::TJsonValue json;
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
        }

        {
            TStringStream jsonStream;
            NJson::TJsonValue json;
            UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream));
            UNIT_ASSERT(ReadJsonTree(&jsonStream, &json));
            UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
        } // streamed
    }
} // TestCompositeRepeated

Y_UNIT_TEST(TestEnumConfig) {
    {
        TFlatOptional proto;
        proto.SetEnum(E_1);
        NJson::TJsonValue modelJson;
        modelJson.InsertValue("Enum", 1);
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.EnumMode = TProto2JsonConfig::EnumNumber;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        TFlatOptional proto;
        proto.SetEnum(E_1);
        NJson::TJsonValue modelJson;
        modelJson.InsertValue("Enum", "E_1");
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.EnumMode = TProto2JsonConfig::EnumName;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        TFlatOptional proto;
        proto.SetEnum(E_1);
        NJson::TJsonValue modelJson;
        modelJson.InsertValue("Enum", "NProtobufJsonTest.E_1");
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.EnumMode = TProto2JsonConfig::EnumFullName;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        TFlatOptional proto;
        proto.SetEnum(E_1);
        NJson::TJsonValue modelJson;
        modelJson.InsertValue("Enum", "e_1");
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.EnumMode = TProto2JsonConfig::EnumNameLowerCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        TFlatOptional proto;
        proto.SetEnum(E_1);
        NJson::TJsonValue modelJson;
        modelJson.InsertValue("Enum", "nprotobufjsontest.e_1");
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.EnumMode = TProto2JsonConfig::EnumFullNameLowerCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
} // TestEnumConfig

Y_UNIT_TEST(TestMissingSingleKeyConfig) {
    {
        TFlatOptional proto;
        NJson::TJsonValue modelJson(NJson::JSON_MAP);
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeySkip;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        NJson::TJsonValue modelJson;
#define DEFINE_FIELD(name, value) \
    modelJson.InsertValue(#name, NJson::TJsonValue(NJson::JSON_NULL));
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD

        TFlatOptional proto;
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyNull;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
    {
        // Test MissingKeyExplicitDefaultThrowRequired for non explicit default values.
        TFlatOptional proto;
        NJson::TJsonValue modelJson(NJson::JSON_MAP);
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
    {
        // Test MissingKeyExplicitDefaultThrowRequired for explicit default values.
        NJson::TJsonValue modelJson;
        modelJson["String"] = "value";

        TSingleDefaultString proto;
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
    {
        // Test MissingKeyExplicitDefaultThrowRequired for empty required values.
        TFlatRequired proto;
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;
        UNIT_ASSERT_EXCEPTION_CONTAINS(Proto2Json(proto, json, config), yexception, "Empty required protobuf field");
    }
    {
        // Test MissingKeyExplicitDefaultThrowRequired for required value.
        TSingleRequiredString proto;
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;

        UNIT_ASSERT_EXCEPTION_CONTAINS(Proto2Json(proto, json, config), yexception, "Empty required protobuf field");

        NJson::TJsonValue modelJson;
        modelJson["String"] = "value";
        proto.SetString("value");
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
} // TestMissingSingleKeyConfig

Y_UNIT_TEST(TestMissingRepeatedKeyNoConfig) {
    {
        TFlatRepeated proto;
        NJson::TJsonValue modelJson(NJson::JSON_MAP);
        NJson::TJsonValue json;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
} // TestMissingRepeatedKeyNoConfig

Y_UNIT_TEST(TestMissingRepeatedKeyConfig) {
    {
        TFlatRepeated proto;
        NJson::TJsonValue modelJson(NJson::JSON_MAP);
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeySkip;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }

    {
        NJson::TJsonValue modelJson;
#define DEFINE_FIELD(name, value) \
    modelJson.InsertValue(#name, NJson::TJsonValue(NJson::JSON_NULL));
#include <library/cpp/protobuf/json/ut/fields.incl>
#undef DEFINE_FIELD

        TFlatRepeated proto;
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeyNull;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
    {
        TFlatRepeated proto;
        NJson::TJsonValue modelJson(NJson::JSON_MAP);
        NJson::TJsonValue json;
        TProto2JsonConfig config;
        config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired;

        // SHould be same as MissingKeySkip
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config));
        UNIT_ASSERT_JSONS_EQUAL(json, modelJson);
    }
} // TestMissingRepeatedKeyConfig

Y_UNIT_TEST(TestEscaping) {
    // No escape
    {
        TString modelStr(R"_({"String":"value\""})_");

        TFlatOptional proto;
        proto.SetString(R"_(value")_");
        TStringStream jsonStr;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // TEscapeJTransform
    {
        TString modelStr(R"_({"String":"value\""})_");

        TFlatOptional proto;
        proto.SetString(R"_(value")_");
        TProto2JsonConfig config;
        config.StringTransforms.push_back(new TEscapeJTransform<false, true>());
        TStringStream jsonStr;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(modelStr, jsonStr.Str());
    }

    // TCEscapeTransform
    {
        TString modelStr(R"_({"String":"value\""})_");

        TFlatOptional proto;
        proto.SetString(R"_(value")_");
        TProto2JsonConfig config;
        config.StringTransforms.push_back(new TCEscapeTransform());
        TStringStream jsonStr;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // TSafeUtf8CEscapeTransform
    {
        TString modelStr(R"_({"String":"value\""})_");

        TFlatOptional proto;
        proto.SetString(R"_(value")_");
        TProto2JsonConfig config;
        config.StringTransforms.push_back(new TSafeUtf8CEscapeTransform());
        TStringStream jsonStr;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
} // TestEscaping

class TBytesTransform: public IStringTransform {
public:
    int GetType() const override {
        return 0;
    }
    void Transform(TString&) const override {
    }
    void TransformBytes(TString& str) const override {
        str = "bytes";
    }
};

Y_UNIT_TEST(TestBytesTransform) {
    // Test that string field is not changed
    {
        TString modelStr(R"_({"String":"value"})_");

        TFlatOptional proto;
        proto.SetString(R"_(value)_");
        TProto2JsonConfig config;
        config.StringTransforms.push_back(new TBytesTransform());
        TStringStream jsonStr;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Test that bytes field is changed
    {
        TString modelStr(R"_({"Bytes":"bytes"})_");

        TFlatOptional proto;
        proto.SetBytes(R"_(value)_");
        TProto2JsonConfig config;
        config.StringTransforms.push_back(new TBytesTransform());
        TStringStream jsonStr;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
}

Y_UNIT_TEST(TestFieldNameMode) {
    // Original case 1
    {
        TString modelStr(R"_({"String":"value"})_");

        TFlatOptional proto;
        proto.SetString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Original case 2
    {
        TString modelStr(R"_({"String":"value"})_");

        TFlatOptional proto;
        proto.SetString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameOriginalCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Lowercase
    {
        TString modelStr(R"_({"string":"value"})_");

        TFlatOptional proto;
        proto.SetString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameLowerCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Uppercase
    {
        TString modelStr(R"_({"STRING":"value"})_");

        TFlatOptional proto;
        proto.SetString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameUpperCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Camelcase
    {
        TString modelStr(R"_({"string":"value"})_");

        TFlatOptional proto;
        proto.SetString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
    {
        TString modelStr(R"_({"oneString":"value"})_");

        TFlatOptional proto;
        proto.SetOneString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
    {
        TString modelStr(R"_({"oneTwoString":"value"})_");

        TFlatOptional proto;
        proto.SetOneTwoString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // snake_case
    {
        TString modelStr(R"_({"string":"value"})_");

        TFlatOptional proto;
        proto.SetString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
    {
        TString modelStr(R"_({"one_string":"value"})_");

        TFlatOptional proto;
        proto.SetOneString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
    {
        TString modelStr(R"_({"one_two_string":"value"})_");

        TFlatOptional proto;
        proto.SetOneTwoString("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }
    {
        TString modelStr(R"_({"a_b_c":"value","user_i_d":"value"})_");

        TFlatOptional proto;
        proto.SetABC("value");
        proto.SetUserID("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // snake_case_dense
    {
        TString modelStr(R"_({"abc":"value","user_id":"value"})_");

        TFlatOptional proto;
        proto.SetABC("value");
        proto.SetUserID("value");
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCaseDense;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Original case, repeated
    {
        TString modelStr(R"_({"I32":[1,2]})_");

        TFlatRepeated proto;
        proto.AddI32(1);
        proto.AddI32(2);
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameOriginalCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // Lower case, repeated
    {
        TString modelStr(R"_({"i32":[1,2]})_");

        TFlatRepeated proto;
        proto.AddI32(1);
        proto.AddI32(2);
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.FieldNameMode = TProto2JsonConfig::FieldNameLowerCase;

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // UseJsonName
    {
        // FIXME(CONTRIB-139): see the comment about UseJsonName in json2proto_ut.cpp:
        // Def_upper json name should be "DefUpper".
        TString modelStr(R"_({"My-Upper":1,"my-lower":2,"defUpper":3,"defLower":4})_");

        TWithJsonName proto;
        proto.Setmy_upper(1);
        proto.SetMy_lower(2);
        proto.SetDef_upper(3);
        proto.Setdef_lower(4);
        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.SetUseJsonName(true);

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // UseJsonName and UseJsonEnumValue
    {
        TString modelStr(R"_({"json_enum":"enum_1"})_");

        TCustomJsonEnumValue proto;
        proto.SetJsonEnum(EJsonEnum::J_1);

        TStringStream jsonStr;
        TProto2JsonConfig config;
        config.SetUseJsonName(true);
        config.SetUseJsonEnumValue(true);

        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));
        UNIT_ASSERT_STRINGS_EQUAL(jsonStr.Str(), modelStr);
    }

    // FieldNameMode with UseJsonName
    {
        TProto2JsonConfig config;
        config.SetFieldNameMode(TProto2JsonConfig::FieldNameLowerCase);
        UNIT_ASSERT_EXCEPTION_CONTAINS(
            config.SetUseJsonName(true), yexception, "mutually exclusive");
    }
    {
        TProto2JsonConfig config;
        config.SetUseJsonName(true);
        UNIT_ASSERT_EXCEPTION_CONTAINS(
            config.SetFieldNameMode(TProto2JsonConfig::FieldNameLowerCase), yexception, "mutually exclusive");
    }

    /// TODO: test missing keys
} // TestFieldNameMode

Y_UNIT_TEST(TestNan) {
    TFlatOptional proto;
    proto.SetDouble(std::numeric_limits<double>::quiet_NaN());

    UNIT_ASSERT_EXCEPTION(Proto2Json(proto, TProto2JsonConfig()), yexception);
} // TestNan

Y_UNIT_TEST(TestInf) {
    TFlatOptional proto;
    proto.SetFloat(std::numeric_limits<float>::infinity());

    UNIT_ASSERT_EXCEPTION(Proto2Json(proto, TProto2JsonConfig()), yexception);
} // TestInf

Y_UNIT_TEST(TestMap) {
    TMapType proto;

    auto& items = *proto.MutableItems();
    items["key1"] = "value1";
    items["key2"] = "value2";
    items["key3"] = "value3";

    TString modelStr(R"_({"Items":[{"key":"key3","value":"value3"},{"key":"key2","value":"value2"},{"key":"key1","value":"value1"}]})_");

    TStringStream jsonStr;
    TProto2JsonConfig config;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));

    NJson::TJsonValue jsonValue, modelValue;
    NJson::TJsonValue::TArray jsonItems, modelItems;
    UNIT_ASSERT(NJson::ReadJsonTree(jsonStr.Str(), &jsonValue));
    UNIT_ASSERT(NJson::ReadJsonTree(modelStr, &modelValue));
    UNIT_ASSERT(jsonValue.Has("Items"));
    jsonValue["Items"].GetArray(&jsonItems);
    modelValue["Items"].GetArray(&modelItems);
    auto itemKey = [](const NJson::TJsonValue& v) {
        return v["key"].GetString();
    };
    SortBy(jsonItems, itemKey);
    SortBy(modelItems, itemKey);
    UNIT_ASSERT_EQUAL(jsonItems, modelItems);
} // TestMap

Y_UNIT_TEST(TestMapAsObject) {
    TMapType proto;

    auto& items = *proto.MutableItems();
    items["key1"] = "value1";
    items["key2"] = "value2";
    items["key3"] = "value3";

    TString modelStr(R"_({"Items":{"key3":"value3","key2":"value2","key1":"value1"}})_");

    TStringStream jsonStr;
    TProto2JsonConfig config;
    config.MapAsObject = true;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));

    UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
} // TestMapAsObject

Y_UNIT_TEST(TestMapWTF) {
    TMapType proto;

    auto& items = *proto.MutableItems();
    items["key1"] = "value1";
    items["key2"] = "value2";
    items["key3"] = "value3";

    TString modelStr(R"_({"Items":{"key3":"value3","key2":"value2","key1":"value1"}})_");

    TStringStream jsonStr;
    UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr));

    UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr);
} // TestMapWTF

Y_UNIT_TEST(TestStringifyNumbers) {
#define TEST_SINGLE(flag, field, value, expectString)                            \
    do {                                                                         \
        TFlatOptional proto;                                                     \
        proto.Set##field(value);                                                 \
                                                                                 \
        TStringStream jsonStr;                                                   \
        TProto2JsonConfig config;                                                \
        config.SetStringifyNumbers(flag);                                        \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));            \
        if (expectString) {                                                      \
            UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":\"" #value "\"}"); \
        } else {                                                                 \
            UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":" #value "}");     \
        }                                                                        \
    } while (false)

    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 10000000000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -1, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -1000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -10000000000000000, false);

    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 10000000000000000, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -1, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -1000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -10000000000000000, true);

    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1000000000, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 10000000000000000, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -1, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -1000000000, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -10000000000000000, true);


    TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 1, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI32, 1000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 10000000000000000, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI64, -1, true);
    TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI32, -1000000000, false);
    TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI64, -10000000000000000, true);

#undef TEST_SINGLE
} // TestStringifyNumbers

Y_UNIT_TEST(TestExtension) {
    TExtensionField proto;
    proto.SetExtension(bar, 1);

    Y_ASSERT(proto.HasExtension(bar));
    UNIT_ASSERT_EQUAL(Proto2Json(proto, TProto2JsonConfig()), "{\"NProtobufJsonTest.bar\":1}");


    TProto2JsonConfig cfg;
    cfg.SetExtensionFieldNameMode(TProto2JsonConfig::ExtFldNameShort);
    UNIT_ASSERT_EQUAL(Proto2Json(proto, cfg), "{\"bar\":1}");
} // TestExtension

Y_UNIT_TEST(TestSimplifiedDuration) {
    TString json;
    TSingleDuration simpleDuration;
    simpleDuration.mutable_duration()->set_seconds(10);
    simpleDuration.mutable_duration()->set_nanos(101);
    Proto2Json(simpleDuration, json, TProto2JsonConfig().SetConvertTimeAsString(true));
    UNIT_ASSERT_EQUAL_C(json, "{\"Duration\":\"10.000000101s\"}", "real value is " << json);
} // TestSimplifiedDuration

Y_UNIT_TEST(TestSimplifiedTimestamp) {
    TString json;
    TSingleTimestamp simpleTimestamp;
    simpleTimestamp.mutable_timestamp()->set_seconds(10000000);
    simpleTimestamp.mutable_timestamp()->set_nanos(504);
    Proto2Json(simpleTimestamp, json, TProto2JsonConfig().SetConvertTimeAsString(true));
    UNIT_ASSERT_EQUAL_C(json, "{\"Timestamp\":\"1970-04-26T17:46:40.000000504Z\"}", "real value is " << json);
} // TestSimplifiedTimestamp

Y_UNIT_TEST(TestFloatToString) {
#define TEST_SINGLE(mode, value, expectedValue)                                             \
    do {                                                                                    \
        TFlatOptional proto;                                                                \
        proto.SetFloat(value);                                                              \
                                                                                            \
        TStringStream jsonStr;                                                              \
        TProto2JsonConfig config;                                                           \
        config.SetFloatNDigits(3).SetFloatToStringMode(mode);                               \
        UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config));                       \
        TString expectedStr = TStringBuilder() << "{\"Float\":" << expectedValue << "}";    \
        UNIT_ASSERT_EQUAL_C(jsonStr.Str(), expectedStr, "real value is " << jsonStr.Str()); \
    } while (false)

    TEST_SINGLE(EFloatToStringMode::PREC_NDIGITS, 1234.18345, "1.23e+03");
    TEST_SINGLE(EFloatToStringMode::PREC_NDIGITS, 12.18345, "12.2");
    TEST_SINGLE(EFloatToStringMode::PREC_POINT_DIGITS, 12345.18355, "12345.184");
    TEST_SINGLE(EFloatToStringMode::PREC_POINT_DIGITS, 12.18355, "12.184");
    TEST_SINGLE(EFloatToStringMode::PREC_POINT_DIGITS, 12.18, "12.180");
    TEST_SINGLE(EFloatToStringMode::PREC_POINT_DIGITS_STRIP_ZEROES, 12345.18355, "12345.184");
    TEST_SINGLE(EFloatToStringMode::PREC_POINT_DIGITS_STRIP_ZEROES, 12.18355, "12.184");
    TEST_SINGLE(EFloatToStringMode::PREC_POINT_DIGITS_STRIP_ZEROES, 12.18, "12.18");

#undef TEST_SINGLE
} // TestFloatToString

} // TProto2JsonTest