Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to print enums as ints in Ruby JSON encoder #6384

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 16 additions & 6 deletions ruby/ext/google/protobuf_c/encode_decode.c
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1473,13 +1477,15 @@ 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);
Descriptor* desc = ruby_to_Descriptor(descriptor);
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) {
Expand All @@ -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;
Expand Down
41 changes: 27 additions & 14 deletions ruby/ext/google/protobuf_c/upb.c
Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions ruby/ext/google/protobuf_c/upb.h
Expand Up @@ -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<const Handlers> 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;

Expand All @@ -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
Expand All @@ -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<const Handlers> 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<const Handlers>(h, &h);
}
} /* namespace json */
Expand Down
28 changes: 28 additions & 0 deletions ruby/tests/encode_decode_test.rb
Expand Up @@ -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
Expand Down