summaryrefslogtreecommitdiff
path: root/libjsonpb
diff options
context:
space:
mode:
authorYifan Hong <elsk@google.com>2019-02-08 16:26:22 -0800
committerYifan Hong <elsk@google.com>2019-02-14 16:11:15 -0800
commitcb7a7ab24d394d62935fb8397f3c0a964e2bf1a7 (patch)
tree8f10cd176b2253721d914ad932833943b2089a97 /libjsonpb
parentac80c040c19219aa802ee7560de5f8546bb7d1e0 (diff)
downloadextras-cb7a7ab24d394d62935fb8397f3c0a964e2bf1a7.tar.gz
Add libjsonpbparse.
- 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
Diffstat (limited to 'libjsonpb')
-rw-r--r--libjsonpb/README.md46
-rw-r--r--libjsonpb/parse/Android.bp43
-rw-r--r--libjsonpb/parse/include/jsonpb/error_or.h72
-rw-r--r--libjsonpb/parse/include/jsonpb/jsonpb.h60
-rw-r--r--libjsonpb/parse/jsonpb.cpp75
5 files changed, 296 insertions, 0 deletions
diff --git a/libjsonpb/README.md b/libjsonpb/README.md
new file mode 100644
index 00000000..d8bf6e22
--- /dev/null
+++ b/libjsonpb/README.md
@@ -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
index 00000000..eaec342a
--- /dev/null
+++ b/libjsonpb/parse/Android.bp
@@ -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
index 00000000..66e22969
--- /dev/null
+++ b/libjsonpb/parse/include/jsonpb/error_or.h
@@ -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
index 00000000..350db7fb
--- /dev/null
+++ b/libjsonpb/parse/include/jsonpb/jsonpb.h
@@ -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
index 00000000..bd95dbdf
--- /dev/null
+++ b/libjsonpb/parse/jsonpb.cpp
@@ -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