OSDN Git Service

Add libjsonpbparse.
authorYifan Hong <elsk@google.com>
Sat, 9 Feb 2019 00:26:22 +0000 (16:26 -0800)
committerYifan Hong <elsk@google.com>
Fri, 15 Feb 2019 00:11:15 +0000 (16:11 -0800)
- libjsonpbparse is intended to be used in client parsing code,
  but due to libprotobuf versions it can't be used yet. Hence,
  libprocessgroup continue to use libjsoncpp.

Test: builds
Bug: 123664216
Change-Id: I01b08a0e6ba1110f2f3398ddde9333622153dc9a

libjsonpb/README.md [new file with mode: 0644]
libjsonpb/parse/Android.bp [new file with mode: 0644]
libjsonpb/parse/include/jsonpb/error_or.h [new file with mode: 0644]
libjsonpb/parse/include/jsonpb/jsonpb.h [new file with mode: 0644]
libjsonpb/parse/jsonpb.cpp [new file with mode: 0644]

diff --git a/libjsonpb/README.md b/libjsonpb/README.md
new file mode 100644 (file)
index 0000000..d8bf6e2
--- /dev/null
@@ -0,0 +1,46 @@
+# `libjsonpbparse`
+
+This library provides functions to parse a JSON file to a structured Protobuf
+message.
+
+At this time of writing, `libprotobuf-cpp-full` is at version 3.0.0-beta, and
+unknown fields in a JSON file cannot be ignored. Do **NOT** use this library in
+vendor / recovery until `libprotobuf-cpp-full` is updated.
+
+## Using `libjsoncpp` in parser code
+
+Since `libjsonpbparse` cannot be used in vendor / recovery processes yet,
+`libjsoncpp` is used instead. However, there are notable differences in the
+logic of `libjsoncpp` and `libprotobuf` when parsing JSON files.
+
+- There are no implicit string to integer conversion in `libjsoncpp`. Hence:
+  - If the Protobuf schema uses 64-bit integers (`(s|fixed|u|)int64`):
+    - The JSON file must use strings (to pass tests in `libjsonpbverify`)
+    - Parser code (that uses `libjsoncpp`) must explicitly convert strings to
+      integers. Example:
+      ```c++
+      strtoull(value.asString(), 0, 10)
+      ```
+  - If the Protobuf schema uses special floating point values:
+    - The JSON file must use strings (e.g. `"NaN"`, `"Infinity"`, `"-Infinity"`)
+    - Parser code must explicitly handle these cases. Example:
+      ```c++
+      double d;
+      if (value.isNumeric()) {
+        d = value.asDouble();
+      } else {
+        auto&& s = value.asString();
+        if (s == "NaN") d = std::numeric_limits<double>::quiet_NaN();
+        else if (s == "Infinity") d = std::numeric_limits<double>::infinity();
+        else if (s == "-Infinity") d = -std::numeric_limits<double>::infinity();
+      }
+      ```
+- `libprotobuf` accepts either `lowerCamelCase` (or `json_name` option if it is
+  defined) or the original field name as keys in the input JSON file.
+  The test in `libjsonpbverify` explicitly check this case to avoid ambiguity;
+  only the original field name (or `json_name` option if it is defined) can be
+  used.
+
+Once `libprotobuf` in the source tree is updated to a higher version and
+`libjsonpbparse` is updated to ignore unknown fields in JSON files, all parsing
+code must be converted to use `libjsonpbparse` for consistency.
diff --git a/libjsonpb/parse/Android.bp b/libjsonpb/parse/Android.bp
new file mode 100644 (file)
index 0000000..eaec342
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// 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
+//
+//      http://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.
+
+// A convenient library to convert any JSON string to a specific Protobuf
+// message using reflection.
+
+cc_library_static {
+    name: "libjsonpbparse",
+    host_supported: true,
+
+    // DO NOT make it vendor_available / recovery_available; it doesn't know
+    // how to ignore unknown fields yet. Use it only for testing purposes.
+    // TODO(b/123664216): Make it understand unknown fields when libprotobuf is
+    // updated to version 3.1+, and let libprocessgroup to use this instead of
+    // libjsoncpp.
+    vendor_available: false,
+    recovery_available: false,
+
+    export_include_dirs: ["include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    srcs: [
+        "jsonpb.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libprotobuf-cpp-full",
+    ],
+}
diff --git a/libjsonpb/parse/include/jsonpb/error_or.h b/libjsonpb/parse/include/jsonpb/error_or.h
new file mode 100644 (file)
index 0000000..66e2296
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+
+#pragma once
+
+#include <string>
+#include <variant>
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace jsonpb {
+
+template <typename T>
+struct ErrorOr {
+    template <class... Args>
+    explicit ErrorOr(Args&&... args) : data_(kIndex1, std::forward<Args>(args)...) {}
+    T& operator*() {
+        CHECK(ok());
+        return *std::get_if<1u>(&data_);
+    }
+    const T& operator*() const {
+        CHECK(ok());
+        return *std::get_if<1u>(&data_);
+    }
+    T* operator->() {
+        CHECK(ok());
+        return std::get_if<1u>(&data_);
+    }
+    const T* operator->() const {
+        CHECK(ok());
+        return std::get_if<1u>(&data_);
+    }
+    const std::string& error() const {
+        CHECK(!ok());
+        return *std::get_if<0u>(&data_);
+    }
+    bool ok() const { return data_.index() != 0; }
+    static ErrorOr<T> MakeError(const std::string& message) {
+        return ErrorOr<T>(message, Tag::kDummy);
+    }
+
+  private:
+    enum class Tag { kDummy };
+    static constexpr std::in_place_index_t<0> kIndex0{};
+    static constexpr std::in_place_index_t<1> kIndex1{};
+    ErrorOr(const std::string& msg, Tag) : data_(kIndex0, msg) {}
+
+    std::variant<std::string, T> data_;
+};
+
+template <typename T>
+inline ErrorOr<T> MakeError(const std::string& message) {
+    return ErrorOr<T>::MakeError(message);
+}
+
+}  // namespace jsonpb
+}  // namespace android
diff --git a/libjsonpb/parse/include/jsonpb/jsonpb.h b/libjsonpb/parse/include/jsonpb/jsonpb.h
new file mode 100644 (file)
index 0000000..350db7f
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+
+#pragma once
+
+#include <string>
+
+#include <jsonpb/error_or.h>
+
+#include <google/protobuf/message.h>
+
+namespace android {
+namespace jsonpb {
+
+namespace internal {
+ErrorOr<std::monostate> JsonStringToMessage(const std::string& content,
+                                            google::protobuf::Message* message);
+}  // namespace internal
+
+// TODO: JsonStringToMessage is a newly added function in protobuf
+// and is not yet available in the android tree. Replace this function with
+// https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#JsonStringToMessage.details
+// when the android tree gets updated
+template <typename T>
+ErrorOr<T> JsonStringToMessage(const std::string& content) {
+    ErrorOr<T> ret;
+    auto error = internal::JsonStringToMessage(content, &*ret);
+    if (!error.ok()) {
+        return MakeError<T>(error.error());
+    }
+    return ret;
+}
+
+// TODO: MessageToJsonString is a newly added function in protobuf
+// and is not yet available in the android tree. Replace this function with
+// https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#MessageToJsonString.details
+// when the android tree gets updated.
+//
+// The new MessageToJsonString also allows preserving proto field names. However,
+// the function here can't. Hence, a field name "foo_bar" without json_name option
+// will be "fooBar" in the final output. Additional checks are needed to ensure
+// that doesn't happen.
+ErrorOr<std::string> MessageToJsonString(const google::protobuf::Message& message);
+
+}  // namespace jsonpb
+}  // namespace android
diff --git a/libjsonpb/parse/jsonpb.cpp b/libjsonpb/parse/jsonpb.cpp
new file mode 100644 (file)
index 0000000..bd95dbd
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#include <jsonpb/jsonpb.h>
+
+#include <android-base/logging.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/message.h>
+#include <google/protobuf/util/json_util.h>
+#include <google/protobuf/util/type_resolver_util.h>
+
+namespace android {
+namespace jsonpb {
+
+using google::protobuf::DescriptorPool;
+using google::protobuf::Message;
+using google::protobuf::scoped_ptr;
+using google::protobuf::util::NewTypeResolverForDescriptorPool;
+using google::protobuf::util::TypeResolver;
+
+static constexpr char kTypeUrlPrefix[] = "type.googleapis.com";
+
+std::string GetTypeUrl(const Message& message) {
+    return std::string(kTypeUrlPrefix) + "/" + message.GetDescriptor()->full_name();
+}
+
+ErrorOr<std::string> MessageToJsonString(const Message& message) {
+    scoped_ptr<TypeResolver> resolver(
+            NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
+
+    google::protobuf::util::JsonOptions options;
+    options.add_whitespace = true;
+
+    std::string json;
+    auto status = BinaryToJsonString(resolver.get(), GetTypeUrl(message),
+                                     message.SerializeAsString(), &json, options);
+
+    if (!status.ok()) {
+        return MakeError<std::string>(status.error_message().as_string());
+    }
+    return ErrorOr<std::string>(std::move(json));
+}
+
+namespace internal {
+ErrorOr<std::monostate> JsonStringToMessage(const std::string& content, Message* message) {
+    scoped_ptr<TypeResolver> resolver(
+            NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
+
+    std::string binary;
+    auto status = JsonToBinaryString(resolver.get(), GetTypeUrl(*message), content, &binary);
+    if (!status.ok()) {
+        return MakeError<std::monostate>(status.error_message().as_string());
+    }
+    if (!message->ParseFromString(binary)) {
+        return MakeError<std::monostate>("Fail to parse.");
+    }
+    return ErrorOr<std::monostate>();
+}
+}  // namespace internal
+
+}  // namespace jsonpb
+}  // namespace android