Просмотр исходного кода

Merge pull request #2815 from devwout/ruby_json_emit_defaults

Ruby version optionally emits default values in JSON encoding.
Joshua Haberman 8 лет назад
Родитель
Сommit
b28617b813
2 измененных файлов с 149 добавлено и 33 удалено
  1. 30 29
      ruby/ext/google/protobuf_c/encode_decode.c
  2. 119 4
      ruby/tests/basic.rb

+ 30 - 29
ruby/ext/google/protobuf_c/encode_decode.c

@@ -914,13 +914,9 @@ void stringsink_uninit(stringsink *sink) {
 // semantics, which means that we have true field presence, we will want to
 // modify msgvisitor so that it emits all present fields rather than all
 // non-default-value fields.
-//
-// Likewise, when implementing JSON serialization, we may need to have a
-// 'verbose' mode that outputs all fields and a 'concise' mode that outputs only
-// those with non-default values.
 
 static void putmsg(VALUE msg, const Descriptor* desc,
-                   upb_sink *sink, int depth);
+                   upb_sink *sink, int depth, bool emit_defaults);
 
 static upb_selector_t getsel(const upb_fielddef *f, upb_handlertype_t type) {
   upb_selector_t ret;
@@ -952,7 +948,7 @@ static void putstr(VALUE str, const upb_fielddef *f, upb_sink *sink) {
 }
 
 static void putsubmsg(VALUE submsg, const upb_fielddef *f, upb_sink *sink,
-                      int depth) {
+                      int depth, bool emit_defaults) {
   upb_sink subsink;
   VALUE descriptor;
   Descriptor* subdesc;
@@ -963,12 +959,12 @@ static void putsubmsg(VALUE submsg, const upb_fielddef *f, upb_sink *sink,
   subdesc = ruby_to_Descriptor(descriptor);
 
   upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink);
-  putmsg(submsg, subdesc, &subsink, depth + 1);
+  putmsg(submsg, subdesc, &subsink, depth + 1, emit_defaults);
   upb_sink_endsubmsg(sink, getsel(f, UPB_HANDLER_ENDSUBMSG));
 }
 
 static void putary(VALUE ary, const upb_fielddef *f, upb_sink *sink,
-                   int depth) {
+                   int depth, bool emit_defaults) {
   upb_sink subsink;
   upb_fieldtype_t type = upb_fielddef_type(f);
   upb_selector_t sel = 0;
@@ -1005,7 +1001,7 @@ static void putary(VALUE ary, const upb_fielddef *f, upb_sink *sink,
         putstr(*((VALUE *)memory), f, &subsink);
         break;
       case UPB_TYPE_MESSAGE:
-        putsubmsg(*((VALUE *)memory), f, &subsink, depth);
+        putsubmsg(*((VALUE *)memory), f, &subsink, depth, emit_defaults);
         break;
 
 #undef T
@@ -1019,7 +1015,8 @@ static void put_ruby_value(VALUE value,
                            const upb_fielddef *f,
                            VALUE type_class,
                            int depth,
-                           upb_sink *sink) {
+                           upb_sink *sink,
+                           bool emit_defaults) {
   upb_selector_t sel = 0;
   if (upb_fielddef_isprimitive(f)) {
     sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
@@ -1059,12 +1056,12 @@ static void put_ruby_value(VALUE value,
       putstr(value, f, sink);
       break;
     case UPB_TYPE_MESSAGE:
-      putsubmsg(value, f, sink, depth);
+      putsubmsg(value, f, sink, depth, emit_defaults);
   }
 }
 
 static void putmap(VALUE map, const upb_fielddef *f, upb_sink *sink,
-                   int depth) {
+                   int depth, bool emit_defaults) {
   Map* self;
   upb_sink subsink;
   const upb_fielddef* key_field;
@@ -1090,9 +1087,9 @@ static void putmap(VALUE map, const upb_fielddef *f, upb_sink *sink,
                          &entry_sink);
     upb_sink_startmsg(&entry_sink);
 
-    put_ruby_value(key, key_field, Qnil, depth + 1, &entry_sink);
+    put_ruby_value(key, key_field, Qnil, depth + 1, &entry_sink, emit_defaults);
     put_ruby_value(value, value_field, self->value_type_class, depth + 1,
-                   &entry_sink);
+                   &entry_sink, emit_defaults);
 
     upb_sink_endmsg(&entry_sink, &status);
     upb_sink_endsubmsg(&subsink, getsel(f, UPB_HANDLER_ENDSUBMSG));
@@ -1102,7 +1099,7 @@ static void putmap(VALUE map, const upb_fielddef *f, upb_sink *sink,
 }
 
 static void putmsg(VALUE msg_rb, const Descriptor* desc,
-                   upb_sink *sink, int depth) {
+                   upb_sink *sink, int depth, bool emit_defaults) {
   MessageHeader* msg;
   upb_msg_field_iter i;
   upb_status status;
@@ -1144,31 +1141,31 @@ static void putmsg(VALUE msg_rb, const Descriptor* desc,
 
     if (is_map_field(f)) {
       VALUE map = DEREF(msg, offset, VALUE);
-      if (map != Qnil) {
-        putmap(map, f, sink, depth);
+      if (map != Qnil || emit_defaults) {
+        putmap(map, f, sink, depth, emit_defaults);
       }
     } else if (upb_fielddef_isseq(f)) {
       VALUE ary = DEREF(msg, offset, VALUE);
       if (ary != Qnil) {
-        putary(ary, f, sink, depth);
+        putary(ary, f, sink, depth, emit_defaults);
       }
     } else if (upb_fielddef_isstring(f)) {
       VALUE str = DEREF(msg, offset, VALUE);
-      if (is_matching_oneof || RSTRING_LEN(str) > 0) {
+      if (is_matching_oneof || emit_defaults || RSTRING_LEN(str) > 0) {
         putstr(str, f, sink);
       }
     } else if (upb_fielddef_issubmsg(f)) {
-      putsubmsg(DEREF(msg, offset, VALUE), f, sink, depth);
+      putsubmsg(DEREF(msg, offset, VALUE), f, sink, depth, emit_defaults);
     } else {
       upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
 
-#define T(upbtypeconst, upbtype, ctype, default_value)                \
-  case upbtypeconst: {                                                \
-      ctype value = DEREF(msg, offset, ctype);                        \
-      if (is_matching_oneof || value != default_value) {              \
-        upb_sink_put##upbtype(sink, sel, value);                      \
-      }                                                               \
-    }                                                                 \
+#define T(upbtypeconst, upbtype, ctype, default_value)                    \
+  case upbtypeconst: {                                                    \
+      ctype value = DEREF(msg, offset, ctype);                            \
+      if (is_matching_oneof || emit_defaults || value != default_value) { \
+        upb_sink_put##upbtype(sink, sel, value);                          \
+      }                                                                   \
+    }                                                                     \
     break;
 
       switch (upb_fielddef_type(f)) {
@@ -1246,7 +1243,7 @@ VALUE Message_encode(VALUE klass, VALUE msg_rb) {
     stackenv_init(&se, "Error occurred during encoding: %s");
     encoder = upb_pb_encoder_create(&se.env, serialize_handlers, &sink.sink);
 
-    putmsg(msg_rb, desc, upb_pb_encoder_input(encoder), 0);
+    putmsg(msg_rb, desc, upb_pb_encoder_input(encoder), 0, false);
 
     ret = rb_str_new(sink.ptr, sink.len);
 
@@ -1268,6 +1265,7 @@ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) {
   Descriptor* desc = ruby_to_Descriptor(descriptor);
   VALUE msg_rb;
   VALUE preserve_proto_fieldnames = Qfalse;
+  VALUE emit_defaults = Qfalse;
   stringsink sink;
 
   if (argc < 1 || argc > 2) {
@@ -1283,6 +1281,9 @@ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) {
     }
     preserve_proto_fieldnames = rb_hash_lookup2(
         hash_args, ID2SYM(rb_intern("preserve_proto_fieldnames")), Qfalse);
+
+    emit_defaults = rb_hash_lookup2(
+        hash_args, ID2SYM(rb_intern("emit_defaults")), Qfalse);
   }
 
   stringsink_init(&sink);
@@ -1297,7 +1298,7 @@ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) {
     stackenv_init(&se, "Error occurred during encoding: %s");
     printer = upb_json_printer_create(&se.env, serialize_handlers, &sink.sink);
 
-    putmsg(msg_rb, desc, upb_json_printer_input(printer), 0);
+    putmsg(msg_rb, desc, upb_json_printer_input(printer), 0, RTEST(emit_defaults));
 
     ret = rb_enc_str_new(sink.ptr, sink.len, rb_utf8_encoding());
 

+ 119 - 4
ruby/tests/basic.rb

@@ -1,6 +1,7 @@
 #!/usr/bin/ruby
 
 require 'google/protobuf'
+require 'json'
 require 'test/unit'
 
 # ------------- generated code --------------
@@ -1184,21 +1185,135 @@ module BasicTest
       Foo.encode_json(Foo.new(bar: bar, baz: [baz1, baz2]))
     end
 
+    def test_json_emit_defaults
+      # TODO: Fix JSON in JRuby version.
+      return if RUBY_PLATFORM == "java"
+      m = TestMessage.new
+
+      expected = {
+        optionalInt32: 0,
+        optionalInt64: 0,
+        optionalUint32: 0,
+        optionalUint64: 0,
+        optionalBool: false,
+        optionalFloat: 0,
+        optionalDouble: 0,
+        optionalString: "",
+        optionalBytes: "",
+        optionalEnum: "Default",
+        repeatedInt32: [],
+        repeatedInt64: [],
+        repeatedUint32: [],
+        repeatedUint64: [],
+        repeatedBool: [],
+        repeatedFloat: [],
+        repeatedDouble: [],
+        repeatedString: [],
+        repeatedBytes: [],
+        repeatedMsg: [],
+        repeatedEnum: []
+      }
+
+      actual = TestMessage.encode_json(m, :emit_defaults => true)
+
+      assert JSON.parse(actual, :symbolize_names => true) == expected
+    end
+
+    def test_json_emit_defaults_submsg
+      # TODO: Fix JSON in JRuby version.
+      return if RUBY_PLATFORM == "java"
+      m = TestMessage.new(optional_msg: TestMessage2.new)
+
+      expected = {
+        optionalInt32: 0,
+        optionalInt64: 0,
+        optionalUint32: 0,
+        optionalUint64: 0,
+        optionalBool: false,
+        optionalFloat: 0,
+        optionalDouble: 0,
+        optionalString: "",
+        optionalBytes: "",
+        optionalMsg: {foo: 0},
+        optionalEnum: "Default",
+        repeatedInt32: [],
+        repeatedInt64: [],
+        repeatedUint32: [],
+        repeatedUint64: [],
+        repeatedBool: [],
+        repeatedFloat: [],
+        repeatedDouble: [],
+        repeatedString: [],
+        repeatedBytes: [],
+        repeatedMsg: [],
+        repeatedEnum: []
+      }
+
+      actual = TestMessage.encode_json(m, :emit_defaults => true)
+
+      assert JSON.parse(actual, :symbolize_names => true) == expected
+    end
+
+    def test_json_emit_defaults_repeated_submsg
+      # TODO: Fix JSON in JRuby version.
+      return if RUBY_PLATFORM == "java"
+      m = TestMessage.new(repeated_msg: [TestMessage2.new])
+
+      expected = {
+        optionalInt32: 0,
+        optionalInt64: 0,
+        optionalUint32: 0,
+        optionalUint64: 0,
+        optionalBool: false,
+        optionalFloat: 0,
+        optionalDouble: 0,
+        optionalString: "",
+        optionalBytes: "",
+        optionalEnum: "Default",
+        repeatedInt32: [],
+        repeatedInt64: [],
+        repeatedUint32: [],
+        repeatedUint64: [],
+        repeatedBool: [],
+        repeatedFloat: [],
+        repeatedDouble: [],
+        repeatedString: [],
+        repeatedBytes: [],
+        repeatedMsg: [{foo: 0}],
+        repeatedEnum: []
+      }
+
+      actual = TestMessage.encode_json(m, :emit_defaults => true)
+
+      assert JSON.parse(actual, :symbolize_names => true) == expected
+    end
+
     def test_json_maps
       # TODO: Fix JSON in JRuby version.
       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
+      expected = {mapStringInt32: {a: 1}, mapStringMsg: {}}
+      expected_preserve = {map_string_int32: {a: 1}, map_string_msg: {}}
+      assert JSON.parse(MapMessage.encode_json(m), :symbolize_names => true) == expected
 
       json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true)
-      assert json == expected_preserve
+      assert JSON.parse(json, :symbolize_names => true) == expected_preserve
 
       m2 = MapMessage.decode_json(MapMessage.encode_json(m))
       assert m == m2
     end
 
+    def test_json_maps_emit_defaults_submsg
+      # TODO: Fix JSON in JRuby version.
+      return if RUBY_PLATFORM == "java"
+      m = MapMessage.new(:map_string_msg => {"a" => TestMessage2.new})
+      expected = {mapStringInt32: {}, mapStringMsg: {a: {foo: 0}}}
+
+      actual = MapMessage.encode_json(m, :emit_defaults => true)
+
+      assert JSON.parse(actual, :symbolize_names => true) == expected
+    end
+
     def test_comparison_with_arbitrary_object
       assert MapMessage.new != nil
     end