Przeglądaj źródła

Merge pull request #4195 from alexey-malov/IgnoreUnknownEnumsInJson

Adds an option in C++ JSON parser to ignore unrecognized enum values
Feng Xiao 7 lat temu
rodzic
commit
3aaed9698d

+ 6 - 1
src/google/protobuf/util/internal/datapiece.cc

@@ -272,7 +272,8 @@ StatusOr<string> DataPiece::ToBytes() const {
 }
 }
 
 
 StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type,
 StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type,
-                                bool use_lower_camel_for_enums) const {
+                                bool use_lower_camel_for_enums,
+                                bool ignore_unknown_enum_values) const {
   if (type_ == TYPE_NULL) return google::protobuf::NULL_VALUE;
   if (type_ == TYPE_NULL) return google::protobuf::NULL_VALUE;
 
 
   if (type_ == TYPE_STRING) {
   if (type_ == TYPE_STRING) {
@@ -305,6 +306,10 @@ StatusOr<int> DataPiece::ToEnum(const google::protobuf::Enum* enum_type,
       value = FindEnumValueByNameWithoutUnderscoreOrNull(enum_type, enum_name);
       value = FindEnumValueByNameWithoutUnderscoreOrNull(enum_type, enum_name);
       if (value != NULL) return value->number();
       if (value != NULL) return value->number();
     }
     }
+
+    // If ignore_unknown_enum_values is true an unknown enum value is treated
+    // as the default
+    if (ignore_unknown_enum_values) return enum_type->enumvalue(0).number();
   } else {
   } else {
     // We don't need to check whether the value is actually declared in the
     // We don't need to check whether the value is actually declared in the
     // enum because we preserve unknown enum values as well.
     // enum because we preserve unknown enum values as well.

+ 2 - 1
src/google/protobuf/util/internal/datapiece.h

@@ -164,7 +164,8 @@ class LIBPROTOBUF_EXPORT DataPiece {
   // If the value is not a string, attempts to convert to a 32-bit integer.
   // If the value is not a string, attempts to convert to a 32-bit integer.
   // If none of these succeeds, returns a conversion error status.
   // If none of these succeeds, returns a conversion error status.
   util::StatusOr<int> ToEnum(const google::protobuf::Enum* enum_type,
   util::StatusOr<int> ToEnum(const google::protobuf::Enum* enum_type,
-                               bool use_lower_camel_for_enums) const;
+                               bool use_lower_camel_for_enums,
+                               bool ignore_unknown_enum_values) const;
 
 
  private:
  private:
   // Disallow implicit constructor.
   // Disallow implicit constructor.

+ 5 - 3
src/google/protobuf/util/internal/proto_writer.cc

@@ -267,8 +267,9 @@ inline Status WriteString(int field_number, const DataPiece& data,
 inline Status WriteEnum(int field_number, const DataPiece& data,
 inline Status WriteEnum(int field_number, const DataPiece& data,
                         const google::protobuf::Enum* enum_type,
                         const google::protobuf::Enum* enum_type,
                         CodedOutputStream* stream,
                         CodedOutputStream* stream,
-                        bool use_lower_camel_for_enums) {
-  StatusOr<int> e = data.ToEnum(enum_type, use_lower_camel_for_enums);
+                        bool use_lower_camel_for_enums,
+                        bool ignore_unknown_values) {
+  StatusOr<int> e = data.ToEnum(enum_type, use_lower_camel_for_enums, ignore_unknown_values);
   if (e.ok()) {
   if (e.ok()) {
     WireFormatLite::WriteEnum(field_number, e.ValueOrDie(), stream);
     WireFormatLite::WriteEnum(field_number, e.ValueOrDie(), stream);
   }
   }
@@ -665,7 +666,8 @@ ProtoWriter* ProtoWriter::RenderPrimitiveField(
     case google::protobuf::Field_Kind_TYPE_ENUM: {
     case google::protobuf::Field_Kind_TYPE_ENUM: {
       status = WriteEnum(field.number(), data,
       status = WriteEnum(field.number(), data,
                          typeinfo_->GetEnumByTypeUrl(field.type_url()),
                          typeinfo_->GetEnumByTypeUrl(field.type_url()),
-                         stream_.get(), use_lower_camel_for_enums_);
+                         stream_.get(), use_lower_camel_for_enums_,
+                         ignore_unknown_fields_);
       break;
       break;
     }
     }
     default:  // TYPE_GROUP or TYPE_MESSAGE
     default:  // TYPE_GROUP or TYPE_MESSAGE

+ 1 - 1
src/google/protobuf/util/internal/proto_writer.h

@@ -309,7 +309,7 @@ class LIBPROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter {
   // Indicates whether we finished writing root message completely.
   // Indicates whether we finished writing root message completely.
   bool done_;
   bool done_;
 
 
-  // If true, don't report unknown field names to the listener.
+  // If true, don't report unknown field names and enum values to the listener.
   bool ignore_unknown_fields_;
   bool ignore_unknown_fields_;
 
 
   // If true, check if enum name in camel case or without underscore matches the
   // If true, check if enum name in camel case or without underscore matches the

+ 58 - 0
src/google/protobuf/util/json_util_test.cc

@@ -333,6 +333,64 @@ TEST_F(JsonUtilTest, TestDynamicMessage) {
   EXPECT_EQ(ToJson(generated, options), ToJson(*message, options));
   EXPECT_EQ(ToJson(generated, options), ToJson(*message, options));
 }
 }
 
 
+TEST_F(JsonUtilTest, TestParsingUnknownEnumsAs0) {
+  TestMessage m;
+  {
+    JsonParseOptions options;
+    ASSERT_FALSE(options.ignore_unknown_fields);
+    string input =
+      "{\n"
+      "  \"enum_value\":\"UNKNOWN_VALUE\"\n"
+      "}";
+    m.set_enum_value(proto3::BAR);
+    EXPECT_FALSE(FromJson(input, &m, options));
+    ASSERT_EQ(proto3::BAR, m.enum_value()); // Keep previous value
+
+    options.ignore_unknown_fields = true;
+    EXPECT_TRUE(FromJson(input, &m, options));
+    EXPECT_EQ(0, m.enum_value()); // Unknown enum value must be decoded as 0
+  }
+  // Integer values are read as usual
+  {
+    JsonParseOptions options;
+    string input =
+      "{\n"
+      "  \"enum_value\":12345\n"
+      "}";
+    m.set_enum_value(proto3::BAR);
+    EXPECT_TRUE(FromJson(input, &m, options));
+    ASSERT_EQ(12345, m.enum_value());
+
+    options.ignore_unknown_fields = true;
+    EXPECT_TRUE(FromJson(input, &m, options));
+    EXPECT_EQ(12345, m.enum_value());
+  }
+
+  // Trying to pass an object as an enum field value is always treated as an error
+  {
+    JsonParseOptions options;
+    string input =
+      "{\n"
+      "  \"enum_value\":{}\n"
+      "}";
+    options.ignore_unknown_fields = true;
+    EXPECT_FALSE(FromJson(input, &m, options));
+    options.ignore_unknown_fields = false;
+    EXPECT_FALSE(FromJson(input, &m, options));
+  }
+  // Trying to pass an array as an enum field value is always treated as an error
+  {
+    JsonParseOptions options;
+    string input =
+      "{\n"
+      "  \"enum_value\":[]\n"
+      "}";
+    EXPECT_FALSE(FromJson(input, &m, options));
+    options.ignore_unknown_fields = true;
+    EXPECT_FALSE(FromJson(input, &m, options));
+  }
+}
+
 typedef std::pair<char*, int> Segment;
 typedef std::pair<char*, int> Segment;
 // A ZeroCopyOutputStream that writes to multiple buffers.
 // A ZeroCopyOutputStream that writes to multiple buffers.
 class SegmentedZeroCopyOutputStream : public io::ZeroCopyOutputStream {
 class SegmentedZeroCopyOutputStream : public io::ZeroCopyOutputStream {