diff --git a/ruby/ext/google/protobuf_c/encode_decode.c b/ruby/ext/google/protobuf_c/encode_decode.c index 4b79368552d3..e28a58e88c2c 100644 --- a/ruby/ext/google/protobuf_c/encode_decode.c +++ b/ruby/ext/google/protobuf_c/encode_decode.c @@ -1151,7 +1151,8 @@ static void putmap(VALUE map, const upb_fielddef *f, upb_sink *sink, } static const upb_handlers* msgdef_json_serialize_handlers( - Descriptor* desc, bool preserve_proto_fieldnames); + Descriptor* desc, bool preserve_proto_fieldnames, + bool always_show_enums_as_ints); static void putjsonany(VALUE msg_rb, const Descriptor* desc, upb_sink* sink, int depth, bool emit_defaults) { @@ -1227,7 +1228,7 @@ static void putjsonany(VALUE msg_rb, const Descriptor* desc, } subsink.handlers = - msgdef_json_serialize_handlers(payload_desc, true); + msgdef_json_serialize_handlers(payload_desc, true, true); subsink.closure = sink->closure; putmsg(payload_msg_rb, payload_desc, &subsink, depth, emit_defaults, true, is_wellknown); @@ -1411,19 +1412,22 @@ static const upb_handlers* msgdef_pb_serialize_handlers(Descriptor* desc) { } static const upb_handlers* msgdef_json_serialize_handlers( - Descriptor* desc, bool preserve_proto_fieldnames) { + Descriptor* desc, bool preserve_proto_fieldnames, + bool always_show_enums_as_ints) { if (preserve_proto_fieldnames) { if (desc->json_serialize_handlers == NULL) { desc->json_serialize_handlers = upb_json_printer_newhandlers( - desc->msgdef, true, &desc->json_serialize_handlers); + desc->msgdef, true, always_show_enums_as_ints, + &desc->json_serialize_handlers); } return desc->json_serialize_handlers; } else { if (desc->json_serialize_handlers_preserve == NULL) { desc->json_serialize_handlers_preserve = upb_json_printer_newhandlers( - desc->msgdef, false, &desc->json_serialize_handlers_preserve); + desc->msgdef, false, always_show_enums_as_ints, + &desc->json_serialize_handlers_preserve); } return desc->json_serialize_handlers_preserve; } @@ -1473,6 +1477,7 @@ VALUE Message_encode(VALUE klass, VALUE msg_rb) { * @param options [Hash] options for the decoder * preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase) * emit_defaults: set true to emit 0/false values (default is to omit them) + * always_show_enums_as_ints: set true to print enums as int values (default is strings) */ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) { VALUE descriptor = rb_ivar_get(klass, descriptor_instancevar_interned); @@ -1480,6 +1485,7 @@ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) { VALUE msg_rb; VALUE preserve_proto_fieldnames = Qfalse; VALUE emit_defaults = Qfalse; + VALUE always_show_enums_as_ints = Qfalse; stringsink sink; if (argc < 1 || argc > 2) { @@ -1498,13 +1504,17 @@ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) { emit_defaults = rb_hash_lookup2( hash_args, ID2SYM(rb_intern("emit_defaults")), Qfalse); + + always_show_enums_as_ints = rb_hash_lookup2( + hash_args, ID2SYM(rb_intern("always_show_enums_as_ints")), Qfalse); } stringsink_init(&sink); { const upb_handlers* serialize_handlers = - msgdef_json_serialize_handlers(desc, RTEST(preserve_proto_fieldnames)); + msgdef_json_serialize_handlers(desc, RTEST(preserve_proto_fieldnames), + RTEST(always_show_enums_as_ints)); upb_json_printer* printer; stackenv se; VALUE ret; diff --git a/ruby/ext/google/protobuf_c/upb.c b/ruby/ext/google/protobuf_c/upb.c index 3d96c5ccc70b..017cb873f33b 100644 --- a/ruby/ext/google/protobuf_c/upb.c +++ b/ruby/ext/google/protobuf_c/upb.c @@ -16371,6 +16371,7 @@ TYPE_HANDLERS_MAPKEY(uint64_t, fmt_uint64) typedef struct { void *keyname; const upb_enumdef *enumdef; + bool always_print_enums_as_ints; } EnumHandlerData; static bool scalar_enum(void *closure, const void *handler_data, @@ -16382,7 +16383,7 @@ static bool scalar_enum(void *closure, const void *handler_data, CHK(putkey(closure, hd->keyname)); symbolic_name = upb_enumdef_iton(hd->enumdef, val); - if (symbolic_name) { + if (symbolic_name && !hd->always_print_enums_as_ints) { print_data(p, "\"", 1); putstring(p, symbolic_name, strlen(symbolic_name)); print_data(p, "\"", 1); @@ -16395,9 +16396,10 @@ static bool scalar_enum(void *closure, const void *handler_data, static void print_enum_symbolic_name(upb_json_printer *p, const upb_enumdef *def, + bool always_print_enums_as_ints, int32_t val) { const char *symbolic_name = upb_enumdef_iton(def, val); - if (symbolic_name) { + if (symbolic_name && !always_print_enums_as_ints) { print_data(p, "\"", 1); putstring(p, symbolic_name, strlen(symbolic_name)); print_data(p, "\"", 1); @@ -16412,7 +16414,7 @@ static bool repeated_enum(void *closure, const void *handler_data, upb_json_printer *p = closure; print_comma(p); - print_enum_symbolic_name(p, hd->enumdef, val); + print_enum_symbolic_name(p, hd->enumdef, hd->always_print_enums_as_ints, val); return true; } @@ -16422,7 +16424,7 @@ static bool mapvalue_enum(void *closure, const void *handler_data, const EnumHandlerData *hd = handler_data; upb_json_printer *p = closure; - print_enum_symbolic_name(p, hd->enumdef, val); + print_enum_symbolic_name(p, hd->enumdef, hd->always_print_enums_as_ints, val); return true; } @@ -16684,10 +16686,12 @@ static size_t mapkey_bytes(void *closure, const void *handler_data, static void set_enum_hd(upb_handlers *h, const upb_fielddef *f, bool preserve_fieldnames, + bool always_print_enums_as_ints, upb_handlerattr *attr) { EnumHandlerData *hd = upb_gmalloc(sizeof(EnumHandlerData)); hd->enumdef = (const upb_enumdef *)upb_fielddef_subdef(f); hd->keyname = newstrpc(h, f, preserve_fieldnames); + hd->always_print_enums_as_ints = always_print_enums_as_ints; upb_handlers_addcleanup(h, hd, upb_gfree); upb_handlerattr_sethandlerdata(attr, hd); } @@ -16705,7 +16709,7 @@ static void set_enum_hd(upb_handlers *h, * field, and then one value field), so this is not a pressing concern at the * moment. */ void printer_sethandlers_mapentry(const void *closure, bool preserve_fieldnames, - upb_handlers *h) { + bool always_print_enums_as_ints, upb_handlers *h) { const upb_msgdef *md = upb_handlers_msgdef(h); /* A mapentry message is printed simply as '"key": value'. Rather than @@ -16779,7 +16783,7 @@ void printer_sethandlers_mapentry(const void *closure, bool preserve_fieldnames, break; case UPB_TYPE_ENUM: { upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER; - set_enum_hd(h, value_field, preserve_fieldnames, &enum_attr); + set_enum_hd(h, value_field, preserve_fieldnames, always_print_enums_as_ints, &enum_attr); upb_handlers_setint32(h, value_field, mapvalue_enum, &enum_attr); upb_handlerattr_uninit(&enum_attr); break; @@ -17295,13 +17299,15 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { bool is_mapentry = upb_msgdef_mapentry(md); upb_handlerattr empty_attr = UPB_HANDLERATTR_INITIALIZER; upb_msg_field_iter i; - const bool *preserve_fieldnames_ptr = closure; - const bool preserve_fieldnames = *preserve_fieldnames_ptr; + const bool **printer_options_ptr = closure; + const bool *printer_options = *printer_options_ptr; + const bool preserve_fieldnames = printer_options[0]; + const bool always_print_enums_as_ints = printer_options[1]; if (is_mapentry) { /* mapentry messages are sufficiently different that we handle them * separately. */ - printer_sethandlers_mapentry(closure, preserve_fieldnames, h); + printer_sethandlers_mapentry(closure, preserve_fieldnames, always_print_enums_as_ints, h); return; } @@ -17384,11 +17390,10 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { TYPE(UPB_TYPE_INT64, int64, int64_t); TYPE(UPB_TYPE_UINT64, uint64, uint64_t); case UPB_TYPE_ENUM: { - /* For now, we always emit symbolic names for enums. We may want an - * option later to control this behavior, but we will wait for a real - * need first. */ + /* By default, we always emit symbolic names for enums. The option + * 'enums-as-ints' can override this behaviour. */ upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER; - set_enum_hd(h, f, preserve_fieldnames, &enum_attr); + set_enum_hd(h, f, preserve_fieldnames, always_print_enums_as_ints, &enum_attr); if (upb_fielddef_isseq(f)) { upb_handlers_setint32(h, f, repeated_enum, &enum_attr); @@ -17469,9 +17474,17 @@ upb_sink *upb_json_printer_input(upb_json_printer *p) { const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md, bool preserve_fieldnames, + bool always_print_enums_as_ints, const void *owner) { + /* Allow for multiple json printing options by passing a pointer to an array + * of options. */ + bool json_options[2]; + json_options[0] = preserve_fieldnames; + json_options[1] = always_print_enums_as_ints; + bool *json_ptr = json_options; + return upb_handlers_newfrozen( - md, owner, printer_sethandlers, &preserve_fieldnames); + md, owner, printer_sethandlers, &json_ptr); } #undef UPB_SIZE diff --git a/ruby/ext/google/protobuf_c/upb.h b/ruby/ext/google/protobuf_c/upb.h index bb6a903c10d9..06ebe1f2fd83 100644 --- a/ruby/ext/google/protobuf_c/upb.h +++ b/ruby/ext/google/protobuf_c/upb.h @@ -10589,9 +10589,13 @@ class upb::json::Printer { /* Returns handlers for printing according to the specified schema. * If preserve_proto_fieldnames is true, the output JSON will use the * original .proto field names (ie. {"my_field":3}) instead of using - * camelCased names, which is the default: (eg. {"myField":3}). */ + * camelCased names, which is the default: (eg. {"myField":3}). + * If always_show_enums_as_ints is true, the output JSON will use the + * int value for enums (ie. {"foo":1} instead of the enum string, + * which is the default: (ie. {"foo":"bar"}). */ static reffed_ptr NewHandlers(const upb::MessageDef* md, - bool preserve_proto_fieldnames); + bool preserve_proto_fieldnames, + bool always_show_enums_as_ints); static const size_t kSize = UPB_JSON_PRINTER_SIZE; @@ -10609,6 +10613,7 @@ upb_json_printer *upb_json_printer_create(upb_env *e, const upb_handlers *h, upb_sink *upb_json_printer_input(upb_json_printer *p); const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md, bool preserve_fieldnames, + bool always_show_enums_as_ints, const void *owner); UPB_END_EXTERN_C @@ -10623,9 +10628,9 @@ inline Printer* Printer::Create(Environment* env, const upb::Handlers* handlers, } inline Sink* Printer::input() { return upb_json_printer_input(this); } inline reffed_ptr Printer::NewHandlers( - const upb::MessageDef *md, bool preserve_proto_fieldnames) { + const upb::MessageDef *md, bool preserve_proto_fieldnames, bool always_show_enums_as_ints) { const Handlers* h = upb_json_printer_newhandlers( - md, preserve_proto_fieldnames, &h); + md, preserve_proto_fieldnames, always_show_enums_as_ints, &h); return reffed_ptr(h, &h); } } /* namespace json */ diff --git a/ruby/tests/encode_decode_test.rb b/ruby/tests/encode_decode_test.rb index d3cebab215fa..017389473180 100644 --- a/ruby/tests/encode_decode_test.rb +++ b/ruby/tests/encode_decode_test.rb @@ -83,6 +83,34 @@ def test_encode_json ) assert_match 'optional_int32', json + + # Test for enums printing as ints. + msg = A::B::C::TestMessage.new({ optional_enum: 1 }) + json = A::B::C::TestMessage.encode_json( + msg, + { :always_show_enums_as_ints => true } + ) + + assert_match '"optionalEnum":1', json + + # Test for default enum being printed as int. + msg = A::B::C::TestMessage.new({ optional_enum: 0 }) + json = A::B::C::TestMessage.encode_json( + msg, + { :always_show_enums_as_ints => true, :emit_defaults => true } + ) + + assert_match '"optionalEnum":0', json + + # Test for repeated enums printing as ints. + msg = A::B::C::TestMessage.new({ repeated_enum: [0,1,2,3] }) + json = A::B::C::TestMessage.encode_json( + msg, + { :always_show_enums_as_ints => true } + ) + + assert_match '"repeatedEnum":[0,1,2,3]', json + end def test_encode_wrong_msg