瀏覽代碼

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)
Josh Haberman 9 年之前
父節點
當前提交
194ad621bb

+ 5 - 0
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;
 }

+ 36 - 9
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;

+ 1 - 1
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;

+ 2 - 1
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);
 

+ 23 - 18
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);
 }

+ 10 - 4
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<const Handlers> 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<const Handlers> 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<const Handlers> 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<const Handlers>(h, &h);
 }
 }  /* namespace json */

+ 5 - 0
ruby/tests/basic.rb

@@ -1161,7 +1161,12 @@ module BasicTest
       return if RUBY_PLATFORM == "java"
       m = MapMessage.new(:map_string_int32 => {"a" => 1})
       expected = '{"mapStringInt32":{"a":1},"mapStringMsg":{}}'
+      expected_preserve = '{"map_string_int32":{"a":1},"map_string_msg":{}}'
       assert MapMessage.encode_json(m) == expected
+
+      json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true)
+      assert json == expected_preserve
+
       m2 = MapMessage.decode_json(MapMessage.encode_json(m))
       assert m == m2
     end