Cherry-pick #8356 into 3.16.x (#8518)

* Ruby: Add support for proto3 json_name in compiler and field definitions

* Address review feedback

* Add test for json_name functionality

Co-authored-by: Lukas Fittl <lukas@fittl.com>
diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c
index 1d912c1..992f54b 100644
--- a/ruby/ext/google/protobuf_c/defs.c
+++ b/ruby/ext/google/protobuf_c/defs.c
@@ -868,6 +868,20 @@
   return Convert_UpbToRuby(default_val, TypeInfo_get(self->fielddef), Qnil);
 }
 
+
+/*
+ * call-seq:
+ *     FieldDescriptor.json_name => json_name
+ *
+ * Returns this field's json_name, as a Ruby string, or nil if not yet set.
+ */
+static VALUE FieldDescriptor_json_name(VALUE _self) {
+  FieldDescriptor* self = ruby_to_FieldDescriptor(_self);
+  const upb_fielddef *f = self->fielddef;
+  const char *json_name = upb_fielddef_jsonname(f);
+  return rb_str_new2(json_name);
+}
+
 /*
  * call-seq:
  *     FieldDescriptor.label => label
@@ -1043,6 +1057,7 @@
   rb_define_method(klass, "name", FieldDescriptor_name, 0);
   rb_define_method(klass, "type", FieldDescriptor__type, 0);
   rb_define_method(klass, "default", FieldDescriptor_default, 0);
+  rb_define_method(klass, "json_name", FieldDescriptor_json_name, 0);
   rb_define_method(klass, "label", FieldDescriptor_label, 0);
   rb_define_method(klass, "number", FieldDescriptor_number, 0);
   rb_define_method(klass, "submsg_name", FieldDescriptor_submsg_name, 0);
@@ -1750,6 +1765,16 @@
           field_proto,
           FileBuilderContext_strdup(self->file_builder, default_value));
     }
+
+    if (rb_funcall(options, rb_intern("key?"), 1,
+                   ID2SYM(rb_intern("json_name"))) == Qtrue) {
+      VALUE json_name =
+          rb_hash_lookup(options, ID2SYM(rb_intern("json_name")));
+
+      google_protobuf_FieldDescriptorProto_set_json_name(
+          field_proto,
+          FileBuilderContext_strdup(self->file_builder, json_name));
+    }
   }
 
   if (oneof_index >= 0) {
@@ -1899,18 +1924,20 @@
  */
 static VALUE MessageBuilderContext_repeated(int argc, VALUE* argv,
                                             VALUE _self) {
-  VALUE name, type, number, type_class;
+  VALUE name, type, number;
+  VALUE type_class, options = Qnil;
 
-  if (argc < 3) {
-    rb_raise(rb_eArgError, "Expected at least 3 arguments.");
+  rb_scan_args(argc, argv, "32", &name, &type, &number, &type_class, &options);
+
+  // Allow passing (name, type, number, options) or
+  // (name, type, number, type_class, options)
+  if (argc == 4 && RB_TYPE_P(type_class, T_HASH)) {
+    options = type_class;
+    type_class = Qnil;
   }
-  name = argv[0];
-  type = argv[1];
-  number = argv[2];
-  type_class = (argc > 3) ? argv[3] : Qnil;
 
   msgdef_add_field(_self, UPB_LABEL_REPEATED, name, type, number, type_class,
-                   Qnil, -1, false);
+                   options, -1, false);
 
   return Qnil;
 }
diff --git a/ruby/tests/encode_decode_test.rb b/ruby/tests/encode_decode_test.rb
index cce364d..429ac43 100755
--- a/ruby/tests/encode_decode_test.rb
+++ b/ruby/tests/encode_decode_test.rb
@@ -95,4 +95,10 @@
     end
   end
 
+  def test_json_name
+    msg = A::B::C::TestJsonName.new(:value => 42)
+    json = msg.to_json
+    assert_match json, "{\"CustomJsonName\":42}"
+  end
+
 end
diff --git a/ruby/tests/generated_code.proto b/ruby/tests/generated_code.proto
index e811669..bfdfa5a 100644
--- a/ruby/tests/generated_code.proto
+++ b/ruby/tests/generated_code.proto
@@ -83,3 +83,7 @@
   map<string, TestUnknown> map_unknown = 67;
   int32 unknown_field = 89;
 }
+
+message TestJsonName {
+  int32 value = 1 [json_name = "CustomJsonName"];
+}
diff --git a/src/google/protobuf/compiler/ruby/ruby_generator.cc b/src/google/protobuf/compiler/ruby/ruby_generator.cc
index a6935c7..cca69de 100644
--- a/src/google/protobuf/compiler/ruby/ruby_generator.cc
+++ b/src/google/protobuf/compiler/ruby/ruby_generator.cc
@@ -220,6 +220,11 @@
                      DefaultValueForField(field));
     }
 
+    if (field->has_json_name()) {
+      printer->Print(", json_name: \"$json_name$\"", "json_name",
+                    field->json_name());
+    }
+
     printer->Print("\n");
   }
 }