From 1eee3202fce4953a9c0c3adbf357ed10bd71e01e Mon Sep 17 00:00:00 2001 From: Brendan McCarthy Date: Fri, 17 Mar 2017 18:46:18 +1000 Subject: [PATCH 1/2] Add use_snake_case_for_field_names option to JsonPrintOptions --- src/google/protobuf/util/internal/json_objectwriter.cc | 10 ++++++++-- src/google/protobuf/util/internal/json_objectwriter.h | 8 ++++++++ src/google/protobuf/util/json_util.cc | 1 + src/google/protobuf/util/json_util.h | 5 ++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/google/protobuf/util/internal/json_objectwriter.cc b/src/google/protobuf/util/internal/json_objectwriter.cc index 6e4edd8836..c36459a4f9 100644 --- a/src/google/protobuf/util/internal/json_objectwriter.cc +++ b/src/google/protobuf/util/internal/json_objectwriter.cc @@ -176,8 +176,14 @@ void JsonObjectWriter::WritePrefix(StringPiece name) { if (!name.empty() || empty_key_ok) { WriteChar('"'); if (!name.empty()) { - ArrayByteSource source(name); - JsonEscaping::Escape(&source, &sink_); + if (use_snake_case_for_field_names_) { + string snake_name = ToSnakeCase(name); + ArrayByteSource source(snake_name); + JsonEscaping::Escape(&source, &sink_); + } else { + ArrayByteSource source(name); + JsonEscaping::Escape(&source, &sink_); + } } stream_->WriteString("\":"); if (!indent_string_.empty()) WriteChar(' '); diff --git a/src/google/protobuf/util/internal/json_objectwriter.h b/src/google/protobuf/util/internal/json_objectwriter.h index 31edc2927f..fbef54618b 100644 --- a/src/google/protobuf/util/internal/json_objectwriter.h +++ b/src/google/protobuf/util/internal/json_objectwriter.h @@ -94,6 +94,7 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { sink_(out), indent_string_(indent_string.ToString()), use_websafe_base64_for_bytes_(false), + use_snake_case_for_field_names_(false), empty_name_ok_for_next_key_(false) {} virtual ~JsonObjectWriter(); @@ -118,6 +119,10 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { use_websafe_base64_for_bytes_ = value; } + void set_use_snake_case_for_field_names(bool value) { + use_snake_case_for_field_names_ = value; + } + // Whether empty strings should be rendered for the next JSON key. This // setting is only valid until the next key is rendered, after which it gets // reset to false. @@ -217,6 +222,9 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { // to regular base64 encoding. bool use_websafe_base64_for_bytes_; + // Whether to use snake_case or lowerCamelCase for field names + bool use_snake_case_for_field_names_; + // Whether empty strings should be rendered for the next JSON key. This // setting is only valid until the next key is rendered, after which it gets // reset to false. diff --git a/src/google/protobuf/util/json_util.cc b/src/google/protobuf/util/json_util.cc index 129b6eaf6c..c86cb8cb5f 100644 --- a/src/google/protobuf/util/json_util.cc +++ b/src/google/protobuf/util/json_util.cc @@ -86,6 +86,7 @@ util::Status BinaryToJsonStream(TypeResolver* resolver, io::CodedOutputStream out_stream(json_output); converter::JsonObjectWriter json_writer(options.add_whitespace ? " " : "", &out_stream); + json_writer.set_use_snake_case_for_field_names(options.use_snake_case_for_field_names); if (options.always_print_primitive_fields) { converter::DefaultValueObjectWriter default_value_writer( resolver, type, &json_writer); diff --git a/src/google/protobuf/util/json_util.h b/src/google/protobuf/util/json_util.h index 53b1d1ba57..439d137f2b 100644 --- a/src/google/protobuf/util/json_util.h +++ b/src/google/protobuf/util/json_util.h @@ -64,10 +64,13 @@ struct JsonPrintOptions { // Whether to always print enums as ints. By default they are rendered as // strings. bool always_print_enums_as_ints; + // Whether to convert field names to snake case + bool use_snake_case_for_field_names; JsonPrintOptions() : add_whitespace(false), always_print_primitive_fields(false), - always_print_enums_as_ints(false) { + always_print_enums_as_ints(false), + use_snake_case_for_field_names(false) { } }; From 89eb4e51b24f7417224b47faf32503d11b6b1bc0 Mon Sep 17 00:00:00 2001 From: Brendan McCarthy Date: Fri, 17 Mar 2017 22:47:38 +1000 Subject: [PATCH 2/2] Add option to preserve original proto field names --- .../internal/default_value_objectwriter.cc | 22 ++++++++--- .../internal/default_value_objectwriter.h | 11 +++++- .../util/internal/json_objectwriter.cc | 10 +---- .../util/internal/json_objectwriter.h | 8 ---- .../util/internal/protostream_objectsource.cc | 8 +++- .../util/internal/protostream_objectsource.h | 8 ++++ src/google/protobuf/util/json_util.cc | 5 ++- src/google/protobuf/util/json_util.h | 6 +-- src/google/protobuf/util/json_util_test.cc | 37 +++++++++++++++++++ 9 files changed, 87 insertions(+), 28 deletions(-) diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.cc b/src/google/protobuf/util/internal/default_value_objectwriter.cc index 1772219ab2..6b0c523437 100644 --- a/src/google/protobuf/util/internal/default_value_objectwriter.cc +++ b/src/google/protobuf/util/internal/default_value_objectwriter.cc @@ -65,6 +65,7 @@ DefaultValueObjectWriter::DefaultValueObjectWriter( current_(NULL), root_(NULL), suppress_empty_list_(false), + preserve_proto_field_names_(false), field_scrub_callback_(NULL), ow_(ow) {} @@ -191,7 +192,8 @@ void DefaultValueObjectWriter::RegisterFieldScrubCallBack( DefaultValueObjectWriter::Node::Node( const string& name, const google::protobuf::Type* type, NodeKind kind, const DataPiece& data, bool is_placeholder, const std::vector& path, - bool suppress_empty_list, FieldScrubCallBack* field_scrub_callback) + bool suppress_empty_list, bool preserve_proto_field_names, + FieldScrubCallBack* field_scrub_callback) : name_(name), type_(type), kind_(kind), @@ -200,6 +202,7 @@ DefaultValueObjectWriter::Node::Node( is_placeholder_(is_placeholder), path_(path), suppress_empty_list_(suppress_empty_list), + preserve_proto_field_names_(preserve_proto_field_names), field_scrub_callback_(field_scrub_callback) {} DefaultValueObjectWriter::Node* DefaultValueObjectWriter::Node::FindChild( @@ -369,10 +372,12 @@ void DefaultValueObjectWriter::Node::PopulateChildren( // If the child field is of primitive type, sets its data to the default // value of its type. google::protobuf::scoped_ptr child(new Node( - field.json_name(), field_type, kind, + preserve_proto_field_names_ ? field.name() : field.json_name(), + field_type, kind, kind == PRIMITIVE ? CreateDefaultDataPieceForField(field, typeinfo) : DataPiece::NullData(), - true, path, suppress_empty_list_, field_scrub_callback_)); + true, path, suppress_empty_list_, preserve_proto_field_names_, + field_scrub_callback_)); new_children.push_back(child.release()); } // Adds all leftover nodes in children_ to the beginning of new_child. @@ -469,6 +474,7 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject( std::vector path; root_.reset(new Node(name.ToString(), &type_, OBJECT, DataPiece::NullData(), false, path, suppress_empty_list_, + preserve_proto_field_names_, field_scrub_callback_.get())); root_->PopulateChildren(typeinfo_); current_ = root_.get(); @@ -485,7 +491,8 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject( : NULL), OBJECT, DataPiece::NullData(), false, child == NULL ? current_->path() : child->path(), - suppress_empty_list_, field_scrub_callback_.get())); + suppress_empty_list_, preserve_proto_field_names_, + field_scrub_callback_.get())); child = node.get(); current_->AddChild(node.release()); } @@ -517,6 +524,7 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::StartList( std::vector path; root_.reset(new Node(name.ToString(), &type_, LIST, DataPiece::NullData(), false, path, suppress_empty_list_, + preserve_proto_field_names_, field_scrub_callback_.get())); current_ = root_.get(); return this; @@ -527,7 +535,8 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::StartList( google::protobuf::scoped_ptr node( new Node(name.ToString(), NULL, LIST, DataPiece::NullData(), false, child == NULL ? current_->path() : child->path(), - suppress_empty_list_, field_scrub_callback_.get())); + suppress_empty_list_, preserve_proto_field_names_, + field_scrub_callback_.get())); child = node.get(); current_->AddChild(node.release()); } @@ -588,7 +597,8 @@ void DefaultValueObjectWriter::RenderDataPiece(StringPiece name, google::protobuf::scoped_ptr node( new Node(name.ToString(), NULL, PRIMITIVE, data, false, child == NULL ? current_->path() : child->path(), - suppress_empty_list_, field_scrub_callback_.get())); + suppress_empty_list_, preserve_proto_field_names_, + field_scrub_callback_.get())); current_->AddChild(node.release()); } else { child->set_data(data); diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.h b/src/google/protobuf/util/internal/default_value_objectwriter.h index dc4551c943..ddf2359408 100644 --- a/src/google/protobuf/util/internal/default_value_objectwriter.h +++ b/src/google/protobuf/util/internal/default_value_objectwriter.h @@ -126,6 +126,9 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter { // are written. void set_suppress_empty_list(bool value) { suppress_empty_list_ = value; } + // If set to true, original proto field names are used + void set_preserve_proto_field_names(bool value) { preserve_proto_field_names_ = value; } + private: enum NodeKind { PRIMITIVE = 0, @@ -141,7 +144,7 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter { Node(const string& name, const google::protobuf::Type* type, NodeKind kind, const DataPiece& data, bool is_placeholder, const std::vector& path, bool suppress_empty_list, - FieldScrubCallBack* field_scrub_callback); + bool preserve_proto_field_names, FieldScrubCallBack* field_scrub_callback); virtual ~Node() { for (int i = 0; i < children_.size(); ++i) { delete children_[i]; @@ -220,6 +223,9 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter { // Whether to suppress empty list output. bool suppress_empty_list_; + // Whether to preserve original proto field names + bool preserve_proto_field_names_; + // Pointer to function for determining whether a field needs to be scrubbed // or not. This callback is owned by the creator of this node. FieldScrubCallBack* field_scrub_callback_; @@ -268,6 +274,9 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter { // Whether to suppress output of empty lists. bool suppress_empty_list_; + // Whether to preserve original proto field names + bool preserve_proto_field_names_; + // Unique Pointer to function for determining whether a field needs to be // scrubbed or not. FieldScrubCallBackPtr field_scrub_callback_; diff --git a/src/google/protobuf/util/internal/json_objectwriter.cc b/src/google/protobuf/util/internal/json_objectwriter.cc index c36459a4f9..6e4edd8836 100644 --- a/src/google/protobuf/util/internal/json_objectwriter.cc +++ b/src/google/protobuf/util/internal/json_objectwriter.cc @@ -176,14 +176,8 @@ void JsonObjectWriter::WritePrefix(StringPiece name) { if (!name.empty() || empty_key_ok) { WriteChar('"'); if (!name.empty()) { - if (use_snake_case_for_field_names_) { - string snake_name = ToSnakeCase(name); - ArrayByteSource source(snake_name); - JsonEscaping::Escape(&source, &sink_); - } else { - ArrayByteSource source(name); - JsonEscaping::Escape(&source, &sink_); - } + ArrayByteSource source(name); + JsonEscaping::Escape(&source, &sink_); } stream_->WriteString("\":"); if (!indent_string_.empty()) WriteChar(' '); diff --git a/src/google/protobuf/util/internal/json_objectwriter.h b/src/google/protobuf/util/internal/json_objectwriter.h index fbef54618b..31edc2927f 100644 --- a/src/google/protobuf/util/internal/json_objectwriter.h +++ b/src/google/protobuf/util/internal/json_objectwriter.h @@ -94,7 +94,6 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { sink_(out), indent_string_(indent_string.ToString()), use_websafe_base64_for_bytes_(false), - use_snake_case_for_field_names_(false), empty_name_ok_for_next_key_(false) {} virtual ~JsonObjectWriter(); @@ -119,10 +118,6 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { use_websafe_base64_for_bytes_ = value; } - void set_use_snake_case_for_field_names(bool value) { - use_snake_case_for_field_names_ = value; - } - // Whether empty strings should be rendered for the next JSON key. This // setting is only valid until the next key is rendered, after which it gets // reset to false. @@ -222,9 +217,6 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter { // to regular base64 encoding. bool use_websafe_base64_for_bytes_; - // Whether to use snake_case or lowerCamelCase for field names - bool use_snake_case_for_field_names_; - // Whether empty strings should be rendered for the next JSON key. This // setting is only valid until the next key is rendered, after which it gets // reset to false. diff --git a/src/google/protobuf/util/internal/protostream_objectsource.cc b/src/google/protobuf/util/internal/protostream_objectsource.cc index f9fd7b0159..61491af0c2 100644 --- a/src/google/protobuf/util/internal/protostream_objectsource.cc +++ b/src/google/protobuf/util/internal/protostream_objectsource.cc @@ -121,6 +121,7 @@ ProtoStreamObjectSource::ProtoStreamObjectSource( type_(type), use_lower_camel_for_enums_(false), use_ints_for_enums_(false), + preserve_proto_field_names_(false), recursion_depth_(0), max_recursion_depth_(kDefaultMaxRecursionDepth), render_unknown_fields_(false), @@ -137,6 +138,7 @@ ProtoStreamObjectSource::ProtoStreamObjectSource( type_(type), use_lower_camel_for_enums_(false), use_ints_for_enums_(false), + preserve_proto_field_names_(false), recursion_depth_(0), max_recursion_depth_(kDefaultMaxRecursionDepth), render_unknown_fields_(false), @@ -200,7 +202,11 @@ Status ProtoStreamObjectSource::WriteMessage(const google::protobuf::Type& type, last_tag = tag; field = FindAndVerifyField(type, tag); if (field != NULL) { - field_name = field->json_name(); + if (preserve_proto_field_names_) { + field_name = field->name(); + } else { + field_name = field->json_name(); + } } } if (field == NULL) { diff --git a/src/google/protobuf/util/internal/protostream_objectsource.h b/src/google/protobuf/util/internal/protostream_objectsource.h index 63d5f455d6..5f443d9d48 100644 --- a/src/google/protobuf/util/internal/protostream_objectsource.h +++ b/src/google/protobuf/util/internal/protostream_objectsource.h @@ -116,6 +116,11 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource { use_ints_for_enums_ = value; } + // Sets whether to use original proto field names + void set_preserve_proto_field_names(bool value) { + preserve_proto_field_names_ = value; + } + // Sets the max recursion depth of proto message to be deserialized. Proto // messages over this depth will fail to be deserialized. // Default value is 64. @@ -294,6 +299,9 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource { // Whether to render enums as ints always. Defaults to false. bool use_ints_for_enums_; + // Whether to preserve proto field names + bool preserve_proto_field_names_; + // Tracks current recursion depth. mutable int recursion_depth_; diff --git a/src/google/protobuf/util/json_util.cc b/src/google/protobuf/util/json_util.cc index c86cb8cb5f..8595a8813d 100644 --- a/src/google/protobuf/util/json_util.cc +++ b/src/google/protobuf/util/json_util.cc @@ -83,13 +83,16 @@ util::Status BinaryToJsonStream(TypeResolver* resolver, RETURN_IF_ERROR(resolver->ResolveMessageType(type_url, &type)); converter::ProtoStreamObjectSource proto_source(&in_stream, resolver, type); proto_source.set_use_ints_for_enums(options.always_print_enums_as_ints); + proto_source.set_preserve_proto_field_names( + options.preserve_proto_field_names); io::CodedOutputStream out_stream(json_output); converter::JsonObjectWriter json_writer(options.add_whitespace ? " " : "", &out_stream); - json_writer.set_use_snake_case_for_field_names(options.use_snake_case_for_field_names); if (options.always_print_primitive_fields) { converter::DefaultValueObjectWriter default_value_writer( resolver, type, &json_writer); + default_value_writer.set_preserve_proto_field_names( + options.preserve_proto_field_names); return proto_source.WriteTo(&default_value_writer); } else { return proto_source.WriteTo(&json_writer); diff --git a/src/google/protobuf/util/json_util.h b/src/google/protobuf/util/json_util.h index 439d137f2b..dd9a736f24 100644 --- a/src/google/protobuf/util/json_util.h +++ b/src/google/protobuf/util/json_util.h @@ -64,13 +64,13 @@ struct JsonPrintOptions { // Whether to always print enums as ints. By default they are rendered as // strings. bool always_print_enums_as_ints; - // Whether to convert field names to snake case - bool use_snake_case_for_field_names; + // Whether to preserve proto field names + bool preserve_proto_field_names; JsonPrintOptions() : add_whitespace(false), always_print_primitive_fields(false), always_print_enums_as_ints(false), - use_snake_case_for_field_names(false) { + preserve_proto_field_names(false) { } }; diff --git a/src/google/protobuf/util/json_util_test.cc b/src/google/protobuf/util/json_util_test.cc index b0c2f4946e..ecbed747a9 100644 --- a/src/google/protobuf/util/json_util_test.cc +++ b/src/google/protobuf/util/json_util_test.cc @@ -158,6 +158,43 @@ TEST_F(JsonUtilTest, TestDefaultValues) { "\"repeatedMessageValue\":[]" "}", ToJson(m, options)); + + options.preserve_proto_field_names = true; + m.set_string_value("i am a test string value"); + m.set_bytes_value("i am a test bytes value"); + EXPECT_EQ( + "{\"bool_value\":false," + "\"int32_value\":0," + "\"int64_value\":\"0\"," + "\"uint32_value\":0," + "\"uint64_value\":\"0\"," + "\"float_value\":0," + "\"double_value\":0," + "\"string_value\":\"i am a test string value\"," + "\"bytes_value\":\"aSBhbSBhIHRlc3QgYnl0ZXMgdmFsdWU=\"," + "\"enum_value\":\"FOO\"," + "\"repeated_bool_value\":[]," + "\"repeated_int32_value\":[]," + "\"repeated_int64_value\":[]," + "\"repeated_uint32_value\":[]," + "\"repeated_uint64_value\":[]," + "\"repeated_float_value\":[]," + "\"repeated_double_value\":[]," + "\"repeated_string_value\":[]," + "\"repeated_bytes_value\":[]," + "\"repeated_enum_value\":[]," + "\"repeated_message_value\":[]" + "}", + ToJson(m, options)); +} + +TEST_F(JsonUtilTest, TestPreserveProtoFieldNames) { + TestMessage m; + m.mutable_message_value(); + + JsonPrintOptions options; + options.preserve_proto_field_names = true; + EXPECT_EQ("{\"message_value\":{}}", ToJson(m, options)); } TEST_F(JsonUtilTest, TestAlwaysPrintEnumsAsInts) {