diff options
author | Yifan Hong <elsk@google.com> | 2019-02-08 16:26:22 -0800 |
---|---|---|
committer | Yifan Hong <elsk@google.com> | 2019-02-14 16:11:15 -0800 |
commit | cb7a7ab24d394d62935fb8397f3c0a964e2bf1a7 (patch) | |
tree | 8f10cd176b2253721d914ad932833943b2089a97 /libjsonpb | |
parent | ac80c040c19219aa802ee7560de5f8546bb7d1e0 (diff) | |
download | extras-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.md | 46 | ||||
-rw-r--r-- | libjsonpb/parse/Android.bp | 43 | ||||
-rw-r--r-- | libjsonpb/parse/include/jsonpb/error_or.h | 72 | ||||
-rw-r--r-- | libjsonpb/parse/include/jsonpb/jsonpb.h | 60 | ||||
-rw-r--r-- | libjsonpb/parse/jsonpb.cpp | 75 |
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 |