From 842f1324cdcdb1b74d130a101473c083abe397d0 Mon Sep 17 00:00:00 2001
From: Carlos O'Ryan <coryan@google.com>
Date: Wed, 21 Sep 2022 22:04:24 +0000
Subject: [PATCH] feat(storage): easier mocks with `ObjectMetadata`

Recapitulates the changes to make `BucketMetadata` easier to mock. This
change adds modifiers for all the fields in `ObjectMetadata` and removes
the need for `internal::CommonMetadata<>`.

Since `internal::CommonMetadata<>` is not needed, I deprecated the
class, removed all references to the class or its header file, and
refactor any useful code out of its header file.
---
 google/cloud/storage/bucket_metadata.h        |   2 +-
 .../storage/google_cloud_cpp_storage.bzl      |   1 +
 .../storage/google_cloud_cpp_storage.cmake    |   1 +
 .../storage/internal/access_control_common.h  |   1 -
 .../internal/bucket_metadata_parser.cc        |   2 +-
 .../cloud/storage/internal/common_metadata.h  |  49 +--
 .../storage/internal/common_metadata_parser.h |   6 +-
 .../internal/grpc_object_metadata_parser.cc   |  84 ++---
 .../storage/internal/grpc_owner_parser.h      |   2 +-
 .../storage/internal/lifecycle_rule_parser.cc |   2 +-
 .../internal/object_access_control_parser.cc  |   2 +-
 .../internal/object_metadata_parser.cc        | 298 +++++++++++++-----
 google/cloud/storage/object_access_control.h  |   1 -
 google/cloud/storage/object_metadata.cc       |  52 +--
 google/cloud/storage/object_metadata.h        | 223 ++++++++++---
 google/cloud/storage/object_metadata_test.cc  |  28 +-
 google/cloud/storage/owner.h                  |  65 ++++
 17 files changed, 592 insertions(+), 227 deletions(-)
 create mode 100644 google/cloud/storage/owner.h

diff --git a/google/cloud/storage/bucket_metadata.h b/google/cloud/storage/bucket_metadata.h
index 263cad94b3c45..17aae706a8d33 100644
--- a/google/cloud/storage/bucket_metadata.h
+++ b/google/cloud/storage/bucket_metadata.h
@@ -16,10 +16,10 @@
 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_BUCKET_METADATA_H
 
 #include "google/cloud/storage/bucket_access_control.h"
-#include "google/cloud/storage/internal/common_metadata.h"
 #include "google/cloud/storage/internal/patch_builder.h"
 #include "google/cloud/storage/lifecycle_rule.h"
 #include "google/cloud/storage/object_access_control.h"
+#include "google/cloud/storage/owner.h"
 #include "google/cloud/storage/version.h"
 #include "google/cloud/optional.h"
 #include "absl/types/optional.h"
