From 194ad621bb7260c4f2f27f3575ce21ab946b786c Mon Sep 17 00:00:00 2001 From: Josh Haberman Date: Thu, 14 Apr 2016 18:33:17 -0700 Subject: Ruby JSON: always accept both camelCase and original field names. For JSON encoding we provide a new option to decide at encode time whether to use camelCase or original proto field names: json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true) --- ruby/ext/google/protobuf_c/defs.c | 5 ++++ ruby/ext/google/protobuf_c/encode_decode.c | 45 ++++++++++++++++++++++++------ ruby/ext/google/protobuf_c/message.c | 2 +- ruby/ext/google/protobuf_c/protobuf.h | 3 +- ruby/ext/google/protobuf_c/upb.c | 41 +++++++++++++++------------ ruby/ext/google/protobuf_c/upb.h | 14 +++++++--- 6 files changed, 77 insertions(+), 33 deletions(-) (limited to 'ruby/ext/google/protobuf_c') diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index 96ef4953..7e93bafb 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -255,6 +255,10 @@ void Descriptor_free(void* _self) { upb_handlers_unref(self->json_serialize_handlers, &self->json_serialize_handlers); } + if (self->json_serialize_handlers_preserve) { + upb_handlers_unref(self->json_serialize_handlers_preserve, + &self->json_serialize_handlers_preserve); + } xfree(self); } @@ -278,6 +282,7 @@ VALUE Descriptor_alloc(VALUE klass) { self->json_fill_method = NULL; self->pb_serialize_handlers = NULL; self->json_serialize_handlers = NULL; + self->json_serialize_handlers_preserve = NULL; self->typeclass_references = rb_ary_new(); return ret; } diff --git a/ruby/ext/google/protobuf_c/encode_decode.c b/ruby/ext/google/protobuf_c/encode_decode.c index c2c369eb..9bc7273e 100644 --- a/ruby/ext/google/protobuf_c/encode_decode.c +++ b/ruby/ext/google/protobuf_c/encode_decode.c @@ -1130,13 +1130,23 @@ static const upb_handlers* msgdef_pb_serialize_handlers(Descriptor* desc) { return desc->pb_serialize_handlers; } -static const upb_handlers* msgdef_json_serialize_handlers(Descriptor* desc) { - if (desc->json_serialize_handlers == NULL) { - desc->json_serialize_handlers = - upb_json_printer_newhandlers( - desc->msgdef, &desc->json_serialize_handlers); +static const upb_handlers* msgdef_json_serialize_handlers( + Descriptor* desc, bool preserve_proto_fieldnames) { + 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); + } + 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); + } + return desc->json_serialize_handlers_preserve; } - return desc->json_serialize_handlers; } /* @@ -1181,16 +1191,33 @@ VALUE Message_encode(VALUE klass, VALUE msg_rb) { * * Encodes the given message object into its serialized JSON representation. */ -VALUE Message_encode_json(VALUE klass, VALUE msg_rb) { +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; stringsink sink; + + if (argc < 1 || argc > 2) { + rb_raise(rb_eArgError, "Expected 1 or 2 arguments."); + } + + msg_rb = argv[0]; + + if (argc == 2) { + VALUE hash_args = argv[1]; + if (TYPE(hash_args) != T_HASH) { + rb_raise(rb_eArgError, "Expected hash arguments."); + } + preserve_proto_fieldnames = rb_hash_lookup2( + hash_args, ID2SYM(rb_intern("preserve_proto_fieldnames")), Qfalse); + } + stringsink_init(&sink); { const upb_handlers* serialize_handlers = - msgdef_json_serialize_handlers(desc); + msgdef_json_serialize_handlers(desc, RTEST(preserve_proto_fieldnames)); upb_json_printer* printer; stackenv se; VALUE ret; diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index 283939c9..3a51fe47 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -475,7 +475,7 @@ VALUE build_class_from_descriptor(Descriptor* desc) { rb_define_singleton_method(klass, "decode", Message_decode, 1); rb_define_singleton_method(klass, "encode", Message_encode, 1); rb_define_singleton_method(klass, "decode_json", Message_decode_json, 1); - rb_define_singleton_method(klass, "encode_json", Message_encode_json, 1); + rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1); rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0); return klass; diff --git a/ruby/ext/google/protobuf_c/protobuf.h b/ruby/ext/google/protobuf_c/protobuf.h index 21ce7bb3..2834c894 100644 --- a/ruby/ext/google/protobuf_c/protobuf.h +++ b/ruby/ext/google/protobuf_c/protobuf.h @@ -115,6 +115,7 @@ struct Descriptor { const upb_json_parsermethod* json_fill_method; const upb_handlers* pb_serialize_handlers; const upb_handlers* json_serialize_handlers; + const upb_handlers* json_serialize_handlers_preserve; // Handlers hold type class references for sub-message fields directly in some // cases. We need to keep these rooted because they might otherwise be // collected. @@ -498,7 +499,7 @@ VALUE Message_descriptor(VALUE klass); VALUE Message_decode(VALUE klass, VALUE data); VALUE Message_encode(VALUE klass, VALUE msg_rb); VALUE Message_decode_json(VALUE klass, VALUE data); -VALUE Message_encode_json(VALUE klass, VALUE msg_rb); +VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass); VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj); diff --git a/ruby/ext/google/protobuf_c/upb.c b/ruby/ext/google/protobuf_c/upb.c index db84ae3f..eac19f71 100644 --- a/ruby/ext/google/protobuf_c/upb.c +++ b/ruby/ext/google/protobuf_c/upb.c @@ -11624,7 +11624,7 @@ _again: #line 1270 "upb/json/parser.rl" if (p != pe) { - upb_status_seterrf(&parser->status, "Parse error at %s\n", p); + upb_status_seterrf(&parser->status, "Parse error at '%.*s'\n", p, pe - p); upb_env_reporterror(parser->env, &parser->status); } else { capture_suspend(parser, &p); @@ -11725,6 +11725,7 @@ static void add_jsonname_table(upb_json_parsermethod *m, const upb_msgdef* md) { upb_msg_field_next(&i)) { const upb_fielddef *f = upb_msg_iter_field(&i); + /* Add an entry for the JSON name. */ size_t field_len = upb_fielddef_getjsonname(f, buf, len); if (field_len > len) { size_t len2; @@ -11735,10 +11736,10 @@ static void add_jsonname_table(upb_json_parsermethod *m, const upb_msgdef* md) { } upb_strtable_insert(t, buf, upb_value_constptr(f)); - if (getenv("UPB_JSON_ACCEPT_LEGACY_FIELD_NAMES")) { - /* Temporary code to help people migrate if they were depending on the - * old, non-proto3-json-compliant field names. In this case we - * recognize both old names and new names. */ + if (strcmp(buf, upb_fielddef_name(f)) != 0) { + /* Since the JSON name is different from the regular field name, add an + * entry for the raw name (compliant proto3 JSON parsers must accept + * both). */ upb_strtable_insert(t, upb_fielddef_name(f), upb_value_constptr(f)); } @@ -11853,12 +11854,11 @@ void freestrpc(void *ptr) { } /* Convert fielddef name to JSON name and return as a string piece. */ -strpc *newstrpc(upb_handlers *h, const upb_fielddef *f) { +strpc *newstrpc(upb_handlers *h, const upb_fielddef *f, + bool preserve_fieldnames) { /* TODO(haberman): handle malloc failure. */ strpc *ret = malloc(sizeof(*ret)); - if (getenv("UPB_JSON_WRITE_LEGACY_FIELD_NAMES")) { - /* Temporary code to help people migrate if they were depending on the - * old, non-proto3-json-compliant field names. */ + if (preserve_fieldnames) { ret->ptr = upb_strdup(upb_fielddef_name(f)); ret->len = strlen(ret->ptr); } else { @@ -12374,10 +12374,11 @@ 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, upb_handlerattr *attr) { EnumHandlerData *hd = malloc(sizeof(EnumHandlerData)); hd->enumdef = (const upb_enumdef *)upb_fielddef_subdef(f); - hd->keyname = newstrpc(h, f); + hd->keyname = newstrpc(h, f, preserve_fieldnames); upb_handlers_addcleanup(h, hd, free); upb_handlerattr_sethandlerdata(attr, hd); } @@ -12394,7 +12395,8 @@ static void set_enum_hd(upb_handlers *h, * our sources that emit mapentry messages do so canonically (with one key * field, and then one value field), so this is not a pressing concern at the * moment. */ -void printer_sethandlers_mapentry(const void *closure, upb_handlers *h) { +void printer_sethandlers_mapentry(const void *closure, bool preserve_fieldnames, + upb_handlers *h) { const upb_msgdef *md = upb_handlers_msgdef(h); /* A mapentry message is printed simply as '"key": value'. Rather than @@ -12468,7 +12470,7 @@ void printer_sethandlers_mapentry(const void *closure, upb_handlers *h) { break; case UPB_TYPE_ENUM: { upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER; - set_enum_hd(h, value_field, &enum_attr); + set_enum_hd(h, value_field, preserve_fieldnames, &enum_attr); upb_handlers_setint32(h, value_field, mapvalue_enum, &enum_attr); upb_handlerattr_uninit(&enum_attr); break; @@ -12487,13 +12489,13 @@ 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; - - UPB_UNUSED(closure); + const bool *preserve_fieldnames_ptr = closure; + const bool preserve_fieldnames = *preserve_fieldnames_ptr; if (is_mapentry) { /* mapentry messages are sufficiently different that we handle them * separately. */ - printer_sethandlers_mapentry(closure, h); + printer_sethandlers_mapentry(closure, preserve_fieldnames, h); return; } @@ -12514,7 +12516,8 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { const upb_fielddef *f = upb_msg_iter_field(&i); upb_handlerattr name_attr = UPB_HANDLERATTR_INITIALIZER; - upb_handlerattr_sethandlerdata(&name_attr, newstrpc(h, f)); + upb_handlerattr_sethandlerdata(&name_attr, + newstrpc(h, f, preserve_fieldnames)); if (upb_fielddef_ismap(f)) { upb_handlers_setstartseq(h, f, startmap, &name_attr); @@ -12537,7 +12540,7 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { * option later to control this behavior, but we will wait for a real * need first. */ upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER; - set_enum_hd(h, f, &enum_attr); + set_enum_hd(h, f, preserve_fieldnames, &enum_attr); if (upb_fielddef_isseq(f)) { upb_handlers_setint32(h, f, repeated_enum, &enum_attr); @@ -12614,6 +12617,8 @@ upb_sink *upb_json_printer_input(upb_json_printer *p) { } const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md, + bool preserve_fieldnames, const void *owner) { - return upb_handlers_newfrozen(md, owner, printer_sethandlers, NULL); + return upb_handlers_newfrozen( + md, owner, printer_sethandlers, &preserve_fieldnames); } diff --git a/ruby/ext/google/protobuf_c/upb.h b/ruby/ext/google/protobuf_c/upb.h index 6cea1068..b0058161 100644 --- a/ruby/ext/google/protobuf_c/upb.h +++ b/ruby/ext/google/protobuf_c/upb.h @@ -8370,8 +8370,12 @@ class upb::json::Printer { /* The input to the printer. */ Sink* input(); - /* Returns handlers for printing according to the specified schema. */ - static reffed_ptr NewHandlers(const upb::MessageDef* md); + /* 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}). */ + static reffed_ptr NewHandlers(const upb::MessageDef* md, + bool preserve_proto_fieldnames); static const size_t kSize = UPB_JSON_PRINTER_SIZE; @@ -8388,6 +8392,7 @@ upb_json_printer *upb_json_printer_create(upb_env *e, const upb_handlers *h, upb_bytessink *output); upb_sink *upb_json_printer_input(upb_json_printer *p); const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md, + bool preserve_fieldnames, const void *owner); UPB_END_EXTERN_C @@ -8402,8 +8407,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) { - const Handlers* h = upb_json_printer_newhandlers(md, &h); + const upb::MessageDef *md, bool preserve_proto_fieldnames) { + const Handlers* h = upb_json_printer_newhandlers( + md, preserve_proto_fieldnames, &h); return reffed_ptr(h, &h); } } /* namespace json */ -- cgit v1.2.3