diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c index 12f1f9ddd..4be54c39d 100644 --- a/ruby/ext/google/protobuf_c/map.c +++ b/ruby/ext/google/protobuf_c/map.c @@ -652,6 +652,35 @@ VALUE Map_hash(VALUE _self) { return INT2FIX(h); } +/* + * call-seq: + * Map.to_h => {} + * + * Returns a Ruby Hash object containing all the values within the map + */ +VALUE Map_to_h(VALUE _self) { + Map* self = ruby_to_Map(_self); + VALUE hash = rb_hash_new(); + upb_strtable_iter it; + for (upb_strtable_begin(&it, &self->table); + !upb_strtable_done(&it); + upb_strtable_next(&it)) { + VALUE key = table_key_to_ruby( + self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it)); + upb_value v = upb_strtable_iter_value(&it); + void* mem = value_memory(&v); + VALUE value = native_slot_get(self->value_type, + self->value_type_class, + mem); + + if (self->value_type == UPB_TYPE_MESSAGE) { + value = Message_to_h(value); + } + rb_hash_aset(hash, key, value); + } + return hash; +} + /* * call-seq: * Map.inspect => string @@ -804,6 +833,8 @@ void Map_register(VALUE module) { rb_define_method(klass, "dup", Map_dup, 0); rb_define_method(klass, "==", Map_eq, 1); rb_define_method(klass, "hash", Map_hash, 0); + rb_define_method(klass, "to_hash", Map_to_h, 0); + rb_define_method(klass, "to_h", Map_to_h, 0); rb_define_method(klass, "inspect", Map_inspect, 0); rb_define_method(klass, "merge", Map_merge, 1); rb_include_module(klass, rb_mEnumerable); diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index 837a974b8..299111404 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -394,7 +394,12 @@ VALUE Message_inspect(VALUE _self) { return str; } - +/* + * call-seq: + * Message.to_h => {} + * + * Returns the message as a Ruby Hash object, with keys as symbols. + */ VALUE Message_to_h(VALUE _self) { MessageHeader* self; VALUE hash; @@ -410,8 +415,13 @@ VALUE Message_to_h(VALUE _self) { VALUE msg_value = layout_get(self->descriptor->layout, Message_data(self), field); VALUE msg_key = ID2SYM(rb_intern(upb_fielddef_name(field))); - if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) { + if (upb_fielddef_ismap(field)) { + msg_value = Map_to_h(msg_value); + } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) { msg_value = RepeatedField_to_ary(msg_value); + } else if (msg_value != Qnil && + upb_fielddef_type(field) == UPB_TYPE_MESSAGE) { + msg_value = Message_to_h(msg_value); } rb_hash_aset(hash, msg_key, msg_value); } diff --git a/ruby/ext/google/protobuf_c/protobuf.h b/ruby/ext/google/protobuf_c/protobuf.h index d5ced5672..520e9d9b8 100644 --- a/ruby/ext/google/protobuf_c/protobuf.h +++ b/ruby/ext/google/protobuf_c/protobuf.h @@ -424,6 +424,7 @@ VALUE Map_dup(VALUE _self); VALUE Map_deep_copy(VALUE _self); VALUE Map_eq(VALUE _self, VALUE _other); VALUE Map_hash(VALUE _self); +VALUE Map_to_h(VALUE _self); VALUE Map_inspect(VALUE _self); VALUE Map_merge(VALUE _self, VALUE hashmap); VALUE Map_merge_into_self(VALUE _self, VALUE hashmap); @@ -496,6 +497,7 @@ VALUE Message_deep_copy(VALUE _self); VALUE Message_eq(VALUE _self, VALUE _other); VALUE Message_hash(VALUE _self); VALUE Message_inspect(VALUE _self); +VALUE Message_to_h(VALUE _self); VALUE Message_index(VALUE _self, VALUE field_name); VALUE Message_index_set(VALUE _self, VALUE field_name, VALUE value); VALUE Message_descriptor(VALUE klass); diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb index ca81e3a55..ff7576b8b 100644 --- a/ruby/tests/basic.rb +++ b/ruby/tests/basic.rb @@ -927,6 +927,16 @@ module BasicTest :repeated_uint64=>[] } assert_equal expected_result, m.to_h + + m = MapMessage.new( + :map_string_int32 => {"a" => 1, "b" => 2}, + :map_string_msg => {"a" => TestMessage2.new(:foo => 1), + "b" => TestMessage2.new(:foo => 2)}) + expected_result = { + :map_string_int32=>{"a"=>1, "b"=>2}, + :map_string_msg=>{"a"=>{:foo=>1}, "b"=>{:foo=>2}} + } + assert_equal expected_result, m.to_h end