Added protobuf map support to stout JSON<->protobuf conversion.

Map is a feature of proto2 syntax, but it can only be compiled
with 3.0.0+ protobuf compiler, see the following discussion for
details:

https://groups.google.com/forum/#!topic/protobuf/p4WxcplrlA4

We have already upgraded the compiler from 2.6.1 to 3.3.0 in
MESOS-7228. This patch adds map support in the json <-> protobuf
conversion in stout.

Review: https://reviews.apache.org/r/59987/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/5b226140
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/5b226140
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/5b226140

Branch: refs/heads/master
Commit: 5b2261409e2b391d9182c5579e1df481c7f0779f
Parents: 61e7765
Author: Qian Zhang <zhq527...@gmail.com>
Authored: Mon Jul 10 15:21:55 2017 +0800
Committer: Qian Zhang <zhq527...@gmail.com>
Committed: Wed Mar 14 08:18:53 2018 +0800

----------------------------------------------------------------------
 3rdparty/stout/include/stout/protobuf.hpp | 201 ++++++++++++++++---------
 1 file changed, 134 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/5b226140/3rdparty/stout/include/stout/protobuf.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/stout/include/stout/protobuf.hpp 
b/3rdparty/stout/include/stout/protobuf.hpp
index 69a54c9..2fa5072 100644
--- a/3rdparty/stout/include/stout/protobuf.hpp
+++ b/3rdparty/stout/include/stout/protobuf.hpp
@@ -382,12 +382,51 @@ struct Parser : boost::static_visitor<Try<Nothing>>
   {
     switch (field->type()) {
       case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
-        // TODO(gilbert): We currently push up the nested error
-        // messages without wrapping the error message (due to
-        // the recursive nature of parse). We should pass along
-        // variable information in order to construct a helpful
-        // error message, e.g. "Failed to parse field 'a.b.c': ...".
-        if (field->is_repeated()) {
+        if (field->is_map()) {
+          foreachpair (
+              const std::string& name,
+              const JSON::Value& value,
+              object.values) {
+            google::protobuf::Message* entry =
+              reflection->AddMessage(message, field);
+
+            // A map is equivalent to:
+            //
+            //   message MapFieldEntry {
+            //     optional key_type key = 1;
+            //     optional value_type value = 2;
+            //   }
+            //
+            //   repeated MapFieldEntry map_field = N;
+            //
+            // See the link below for details:
+            // https://developers.google.com/protocol-buffers/docs/proto#maps
+            const google::protobuf::FieldDescriptor* key_field =
+              entry->GetDescriptor()->FindFieldByNumber(1);
+
+            JSON::Value key(name);
+
+            Try<Nothing> apply =
+              boost::apply_visitor(Parser(entry, key_field), key);
+
+            if (apply.isError()) {
+              return Error(apply.error());
+            }
+
+            const google::protobuf::FieldDescriptor* value_field =
+              entry->GetDescriptor()->FindFieldByNumber(2);
+
+            apply = boost::apply_visitor(Parser(entry, value_field), value);
+            if (apply.isError()) {
+              return Error(apply.error());
+            }
+          }
+        } else if (field->is_repeated()) {
+          // TODO(gilbert): We currently push up the nested error
+          // messages without wrapping the error message (due to
+          // the recursive nature of parse). We should pass along
+          // variable information in order to construct a helpful
+          // error message, e.g. "Failed to parse field 'a.b.c': ...".
           return parse(reflection->AddMessage(message, field), object);
         } else {
           return parse(reflection->MutableMessage(message, field), object);
@@ -864,6 +903,56 @@ inline Object protobuf(const google::protobuf::Message& 
message)
   const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
   const google::protobuf::Reflection* reflection = message.GetReflection();
 
+  auto value_for_field = [](
+      const google::protobuf::Message& message,
+      const google::protobuf::FieldDescriptor* field) -> JSON::Value {
+    const google::protobuf::Reflection* reflection = message.GetReflection();
+
+    switch (field->type()) {
+      case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
+        return JSON::Number(reflection->GetDouble(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_FLOAT:
+        return JSON::Number(reflection->GetFloat(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_INT64:
+      case google::protobuf::FieldDescriptor::TYPE_SINT64:
+      case google::protobuf::FieldDescriptor::TYPE_SFIXED64:
+        return JSON::Number(reflection->GetInt64(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_UINT64:
+      case google::protobuf::FieldDescriptor::TYPE_FIXED64:
+        return JSON::Number(reflection->GetUInt64(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_INT32:
+      case google::protobuf::FieldDescriptor::TYPE_SINT32:
+      case google::protobuf::FieldDescriptor::TYPE_SFIXED32:
+        return JSON::Number(reflection->GetInt32(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_UINT32:
+      case google::protobuf::FieldDescriptor::TYPE_FIXED32:
+        return JSON::Number(reflection->GetUInt32(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_BOOL:
+        if (reflection->GetBool(message, field)) {
+          return JSON::Boolean(true);
+        } else {
+          return JSON::Boolean(false);
+        }
+        break;
+      case google::protobuf::FieldDescriptor::TYPE_STRING:
+        return JSON::String(reflection->GetString(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_BYTES:
+        return JSON::String(
+            base64::encode(reflection->GetString(message, field)));
+      case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
+        return protobuf(reflection->GetMessage(message, field));
+      case google::protobuf::FieldDescriptor::TYPE_ENUM:
+        return JSON::String(reflection->GetEnum(message, field)->name());
+      case google::protobuf::FieldDescriptor::TYPE_GROUP:
+        // Deprecated! We abort here instead of using a Try as return value,
+        // because we expect this code path to never be taken.
+        ABORT("Unhandled protobuf field type: " +
+              stringify(field->type()));
+    }
+
+    UNREACHABLE();
+  };
+
   // We first look through all the possible fields to determine both
   // the set fields _and_ the optional fields with a default that
   // are not set. Reflection::ListFields() alone will only include
@@ -886,7 +975,44 @@ inline Object protobuf(const google::protobuf::Message& 
message)
   }
 
   foreach (const google::protobuf::FieldDescriptor* field, fields) {
-    if (field->is_repeated()) {
+    if (field->is_map()) {
+      JSON::Object map;
+
+      int fieldSize = reflection->FieldSize(message, field);
+      for (int i = 0; i < fieldSize; ++i) {
+        const google::protobuf::Message& entry =
+          reflection->GetRepeatedMessage(message, field, i);
+
+        // A map is equivalent to:
+        //
+        //   message MapFieldEntry {
+        //     optional key_type key = 1;
+        //     optional value_type value = 2;
+        //   }
+        //
+        //   repeated MapFieldEntry map_field = N;
+        //
+        // See the link below for details:
+        // https://developers.google.com/protocol-buffers/docs/proto#maps
+        const google::protobuf::FieldDescriptor* key_field =
+          entry.GetDescriptor()->FindFieldByNumber(1);
+
+        const google::protobuf::FieldDescriptor* value_field =
+          entry.GetDescriptor()->FindFieldByNumber(2);
+
+        JSON::Value key = value_for_field(entry, key_field);
+
+        std::string name;
+        if (key.is<JSON::String>()) {
+          name = key.as<JSON::String>().value;
+        } else {
+          name = jsonify(key);
+        }
+
+        map.values[name] = value_for_field(entry, value_field);
+      }
+      object.values[field->name()] = map;
+    } else if (field->is_repeated()) {
       JSON::Array array;
       int fieldSize = reflection->FieldSize(message, field);
       array.values.reserve(fieldSize);
@@ -954,66 +1080,7 @@ inline Object protobuf(const google::protobuf::Message& 
message)
       }
       object.values[field->name()] = array;
     } else {
-      switch (field->type()) {
-        case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
-          object.values[field->name()] =
-              JSON::Number(reflection->GetDouble(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_FLOAT:
-          object.values[field->name()] =
-              JSON::Number(reflection->GetFloat(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_INT64:
-        case google::protobuf::FieldDescriptor::TYPE_SINT64:
-        case google::protobuf::FieldDescriptor::TYPE_SFIXED64:
-          object.values[field->name()] =
-              JSON::Number(reflection->GetInt64(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_UINT64:
-        case google::protobuf::FieldDescriptor::TYPE_FIXED64:
-          object.values[field->name()] =
-              JSON::Number(reflection->GetUInt64(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_INT32:
-        case google::protobuf::FieldDescriptor::TYPE_SINT32:
-        case google::protobuf::FieldDescriptor::TYPE_SFIXED32:
-          object.values[field->name()] =
-              JSON::Number(reflection->GetInt32(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_UINT32:
-        case google::protobuf::FieldDescriptor::TYPE_FIXED32:
-          object.values[field->name()] =
-              JSON::Number(reflection->GetUInt32(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_BOOL:
-          if (reflection->GetBool(message, field)) {
-            object.values[field->name()] = JSON::Boolean(true);
-          } else {
-            object.values[field->name()] = JSON::Boolean(false);
-          }
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_STRING:
-          object.values[field->name()] =
-              JSON::String(reflection->GetString(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_BYTES:
-          object.values[field->name()] = JSON::String(
-              base64::encode(reflection->GetString(message, field)));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
-          object.values[field->name()] =
-            protobuf(reflection->GetMessage(message, field));
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_ENUM:
-          object.values[field->name()] =
-              JSON::String(reflection->GetEnum(message, field)->name());
-          break;
-        case google::protobuf::FieldDescriptor::TYPE_GROUP:
-          // Deprecated! We abort here instead of using a Try as return value,
-          // because we expect this code path to never be taken.
-          ABORT("Unhandled protobuf field type: " +
-                stringify(field->type()));
-      }
+      object.values[field->name()] = value_for_field(message, field);
     }
   }
 

Reply via email to