Support hashes for struct initializers (#5716)

* support hashes for struct initalizers

* convert hash keys to string

* update tests

* add extra asserts
diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c
index cf1d6e3..fb25efe 100644
--- a/ruby/ext/google/protobuf_c/map.c
+++ b/ruby/ext/google/protobuf_c/map.c
@@ -71,6 +71,9 @@
     case UPB_TYPE_BYTES:
     case UPB_TYPE_STRING:
       // Strings: use string content directly.
+      if (TYPE(key) == T_SYMBOL) {
+        key = rb_id2str(SYM2ID(key));
+      }
       Check_Type(key, T_STRING);
       key = native_slot_encode_and_freeze_string(self->key_type, key);
       *out_key = RSTRING_PTR(key);
@@ -397,6 +400,11 @@
   void* mem;
   key = table_key(self, key, keybuf, &keyval, &length);
 
+  if (TYPE(value) == T_HASH) {
+    VALUE args[1] = { value };
+    value = rb_class_new_instance(1, args, self->value_type_class);
+  }
+
   mem = value_memory(&v);
   native_slot_set("", self->value_type, self->value_type_class, mem, value);
 
diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb
index 9ec738b..f1b1c6f 100644
--- a/ruby/tests/basic.rb
+++ b/ruby/tests/basic.rb
@@ -204,6 +204,20 @@
       end
     end
 
+    def test_map_field_with_symbol
+      m = MapMessage.new
+      assert m.map_string_int32 == {}
+      assert m.map_string_msg == {}
+
+      m = MapMessage.new(
+        :map_string_int32 => {a: 1, "b" => 2},
+        :map_string_msg => {a: TestMessage2.new(:foo => 1),
+                            b: TestMessage2.new(:foo => 10)})
+      assert_equal 1, m.map_string_int32[:a]
+      assert_equal 2, m.map_string_int32[:b]
+      assert_equal 10, m.map_string_msg[:b].foo
+    end
+
     def test_map_inspect
       m = MapMessage.new(
         :map_string_int32 => {"a" => 1, "b" => 2},
diff --git a/ruby/tests/well_known_types_test.rb b/ruby/tests/well_known_types_test.rb
index f35f7b1..3eafe09 100644
--- a/ruby/tests/well_known_types_test.rb
+++ b/ruby/tests/well_known_types_test.rb
@@ -139,4 +139,58 @@
     assert any.is(Google::Protobuf::Timestamp)
     assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
   end
+
+  def test_struct_init
+    s = Google::Protobuf::Struct.new(fields: {'a' => Google::Protobuf::Value.new({number_value: 4.4})})
+    assert_equal 4.4, s['a']
+
+    s = Google::Protobuf::Struct.new(fields: {'a' => {number_value: 2.2}})
+    assert_equal 2.2, s['a']
+
+    s = Google::Protobuf::Struct.new(fields: {a: {number_value: 1.1}})
+    assert_equal 1.1, s[:a]
+  end
+
+  def test_struct_nested_init
+    s = Google::Protobuf::Struct.new(
+      fields: {
+        'a' => {string_value: 'A'},
+        'b' => {struct_value: {
+          fields: {
+            'x' => {list_value: {values: [{number_value: 1.0}, {string_value: "ok"}]}},
+            'y' => {bool_value: true}}}
+        },
+        'c' => {struct_value: {}}
+      }
+    )
+    assert_equal 'A', s['a']
+    assert_equal 'A', s[:a]
+    expected_b_x = [Google::Protobuf::Value.new(number_value: 1.0), Google::Protobuf::Value.new(string_value: "ok")]
+    assert_equal expected_b_x, s['b']['x'].values
+    assert_equal expected_b_x, s[:b][:x].values
+    assert_equal expected_b_x, s['b'][:x].values
+    assert_equal expected_b_x, s[:b]['x'].values
+    assert_equal true, s['b']['y']
+    assert_equal true, s[:b][:y]
+    assert_equal true, s[:b]['y']
+    assert_equal true, s['b'][:y]
+    assert_equal Google::Protobuf::Struct.new, s['c']
+    assert_equal Google::Protobuf::Struct.new, s[:c]
+
+    s = Google::Protobuf::Struct.new(
+      fields: {
+        a: {string_value: 'Eh'},
+        b: {struct_value: {
+          fields: {
+            y: {bool_value: false}}}
+        }
+      }
+    )
+    assert_equal 'Eh', s['a']
+    assert_equal 'Eh', s[:a]
+    assert_equal false, s['b']['y']
+    assert_equal false, s[:b][:y]
+    assert_equal false, s['b'][:y]
+    assert_equal false, s[:b]['y']
+  end
 end