diff --git a/google/cloud/storage/google_cloud_cpp_storage.bzl b/google/cloud/storage/google_cloud_cpp_storage.bzl
index ddb228c6ddcc7..003402b4cb604 100644
--- a/google/cloud/storage/google_cloud_cpp_storage.bzl
+++ b/google/cloud/storage/google_cloud_cpp_storage.bzl
@@ -122,6 +122,7 @@ google_cloud_cpp_storage_hdrs = [
     "object_write_stream.h",
     "options.h",
     "override_default_project.h",
+    "owner.h",
     "parallel_upload.h",
     "policy_document.h",
     "retry_policy.h",
diff --git a/google/cloud/storage/google_cloud_cpp_storage.cmake b/google/cloud/storage/google_cloud_cpp_storage.cmake
index e8a52804b8e10..285acd7c6f5d1 100644
--- a/google/cloud/storage/google_cloud_cpp_storage.cmake
+++ b/google/cloud/storage/google_cloud_cpp_storage.cmake
@@ -208,6 +208,7 @@ add_library(
     object_write_stream.h
     options.h
     override_default_project.h
+    owner.h
     parallel_upload.cc
     parallel_upload.h
     policy_document.cc
diff --git a/google/cloud/storage/internal/access_control_common.h b/google/cloud/storage/internal/access_control_common.h
index d53d32ee39051..852ee03056f9b 100644
--- a/google/cloud/storage/internal/access_control_common.h
+++ b/google/cloud/storage/internal/access_control_common.h
@@ -15,7 +15,6 @@
 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ACCESS_CONTROL_COMMON_H
 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ACCESS_CONTROL_COMMON_H
 
-#include "google/cloud/storage/internal/common_metadata.h"
 #include "google/cloud/storage/version.h"
 #include "google/cloud/status.h"
 #include "absl/types/optional.h"
diff --git a/google/cloud/storage/internal/bucket_metadata_parser.cc b/google/cloud/storage/internal/bucket_metadata_parser.cc
index c2b217c202082..52e851a5f0940 100644
--- a/google/cloud/storage/internal/bucket_metadata_parser.cc
+++ b/google/cloud/storage/internal/bucket_metadata_parser.cc
@@ -14,8 +14,8 @@
 
 #include "google/cloud/storage/internal/bucket_metadata_parser.h"
 #include "google/cloud/storage/internal/bucket_access_control_parser.h"
-#include "google/cloud/storage/internal/common_metadata_parser.h"
 #include "google/cloud/storage/internal/lifecycle_rule_parser.h"
+#include "google/cloud/storage/internal/metadata_parser.h"
 #include "google/cloud/storage/internal/object_access_control_parser.h"
 #include "absl/strings/str_format.h"
 #include <nlohmann/json.hpp>
diff --git a/google/cloud/storage/internal/common_metadata.h b/google/cloud/storage/internal/common_metadata.h
index 2794d050e873b..abd5e12e5c07a 100644
--- a/google/cloud/storage/internal/common_metadata.h
+++ b/google/cloud/storage/internal/common_metadata.h
@@ -15,6 +15,7 @@
 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_COMMON_METADATA_H
 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_COMMON_METADATA_H
 
+#include "google/cloud/storage/owner.h"
 #include "google/cloud/storage/version.h"
 #include "google/cloud/status_or.h"
 #include "absl/types/optional.h"
@@ -27,38 +28,6 @@ namespace google {
 namespace cloud {
 namespace storage {
 GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
-/// A simple wrapper for the `owner` field in `internal::CommonMetadata`.
-struct Owner {
-  std::string entity;
-  std::string entity_id;
-};
-
-inline bool operator==(Owner const& lhs, Owner const& rhs) {
-  return std::tie(lhs.entity, lhs.entity_id) ==
-         std::tie(rhs.entity, rhs.entity_id);
-}
-
-inline bool operator<(Owner const& lhs, Owner const& rhs) {
-  return std::tie(lhs.entity, lhs.entity_id) <
-         std::tie(rhs.entity, rhs.entity_id);
-}
-
-inline bool operator!=(Owner const& lhs, Owner const& rhs) {
-  return std::rel_ops::operator!=(lhs, rhs);
-}
-
-inline bool operator>(Owner const& lhs, Owner const& rhs) {
-  return std::rel_ops::operator>(lhs, rhs);
-}
-
-inline bool operator<=(Owner const& lhs, Owner const& rhs) {
-  return std::rel_ops::operator<=(lhs, rhs);
-}
-
-inline bool operator>=(Owner const& lhs, Owner const& rhs) {
-  return std::rel_ops::operator>=(lhs, rhs);
-}
-
 namespace internal {
 struct GrpcBucketMetadataParser;
 struct GrpcObjectMetadataParser;
@@ -73,8 +42,10 @@ struct CommonMetadataParser;
  *
  * @see https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
  */
+// TODO(#9897) - remove this class and any references to it
 template <typename Derived>
-class CommonMetadata {
+class GOOGLE_CLOUD_CPP_DEPRECATED(
+    "This class will be removed shortly after 2023-06-01") CommonMetadata {
  public:
   CommonMetadata() = default;
 
@@ -121,8 +92,10 @@ class CommonMetadata {
 };
 
 template <typename T>
-inline bool operator==(CommonMetadata<T> const& lhs,
-                       CommonMetadata<T> const& rhs) {
+GOOGLE_CLOUD_CPP_DEPRECATED(
+    "This class will be removed shortly after 2023-06-01")
+inline bool
+operator==(CommonMetadata<T> const& lhs, CommonMetadata<T> const& rhs) {
   // etag changes each time the metadata changes, so that is the best field
   // to short-circuit this comparison.  The check the name, project number,
   // and metadata generation, which have the next best chance to
@@ -138,8 +111,10 @@ inline bool operator==(CommonMetadata<T> const& lhs,
 }
 
 template <typename T>
-inline bool operator!=(CommonMetadata<T> const& lhs,
-                       CommonMetadata<T> const& rhs) {
+GOOGLE_CLOUD_CPP_DEPRECATED(
+    "This class will be removed shortly after 2023-06-01")
+inline bool
+operator!=(CommonMetadata<T> const& lhs, CommonMetadata<T> const& rhs) {
   return std::rel_ops::operator!=(lhs, rhs);
 }
 
diff --git a/google/cloud/storage/internal/common_metadata_parser.h b/google/cloud/storage/internal/common_metadata_parser.h
index 4ce9425697fdc..23aa9e040fd41 100644
--- a/google/cloud/storage/internal/common_metadata_parser.h
+++ b/google/cloud/storage/internal/common_metadata_parser.h
@@ -20,13 +20,17 @@
 #include "google/cloud/status.h"
 #include <nlohmann/json.hpp>
 #include <string>
+
 namespace google {
 namespace cloud {
 namespace storage {
 GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
 namespace internal {
+// TODO(#9897) - remove this class and any references to it
 template <typename Derived>
-struct CommonMetadataParser {
+struct GOOGLE_CLOUD_CPP_DEPRECATED(
+    "This class will be removed shortly after 2023-06-01")
+    CommonMetadataParser {
   static Status FromJson(CommonMetadata<Derived>& result,
                          nlohmann::json const& json) {
     if (!json.is_object()) {
diff --git a/google/cloud/storage/internal/grpc_object_metadata_parser.cc b/google/cloud/storage/internal/grpc_object_metadata_parser.cc
index fa140dbdac567..ade14c897e24f 100644
--- a/google/cloud/storage/internal/grpc_object_metadata_parser.cc
+++ b/google/cloud/storage/internal/grpc_object_metadata_parser.cc
@@ -86,13 +86,13 @@ ObjectMetadata GrpcObjectMetadataParser::FromProto(
   };
 
   ObjectMetadata metadata;
-  metadata.kind_ = "storage#object";
-  metadata.bucket_ = bucket_id(object);
-  metadata.name_ = std::move(*object.mutable_name());
-  metadata.generation_ = object.generation();
-  metadata.etag_ = object.etag();
-  metadata.id_ = metadata.bucket() + "/" + metadata.name() + "/" +
-                 std::to_string(metadata.generation());
+  metadata.set_kind("storage#object");
+  metadata.set_bucket(bucket_id(object));
+  metadata.set_name(std::move(*object.mutable_name()));
+  metadata.set_generation(object.generation());
+  metadata.set_etag(object.etag());
+  metadata.set_id(metadata.bucket() + "/" + metadata.name() + "/" +
+                  std::to_string(metadata.generation()));
   auto const metadata_endpoint = [&options]() -> std::string {
     if (options.get<RestEndpointOption>() != "https://storage.googleapis.com") {
       return options.get<RestEndpointOption>();
@@ -104,23 +104,23 @@ ObjectMetadata GrpcObjectMetadataParser::FromProto(
     return "/storage/" + options.get<TargetApiVersionOption>();
   }();
   auto const rel_path = "/b/" + metadata.bucket() + "/o/" + metadata.name();
-  metadata.self_link_ = metadata_endpoint + path + rel_path;
-  metadata.media_link_ =
+  metadata.set_self_link(metadata_endpoint + path + rel_path);
+  metadata.set_media_link(
       options.get<RestEndpointOption>() + "/download" + path + rel_path +
-      "?generation=" + std::to_string(metadata.generation()) + "&alt=media";
+      "?generation=" + std::to_string(metadata.generation()) + "&alt=media");
 
-  metadata.metageneration_ = object.metageneration();
+  metadata.set_metageneration(object.metageneration());
   if (object.has_owner()) {
-    metadata.owner_ = storage_internal::FromProto(*object.mutable_owner());
+    metadata.set_owner(storage_internal::FromProto(*object.mutable_owner()));
   }
-  metadata.storage_class_ = std::move(*object.mutable_storage_class());
+  metadata.set_storage_class(std::move(*object.mutable_storage_class()));
   if (object.has_create_time()) {
-    metadata.time_created_ =
-        google::cloud::internal::ToChronoTimePoint(object.create_time());
+    metadata.set_time_created(
+        google::cloud::internal::ToChronoTimePoint(object.create_time()));
   }
   if (object.has_update_time()) {
-    metadata.updated_ =
-        google::cloud::internal::ToChronoTimePoint(object.update_time());
+    metadata.set_updated(
+        google::cloud::internal::ToChronoTimePoint(object.update_time()));
   }
   std::vector<ObjectAccessControl> acl;
   acl.reserve(object.acl_size());
@@ -129,53 +129,53 @@ ObjectMetadata GrpcObjectMetadataParser::FromProto(
         std::move(item), metadata.bucket(), metadata.name(),
         metadata.generation()));
   }
-  metadata.acl_ = std::move(acl);
-  metadata.cache_control_ = std::move(*object.mutable_cache_control());
-  metadata.component_count_ = object.component_count();
-  metadata.content_disposition_ =
-      std::move(*object.mutable_content_disposition());
-  metadata.content_encoding_ = std::move(*object.mutable_content_encoding());
-  metadata.content_language_ = std::move(*object.mutable_content_language());
-  metadata.content_type_ = std::move(*object.mutable_content_type());
+  metadata.set_acl(std::move(acl));
+  metadata.set_cache_control(std::move(*object.mutable_cache_control()));
+  metadata.set_component_count(object.component_count());
+  metadata.set_content_disposition(
+      std::move(*object.mutable_content_disposition()));
+  metadata.set_content_encoding(std::move(*object.mutable_content_encoding()));
+  metadata.set_content_language(std::move(*object.mutable_content_language()));
+  metadata.set_content_type(std::move(*object.mutable_content_type()));
   if (object.has_checksums()) {
     if (object.checksums().has_crc32c()) {
-      metadata.crc32c_ = Crc32cFromProto(object.checksums().crc32c());
+      metadata.set_crc32c(Crc32cFromProto(object.checksums().crc32c()));
     }
     if (!object.checksums().md5_hash().empty()) {
-      metadata.md5_hash_ = MD5FromProto(object.checksums().md5_hash());
+      metadata.set_md5_hash(MD5FromProto(object.checksums().md5_hash()));
     }
   }
   if (object.has_customer_encryption()) {
-    metadata.customer_encryption_ =
-        FromProto(std::move(*object.mutable_customer_encryption()));
+    metadata.set_customer_encryption(
+        FromProto(std::move(*object.mutable_customer_encryption())));
   }
   if (object.has_event_based_hold()) {
-    metadata.event_based_hold_ = object.event_based_hold();
+    metadata.set_event_based_hold(object.event_based_hold());
   }
-  metadata.kms_key_name_ = std::move(*object.mutable_kms_key());
+  metadata.set_kms_key_name(std::move(*object.mutable_kms_key()));
 
   for (auto const& kv : object.metadata()) {
-    metadata.metadata_[kv.first] = kv.second;
+    metadata.upsert_metadata(kv.first, kv.second);
   }
   if (object.has_retention_expire_time()) {
-    metadata.retention_expiration_time_ =
+    metadata.set_retention_expiration_time(
         google::cloud::internal::ToChronoTimePoint(
-            object.retention_expire_time());
+            object.retention_expire_time()));
   }
-  metadata.size_ = static_cast<std::uint64_t>(object.size());
-  metadata.temporary_hold_ = object.temporary_hold();
+  metadata.set_size(static_cast<std::uint64_t>(object.size()));
+  metadata.set_temporary_hold(object.temporary_hold());
   if (object.has_delete_time()) {
-    metadata.time_deleted_ =
-        google::cloud::internal::ToChronoTimePoint(object.delete_time());
+    metadata.set_time_deleted(
+        google::cloud::internal::ToChronoTimePoint(object.delete_time()));
   }
   if (object.has_update_storage_class_time()) {
-    metadata.time_storage_class_updated_ =
+    metadata.set_time_storage_class_updated(
         google::cloud::internal::ToChronoTimePoint(
-            object.update_storage_class_time());
+            object.update_storage_class_time()));
   }
   if (object.has_custom_time()) {
-    metadata.custom_time_ =
-        google::cloud::internal::ToChronoTimePoint(object.custom_time());
+    metadata.set_custom_time(
+        google::cloud::internal::ToChronoTimePoint(object.custom_time()));
   }
 
   return metadata;
diff --git a/google/cloud/storage/internal/grpc_owner_parser.h b/google/cloud/storage/internal/grpc_owner_parser.h
index 30d2acefdb27c..28cafcfce2067 100644
--- a/google/cloud/storage/internal/grpc_owner_parser.h
+++ b/google/cloud/storage/internal/grpc_owner_parser.h
@@ -15,7 +15,7 @@
 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_GRPC_OWNER_PARSER_H
 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_GRPC_OWNER_PARSER_H
 
-#include "google/cloud/storage/internal/common_metadata.h"
+#include "google/cloud/storage/owner.h"
 #include "google/cloud/storage/version.h"
 #include <google/storage/v2/storage.pb.h>
 
diff --git a/google/cloud/storage/internal/lifecycle_rule_parser.cc b/google/cloud/storage/internal/lifecycle_rule_parser.cc
index 8635485817791..5af0c876c8faa 100644
--- a/google/cloud/storage/internal/lifecycle_rule_parser.cc
+++ b/google/cloud/storage/internal/lifecycle_rule_parser.cc
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 #include "google/cloud/storage/internal/lifecycle_rule_parser.h"
-#include "google/cloud/storage/internal/common_metadata_parser.h"
+#include "google/cloud/storage/internal/metadata_parser.h"
 
 namespace google {
 namespace cloud {
diff --git a/google/cloud/storage/internal/object_access_control_parser.cc b/google/cloud/storage/internal/object_access_control_parser.cc
index b1b71e0bf68eb..3122fd4bc25b3 100644
--- a/google/cloud/storage/internal/object_access_control_parser.cc
+++ b/google/cloud/storage/internal/object_access_control_parser.cc
@@ -14,7 +14,7 @@
 
 #include "google/cloud/storage/internal/object_access_control_parser.h"
 #include "google/cloud/storage/internal/access_control_common_parser.h"
-#include "google/cloud/storage/internal/common_metadata_parser.h"
+#include "google/cloud/storage/internal/metadata_parser.h"
 #include <nlohmann/json.hpp>
 
 namespace google {
diff --git a/google/cloud/storage/internal/object_metadata_parser.cc b/google/cloud/storage/internal/object_metadata_parser.cc
index e672295ce62df..611ff37d12baa 100644
--- a/google/cloud/storage/internal/object_metadata_parser.cc
+++ b/google/cloud/storage/internal/object_metadata_parser.cc
@@ -13,10 +13,11 @@
 // limitations under the License.
 
 #include "google/cloud/storage/internal/object_metadata_parser.h"
-#include "google/cloud/storage/internal/common_metadata_parser.h"
+#include "google/cloud/storage/internal/metadata_parser.h"
 #include "google/cloud/storage/internal/object_access_control_parser.h"
 #include "google/cloud/internal/format_time_point.h"
 #include <nlohmann/json.hpp>
+#include <functional>
 
 namespace google {
 namespace cloud {
@@ -37,84 +38,235 @@ void SetIfNotEmpty(nlohmann::json& json, char const* key,
   }
   json[key] = value;
 }
+
+Status ParseAcl(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto i = json.find("acl");
+  if (i == json.end()) return Status{};
+  std::vector<ObjectAccessControl> acl;
+  for (auto const& kv : i->items()) {
+    auto parsed = ObjectAccessControlParser::FromJson(kv.value());
+    if (!parsed) return std::move(parsed).status();
+    acl.push_back(*std::move(parsed));
+  }
+  meta.set_acl(std::move(acl));
+  return Status{};
+}
+
+Status ParseComponentCount(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = internal::ParseIntField(json, "componentCount");
+  if (!v) return std::move(v).status();
+  meta.set_component_count(*v);
+  return Status{};
+}
+
+Status ParseCustomTime(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto f = json.find("customTime");
+  if (f == json.end()) return Status{};
+  auto v = internal::ParseTimestampField(json, "customTime");
+  if (!v) return std::move(v).status();
+  meta.set_custom_time(*v);
+  return Status{};
+}
+
+Status ParseCustomerEncryption(ObjectMetadata& meta,
+                               nlohmann::json const& json) {
+  auto f = json.find("customerEncryption");
+  if (f == json.end()) return Status{};
+  CustomerEncryption e;
+  e.encryption_algorithm = f->value("encryptionAlgorithm", "");
+  e.key_sha256 = f->value("keySha256", "");
+  meta.set_customer_encryption(std::move(e));
+  return Status{};
+}
+
+Status ParseEventBasedHold(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = internal::ParseBoolField(json, "eventBasedHold");
+  if (!v) return std::move(v).status();
+  meta.set_event_based_hold(*v);
+  return Status{};
+}
+
+Status ParseGeneration(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = internal::ParseLongField(json, "generation");
+  if (!v) return std::move(v).status();
+  meta.set_generation(*v);
+  return Status{};
+}
+
+Status ParseMetageneration(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = internal::ParseLongField(json, "metageneration");
+  if (!v) return std::move(v).status();
+  meta.set_metageneration(*v);
+  return Status{};
+}
+
+Status ParseMetadata(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto f = json.find("metadata");
+  if (f == json.end()) return Status{};
+  std::map<std::string, std::string> metadata;
+  for (auto const& kv : f->items()) {
+    metadata.emplace(kv.key(), kv.value().get<std::string>());
+  }
+  meta.mutable_metadata() = std::move(metadata);
+  return Status{};
+}
+
+Status ParseOwner(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto f = json.find("owner");
+  if (f == json.end()) return Status{};
+  Owner owner;
+  owner.entity = f->value("entity", "");
+  owner.entity_id = f->value("entityId", "");
+  meta.set_owner(std::move(owner));
+  return Status{};
+}
+
+Status ParseRetentionExpirationTime(ObjectMetadata& meta,
+                                    nlohmann::json const& json) {
+  auto v = internal::ParseTimestampField(json, "retentionExpirationTime");
+  if (!v) return std::move(v).status();
+  meta.set_retention_expiration_time(*v);
+  return Status{};
+}
+
+Status ParseSize(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = internal::ParseUnsignedLongField(json, "size");
+  if (!v) return std::move(v).status();
+  meta.set_size(*v);
+  return Status{};
+}
+
+Status ParseTemporaryHold(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = internal::ParseBoolField(json, "temporaryHold");
+  if (!v) return std::move(v).status();
+  meta.set_temporary_hold(*v);
+  return Status{};
+}
+
+Status ParseTimeCreated(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = ParseTimestampField(json, "timeCreated");
+  if (!v) return std::move(v).status();
+  meta.set_time_created(*std::move(v));
+  return Status{};
+}
+
+Status ParseTimeDeleted(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = ParseTimestampField(json, "timeDeleted");
+  if (!v) return std::move(v).status();
+  meta.set_time_deleted(*std::move(v));
+  return Status{};
+}
+
+Status ParseTimeStorageClassUpdated(ObjectMetadata& meta,
+                                    nlohmann::json const& json) {
+  auto v = ParseTimestampField(json, "timeStorageClassUpdated");
+  if (!v) return std::move(v).status();
+  meta.set_time_storage_class_updated(*std::move(v));
+  return Status{};
+}
+
+Status ParseUpdated(ObjectMetadata& meta, nlohmann::json const& json) {
+  auto v = ParseTimestampField(json, "updated");
+  if (!v) return std::move(v).status();
+  meta.set_updated(*std::move(v));
+  return Status{};
+}
+
 }  // namespace
 
 StatusOr<ObjectMetadata> ObjectMetadataParser::FromJson(
     nlohmann::json const& json) {
-  if (!json.is_object()) {
-    return Status(StatusCode::kInvalidArgument, __func__);
-  }
-  ObjectMetadata result{};
-  auto status = CommonMetadataParser<ObjectMetadata>::FromJson(result, json);
-  if (!status.ok()) return status;
-
-  if (json.count("acl") != 0) {
-    for (auto const& kv : json["acl"].items()) {
-      auto parsed = ObjectAccessControlParser::FromJson(kv.value());
-      if (!parsed.ok()) {
-        return std::move(parsed).status();
-      }
-      result.acl_.emplace_back(std::move(*parsed));
-    }
-  }
+  if (!json.is_object()) return Status(StatusCode::kInvalidArgument, __func__);
 
-  result.bucket_ = json.value("bucket", "");
-  result.cache_control_ = json.value("cacheControl", "");
-  auto component_count = internal::ParseIntField(json, "componentCount");
-  if (!component_count) return std::move(component_count).status();
-  result.component_count_ = *component_count;
-  result.content_disposition_ = json.value("contentDisposition", "");
-  result.content_encoding_ = json.value("contentEncoding", "");
-  result.content_language_ = json.value("contentLanguage", "");
-  result.content_type_ = json.value("contentType", "");
-  result.crc32c_ = json.value("crc32c", "");
-  if (json.count("customerEncryption") != 0) {
-    auto const& field = json["customerEncryption"];
-    CustomerEncryption e;
-    e.encryption_algorithm = field.value("encryptionAlgorithm", "");
-    e.key_sha256 = field.value("keySha256", "");
-    result.customer_encryption_ = std::move(e);
-  }
-  auto event_based_hold = internal::ParseBoolField(json, "eventBasedHold");
-  if (!event_based_hold) return std::move(event_based_hold).status();
-  result.event_based_hold_ = *event_based_hold;
-  auto generation = internal::ParseLongField(json, "generation");
-  if (!generation) return std::move(generation).status();
-  result.generation_ = *generation;
-  result.kms_key_name_ = json.value("kmsKeyName", "");
-  result.md5_hash_ = json.value("md5Hash", "");
-  result.media_link_ = json.value("mediaLink", "");
-  if (json.count("metadata") > 0) {
-    for (auto const& kv : json["metadata"].items()) {
-      result.metadata_.emplace(kv.key(), kv.value().get<std::string>());
-    }
-  }
-  auto expiration_time =
-      internal::ParseTimestampField(json, "retentionExpirationTime");
-  if (!expiration_time) return std::move(expiration_time).status();
-  result.retention_expiration_time_ = *expiration_time;
-  auto size = internal::ParseUnsignedLongField(json, "size");
-  if (!size) return std::move(size).status();
-  result.size_ = *size;
-  auto temporary_hold = internal::ParseBoolField(json, "temporaryHold");
-  if (!temporary_hold) return std::move(temporary_hold).status();
-  result.temporary_hold_ = *temporary_hold;
-  auto time_deleted = internal::ParseTimestampField(json, "timeDeleted");
-  if (!time_deleted) return std::move(time_deleted).status();
-  result.time_deleted_ = *time_deleted;
-  auto time_storage_class_updated =
-      internal::ParseTimestampField(json, "timeStorageClassUpdated");
-  if (!time_storage_class_updated)
-    return std::move(time_storage_class_updated).status();
-  result.time_storage_class_updated_ = *time_storage_class_updated;
-  if (json.count("customTime") == 0) {
-    result.custom_time_.reset();
-  } else {
-    auto v = internal::ParseTimestampField(json, "customTime");
-    if (!v) return std::move(v).status();
-    result.custom_time_ = *v;
+  using Parser = std::function<Status(ObjectMetadata&, nlohmann::json const&)>;
+  Parser parsers[] = {
+      ParseAcl,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_bucket(json.value("bucket", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_cache_control(json.value("cacheControl", ""));
+        return Status{};
+      },
+      ParseComponentCount,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_content_disposition(json.value("contentDisposition", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_content_encoding(json.value("contentEncoding", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_content_language(json.value("contentLanguage", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_content_type(json.value("contentType", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_crc32c(json.value("crc32c", ""));
+        return Status{};
+      },
+      ParseCustomTime,
+      ParseCustomerEncryption,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_etag(json.value("etag", ""));
+        return Status{};
+      },
+      ParseEventBasedHold,
+      ParseGeneration,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_id(json.value("id", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_kind(json.value("kind", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_kms_key_name(json.value("kmsKeyName", ""));
+        return Status{};
+      },
+      ParseMetageneration,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_md5_hash(json.value("md5Hash", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_media_link(json.value("mediaLink", ""));
+        return Status{};
+      },
+      ParseMetadata,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_name(json.value("name", ""));
+        return Status{};
+      },
+      ParseOwner,
+      ParseRetentionExpirationTime,
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_self_link(json.value("selfLink", ""));
+        return Status{};
+      },
+      [](ObjectMetadata& meta, nlohmann::json const& json) {
+        meta.set_storage_class(json.value("storageClass", ""));
+        return Status{};
+      },
+      ParseSize,
+      ParseTemporaryHold,
+      ParseTimeCreated,
+      ParseTimeDeleted,
+      ParseTimeStorageClassUpdated,
+      ParseUpdated,
+  };
+  ObjectMetadata meta;
+  for (auto const& p : parsers) {
+    auto status = p(meta, json);
+    if (!status.ok()) return status;
   }
-  return result;
+  return meta;
 }
 
 StatusOr<ObjectMetadata> ObjectMetadataParser::FromString(
diff --git a/google/cloud/storage/object_access_control.h b/google/cloud/storage/object_access_control.h
index baabd02be2968..df26b7c163dbd 100644
--- a/google/cloud/storage/object_access_control.h
+++ b/google/cloud/storage/object_access_control.h
@@ -16,7 +16,6 @@
 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OBJECT_ACCESS_CONTROL_H
 
 #include "google/cloud/storage/internal/access_control_common.h"
-#include "google/cloud/storage/internal/common_metadata.h"
 #include "google/cloud/storage/internal/patch_builder.h"
 #include "google/cloud/storage/version.h"
 #include "google/cloud/status_or.h"
diff --git a/google/cloud/storage/object_metadata.cc b/google/cloud/storage/object_metadata.cc
index 13a07651f2720..44ed143e9652c 100644
--- a/google/cloud/storage/object_metadata.cc
+++ b/google/cloud/storage/object_metadata.cc
@@ -27,26 +27,38 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
 using ::google::cloud::internal::FormatRfc3339;
 
 bool operator==(ObjectMetadata const& lhs, ObjectMetadata const& rhs) {
-  return static_cast<internal::CommonMetadata<ObjectMetadata> const&>(lhs) ==
-             rhs &&
-         lhs.acl_ == rhs.acl_ && lhs.bucket_ == rhs.bucket_ &&
-         lhs.cache_control_ == rhs.cache_control_ &&
-         lhs.component_count_ == rhs.component_count_ &&
-         lhs.content_disposition_ == rhs.content_disposition_ &&
-         lhs.content_encoding_ == rhs.content_encoding_ &&
-         lhs.content_language_ == rhs.content_language_ &&
-         lhs.content_type_ == rhs.content_type_ && lhs.crc32c_ == rhs.crc32c_ &&
-         lhs.customer_encryption_ == rhs.customer_encryption_ &&
-         lhs.event_based_hold_ == rhs.event_based_hold_ &&
-         lhs.generation_ == rhs.generation_ &&
-         lhs.kms_key_name_ == rhs.kms_key_name_ &&
-         lhs.md5_hash_ == rhs.md5_hash_ && lhs.media_link_ == rhs.media_link_ &&
-         lhs.metadata_ == rhs.metadata_ &&
-         lhs.retention_expiration_time_ == rhs.retention_expiration_time_ &&
-         lhs.temporary_hold_ == rhs.temporary_hold_ &&
-         lhs.time_deleted_ == rhs.time_deleted_ &&
-         lhs.time_storage_class_updated_ == rhs.time_storage_class_updated_ &&
-         lhs.size_ == rhs.size_ && lhs.custom_time_ == rhs.custom_time_;
+  return lhs.acl_ == rhs.acl_ && lhs.bucket_ == rhs.bucket_                   //
+         && lhs.cache_control_ == rhs.cache_control_                          //
+         && lhs.component_count_ == rhs.component_count_                      //
+         && lhs.content_disposition_ == rhs.content_disposition_              //
+         && lhs.content_encoding_ == rhs.content_encoding_                    //
+         && lhs.content_language_ == rhs.content_language_                    //
+         && lhs.content_type_ == rhs.content_type_                            //
+         && lhs.crc32c_ == rhs.crc32c_                                        //
+         && lhs.custom_time_ == rhs.custom_time_                              //
+         && lhs.customer_encryption_ == rhs.customer_encryption_              //
+         && lhs.etag_ == rhs.etag_                                            //
+         && lhs.event_based_hold_ == rhs.event_based_hold_                    //
+         && lhs.generation_ == rhs.generation_                                //
+         && lhs.id_ == rhs.id_                                                //
+         && lhs.kind_ == rhs.kind_                                            //
+         && lhs.kms_key_name_ == rhs.kms_key_name_                            //
+         && lhs.md5_hash_ == rhs.md5_hash_                                    //
+         && lhs.media_link_ == rhs.media_link_                                //
+         && lhs.metadata_ == rhs.metadata_                                    //
+         && lhs.metageneration_ == rhs.metageneration_                        //
+         && lhs.name_ == rhs.name_                                            //
+         && lhs.owner_ == rhs.owner_                                          //
+         && lhs.retention_expiration_time_ == rhs.retention_expiration_time_  //
+         && lhs.self_link_ == rhs.self_link_                                  //
+         && lhs.size_ == rhs.size_                                            //
+         && lhs.storage_class_ == rhs.storage_class_                          //
+         && lhs.temporary_hold_ == rhs.temporary_hold_                        //
+         && lhs.time_created_ == rhs.time_created_                            //
+         && lhs.time_deleted_ == rhs.time_deleted_                            //
+         && (lhs.time_storage_class_updated_ ==
+             rhs.time_storage_class_updated_)  //
+         && lhs.updated_ == rhs.updated_;
 }
 
 std::ostream& operator<<(std::ostream& os, ObjectMetadata const& rhs) {
diff --git a/google/cloud/storage/object_metadata.h b/google/cloud/storage/object_metadata.h
index 6dbbfb9523eda..67b5b73469ac4 100644
--- a/google/cloud/storage/object_metadata.h
+++ b/google/cloud/storage/object_metadata.h
@@ -15,9 +15,9 @@
 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OBJECT_METADATA_H
 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OBJECT_METADATA_H
 
-#include "google/cloud/storage/internal/common_metadata.h"
 #include "google/cloud/storage/internal/complex_option.h"
 #include "google/cloud/storage/object_access_control.h"
+#include "google/cloud/storage/owner.h"
 #include "google/cloud/storage/version.h"
 #include "google/cloud/optional.h"
 #include "google/cloud/status_or.h"
@@ -96,7 +96,7 @@ inline bool operator>=(CustomerEncryption const& lhs,
  * @see https://cloud.google.com/storage/docs/json_api/v1/objects for a more
  *     detailed description of each attribute and their effects.
  */
-class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
+class ObjectMetadata {
  public:
   ObjectMetadata() = default;
 
@@ -118,6 +118,12 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   /// The name of the bucket containing this object.
   std::string const& bucket() const { return bucket_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_bucket(std::string v) {
+    bucket_ = std::move(v);
+    return *this;
+  }
+
   /// The `cacheControl` attribute.
   std::string const& cache_control() const { return cache_control_; }
 
@@ -130,6 +136,12 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   /// The number of components, for objects built using `ComposeObject()`.
   std::int32_t component_count() const { return component_count_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_component_count(std::int32_t v) {
+    component_count_ = v;
+    return *this;
+  }
+
   /// The `contentDisposition` attribute.
   std::string content_disposition() const { return content_disposition_; }
 
@@ -169,6 +181,33 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   /// The `CRC32C` checksum for the object contents.
   std::string const& crc32c() const { return crc32c_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_crc32c(std::string v) {
+    crc32c_ = std::move(v);
+    return *this;
+  }
+
+  /// Returns `true` if the object has a `customTime` attribute.
+  bool has_custom_time() const { return custom_time_.has_value(); }
+
+  /// Returns the object's `customTime` or the system clock's epoch.
+  std::chrono::system_clock::time_point custom_time() const {
+    return custom_time_.value_or(std::chrono::system_clock::time_point{});
+  }
+
+  /// Changes the `customTime` attribute.
+  ObjectMetadata& set_custom_time(std::chrono::system_clock::time_point v) {
+    custom_time_ = v;
+    return *this;
+  }
+
+  /// Reset (clears) the `customTime` attribute. `has_custom_time()` returns
+  /// `false` after calling this function.
+  ObjectMetadata& reset_custom_time() {
+    custom_time_.reset();
+    return *this;
+  }
+
   /// Returns `true` if the object uses CSEK (Customer-Supplied Encryption
   /// Keys).
   bool has_customer_encryption() const {
@@ -185,8 +224,26 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
     return customer_encryption_.value();
   }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_customer_encryption(CustomerEncryption v) {
+    customer_encryption_ = std::move(v);
+    return *this;
+  }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& reset_customer_encryption() {
+    customer_encryption_.reset();
+    return *this;
+  }
+
   /// The `Etag` attribute.
-  using CommonMetadata::etag;
+  std::string const& etag() const { return etag_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_etag(std::string v) {
+    etag_ = std::move(v);
+    return *this;
+  }
 
   /// The `eventBasedHold` attribute.
   bool event_based_hold() const { return event_based_hold_; }
@@ -206,11 +263,29 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
    */
   std::int64_t generation() const { return generation_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_generation(std::int64_t v) {
+    generation_ = v;
+    return *this;
+  }
+
   /// The `id` attribute (the object name)
-  using CommonMetadata::id;
+  std::string const& id() const { return id_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_id(std::string v) {
+    id_ = std::move(v);
+    return *this;
+  }
 
   /// The `kind` attribute, that is, `storage#object`.
-  using CommonMetadata::kind;
+  std::string const& kind() const { return kind_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_kind(std::string v) {
+    kind_ = std::move(v);
+    return *this;
+  }
 
   /**
    * The name of the KMS (Key Management Service) key used in this object.
@@ -220,12 +295,30 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
    */
   std::string const& kms_key_name() const { return kms_key_name_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_kms_key_name(std::string v) {
+    kms_key_name_ = std::move(v);
+    return *this;
+  }
+
   /// The MD5 hash of the object contents. Can be empty.
   std::string const& md5_hash() const { return md5_hash_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_md5_hash(std::string v) {
+    md5_hash_ = std::move(v);
+    return *this;
+  }
+
   /// The HTTPS link to access the object contents.
   std::string const& media_link() const { return media_link_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_media_link(std::string v) {
+    media_link_ = std::move(v);
+    return *this;
+  }
+
   /**
    * @name Accessors and modifiers for metadata entries.
    *
@@ -279,9 +372,6 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   std::map<std::string, std::string>& mutable_metadata() { return metadata_; }
   ///@}
 
-  /// Returns `true` if the object has an `owner` attribute.
-  using CommonMetadata::has_owner;
-
   /**
    * The generation of the object metadata.
    *
@@ -289,10 +379,25 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
    * attribute) increases the metageneration, but does not change the object
    * generation.
    */
-  using CommonMetadata::metageneration;
+  std::int64_t metageneration() const { return metageneration_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_metageneration(std::int64_t v) {
+    metageneration_ = v;
+    return *this;
+  }
 
   /// The object name, including bucket and generation.
-  using CommonMetadata::name;
+  std::string const& name() const { return name_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_name(std::string v) {
+    name_ = std::move(v);
+    return *this;
+  }
+
+  /// Returns `true` if the object has an `owner` attribute.
+  bool has_owner() const { return owner_.has_value(); }
 
   /**
    * The object's `owner` attribute.
@@ -300,25 +405,56 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
    * It is undefined behavior to call this member function if
    * `has_owner() == false`.
    */
-  using CommonMetadata::owner;
+  Owner const& owner() const { return *owner_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_owner(Owner v) {
+    owner_ = std::move(v);
+    return *this;
+  }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& reset_owner() {
+    owner_.reset();
+    return *this;
+  }
 
   /// The retention expiration time, or the system clock's epoch, if not set.
   std::chrono::system_clock::time_point retention_expiration_time() const {
     return retention_expiration_time_;
   }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_retention_expiration_time(
+      std::chrono::system_clock::time_point v) {
+    retention_expiration_time_ = v;
+    return *this;
+  }
+
   /// An HTTPS link to the object metadata.
-  using CommonMetadata::self_link;
+  std::string const& self_link() const { return self_link_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_self_link(std::string v) {
+    self_link_ = std::move(v);
+    return *this;
+  }
 
   /// The size of the object's data.
   std::uint64_t size() const { return size_; }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_size(std::uint64_t v) {
+    size_ = v;
+    return *this;
+  }
+
   /// The `storageClass` attribute.
-  using CommonMetadata::storage_class;
+  std::string const& storage_class() const { return storage_class_; }
 
   /// Changes the `storageClass` attribute.
   ObjectMetadata& set_storage_class(std::string v) {
-    CommonMetadata::set_storage_class(std::move(v));
+    storage_class_ = std::move(v);
     return *this;
   }
 
@@ -332,39 +468,45 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   }
 
   /// The object creation timestamp.
-  using CommonMetadata::time_created;
+  std::chrono::system_clock::time_point time_created() const {
+    return time_created_;
+  }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_time_created(std::chrono::system_clock::time_point v) {
+    time_created_ = v;
+    return *this;
+  }
 
   /// The object's deletion timestamp.
   std::chrono::system_clock::time_point time_deleted() const {
     return time_deleted_;
   }
 
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_time_deleted(std::chrono::system_clock::time_point v) {
+    time_deleted_ = v;
+    return *this;
+  }
+
   /// The timestamp for the last storage class change.
   std::chrono::system_clock::time_point time_storage_class_updated() const {
     return time_storage_class_updated_;
   }
 
-  /// The timestamp for the last object *metadata* update.
-  using CommonMetadata::updated;
-
-  /// Returns `true` if the object has a `customTime` attribute.
-  bool has_custom_time() const { return custom_time_.has_value(); }
-
-  /// Returns the object's `customTime` or the system clock's epoch.
-  std::chrono::system_clock::time_point custom_time() const {
-    return custom_time_.value_or(std::chrono::system_clock::time_point{});
-  }
-
-  /// Changes the `customTime` attribute.
-  ObjectMetadata& set_custom_time(std::chrono::system_clock::time_point v) {
-    custom_time_ = v;
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_time_storage_class_updated(
+      std::chrono::system_clock::time_point v) {
+    time_storage_class_updated_ = v;
     return *this;
   }
 
-  /// Reset (clears) the `customTime` attribute. `has_custom_time()` returns
-  /// `false` after calling this function.
-  ObjectMetadata& reset_custom_time() {
-    custom_time_.reset();
+  /// The timestamp for the last object *metadata* update.
+  std::chrono::system_clock::time_point updated() const { return updated_; }
+
+  /// @note This is only intended for mocking.
+  ObjectMetadata& set_updated(std::chrono::system_clock::time_point v) {
+    updated_ = v;
     return *this;
   }
 
@@ -374,9 +516,6 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   }
 
  private:
-  friend struct internal::ObjectMetadataParser;
-  friend struct internal::GrpcObjectMetadataParser;
-
   friend std::ostream& operator<<(std::ostream& os, ObjectMetadata const& rhs);
   // Keep the fields in alphabetical order.
   std::vector<ObjectAccessControl> acl_;
@@ -388,19 +527,29 @@ class ObjectMetadata : private internal::CommonMetadata<ObjectMetadata> {
   std::string content_language_;
   std::string content_type_;
   std::string crc32c_;
+  absl::optional<std::chrono::system_clock::time_point> custom_time_;
   absl::optional<CustomerEncryption> customer_encryption_;
+  std::string etag_;
   bool event_based_hold_{false};
   std::int64_t generation_{0};
+  std::string id_;
+  std::string kind_;
   std::string kms_key_name_;
+  std::int64_t metageneration_{0};
   std::string md5_hash_;
   std::string media_link_;
   std::map<std::string, std::string> metadata_;
+  std::string name_;
+  absl::optional<Owner> owner_;
   std::chrono::system_clock::time_point retention_expiration_time_;
+  std::string self_link_;
   std::uint64_t size_{0};
+  std::string storage_class_;
   bool temporary_hold_{false};
+  std::chrono::system_clock::time_point time_created_;
   std::chrono::system_clock::time_point time_deleted_;
   std::chrono::system_clock::time_point time_storage_class_updated_;
-  absl::optional<std::chrono::system_clock::time_point> custom_time_;
+  std::chrono::system_clock::time_point updated_;
 };
 
 std::ostream& operator<<(std::ostream& os, ObjectMetadata const& rhs);
diff --git a/google/cloud/storage/object_metadata_test.cc b/google/cloud/storage/object_metadata_test.cc
index e922d22fecc64..924041aea0ff6 100644
--- a/google/cloud/storage/object_metadata_test.cc
+++ b/google/cloud/storage/object_metadata_test.cc
@@ -188,14 +188,16 @@ TEST(ObjectMetadataTest, IOStream) {
 
 /// @test Verify that ObjectMetadataJsonForCompose works as expected.
 TEST(ObjectMetadataTest, JsonForComposeEmpty) {
-  nlohmann::json actual = ObjectMetadataJsonForCompose(ObjectMetadata());
+  nlohmann::json actual =
+      internal::ObjectMetadataJsonForCompose(ObjectMetadata());
   nlohmann::json expected({});
   EXPECT_EQ(expected, actual);
 }
 
 /// @test Verify that ObjectMetadataJsonForCompose() works as expected.
 TEST(ObjectMetadataTest, JsonForCompose) {
-  auto actual = ObjectMetadataJsonForCompose(CreateObjectMetadataForTest());
+  auto actual =
+      internal::ObjectMetadataJsonForCompose(CreateObjectMetadataForTest());
 
   nlohmann::json expected = {
       {"acl",
@@ -224,14 +226,15 @@ TEST(ObjectMetadataTest, JsonForCompose) {
 
 /// @test Verify that ObjectMetadataJsonForCopy works as expected.
 TEST(ObjectMetadataTest, JsonForCopyEmpty) {
-  nlohmann::json actual = ObjectMetadataJsonForCopy(ObjectMetadata());
+  nlohmann::json actual = internal::ObjectMetadataJsonForCopy(ObjectMetadata());
   nlohmann::json expected({});
   EXPECT_EQ(expected, actual);
 }
 
 /// @test Verify that ObjectMetadataJsonForCopy() works as expected.
 TEST(ObjectMetadataTest, JsonForCopy) {
-  auto actual = ObjectMetadataJsonForCopy(CreateObjectMetadataForTest());
+  auto actual =
+      internal::ObjectMetadataJsonForCopy(CreateObjectMetadataForTest());
 
   nlohmann::json expected = {
       {"acl",
@@ -260,14 +263,16 @@ TEST(ObjectMetadataTest, JsonForCopy) {
 
 /// @test Verify that ObjectMetadataJsonForInsert works as expected.
 TEST(ObjectMetadataTest, JsonForInsertEmpty) {
-  nlohmann::json actual = ObjectMetadataJsonForInsert(ObjectMetadata());
+  nlohmann::json actual =
+      internal::ObjectMetadataJsonForInsert(ObjectMetadata());
   nlohmann::json expected({});
   EXPECT_EQ(expected, actual);
 }
 
 /// @test Verify that ObjectMetadataJsonForInsert() works as expected.
 TEST(ObjectMetadataTest, JsonForInsert) {
-  auto actual = ObjectMetadataJsonForInsert(CreateObjectMetadataForTest());
+  auto actual =
+      internal::ObjectMetadataJsonForInsert(CreateObjectMetadataForTest());
 
   nlohmann::json expected = {
       {"acl",
@@ -298,14 +303,16 @@ TEST(ObjectMetadataTest, JsonForInsert) {
 
 /// @test Verify that `ObjectMetadataJsonForRewrite()` works as expected.
 TEST(ObjectMetadataTest, JsonForRewriteEmpty) {
-  nlohmann::json actual = ObjectMetadataJsonForRewrite(ObjectMetadata());
+  nlohmann::json actual =
+      internal::ObjectMetadataJsonForRewrite(ObjectMetadata());
   nlohmann::json expected({});
   EXPECT_EQ(expected, actual);
 }
 
 /// @test Verify that `ObjectMetadataJsonForRewrite()` works as expected.
 TEST(ObjectMetadataTest, JsonForRewrite) {
-  auto actual = ObjectMetadataJsonForRewrite(CreateObjectMetadataForTest());
+  auto actual =
+      internal::ObjectMetadataJsonForRewrite(CreateObjectMetadataForTest());
 
   nlohmann::json expected = {
       {"acl",
@@ -334,7 +341,8 @@ TEST(ObjectMetadataTest, JsonForRewrite) {
 
 /// @test Verify that ObjectMetadataJsonForUpdate works as expected.
 TEST(ObjectMetadataTest, JsonForUpdateEmpty) {
-  nlohmann::json actual = ObjectMetadataJsonForUpdate(ObjectMetadata());
+  nlohmann::json actual =
+      internal::ObjectMetadataJsonForUpdate(ObjectMetadata());
   nlohmann::json expected({{"eventBasedHold", false}});
   EXPECT_EQ(expected, actual);
 }
@@ -342,7 +350,7 @@ TEST(ObjectMetadataTest, JsonForUpdateEmpty) {
 /// @test Verify that ObjectMetadataJsonForUpdate works as expected.
 TEST(ObjectMetadataTest, JsonForUpdate) {
   auto tested = CreateObjectMetadataForTest();
-  nlohmann::json actual = ObjectMetadataJsonForUpdate(tested);
+  nlohmann::json actual = internal::ObjectMetadataJsonForUpdate(tested);
 
   // Create a JSON object with only the writeable fields, because this is what
   // will be encoded in JsonPayloadForUpdate(). Before adding a new field,
diff --git a/google/cloud/storage/owner.h b/google/cloud/storage/owner.h
new file mode 100644
index 0000000000000..562724f8b174e
--- /dev/null
+++ b/google/cloud/storage/owner.h
@@ -0,0 +1,65 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OWNER_H
+#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OWNER_H
+
+#include "google/cloud/storage/version.h"
+#include <string>
+#include <tuple>
+#include <utility>
+
+namespace google {
+namespace cloud {
+namespace storage {
+GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
+
+/// A simple wrapper for the `owner` field in object and bucket metadata.
+struct Owner {
+  std::string entity;
+  std::string entity_id;
+};
+
+inline bool operator==(Owner const& lhs, Owner const& rhs) {
+  return std::tie(lhs.entity, lhs.entity_id) ==
+         std::tie(rhs.entity, rhs.entity_id);
+}
+
+inline bool operator<(Owner const& lhs, Owner const& rhs) {
+  return std::tie(lhs.entity, lhs.entity_id) <
+         std::tie(rhs.entity, rhs.entity_id);
+}
+
+inline bool operator!=(Owner const& lhs, Owner const& rhs) {
+  return std::rel_ops::operator!=(lhs, rhs);
+}
+
+inline bool operator>(Owner const& lhs, Owner const& rhs) {
+  return std::rel_ops::operator>(lhs, rhs);
+}
+
+inline bool operator<=(Owner const& lhs, Owner const& rhs) {
+  return std::rel_ops::operator<=(lhs, rhs);
+}
+
+inline bool operator>=(Owner const& lhs, Owner const& rhs) {
+  return std::rel_ops::operator>=(lhs, rhs);
+}
+
+GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
+}  // namespace storage
+}  // namespace cloud
+}  // namespace google
+
+#endif  // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OWNER_H