Declarative JSON parser (#30442)

* Declarative JSON parser

* Automated change: Fix sanity tests

* fix

* shrinking stuff a little

* static vtables

* separate fns

* simpler?

* make maps work

* windows fixes

* Automated change: Fix sanity tests

* simplify code

* Automated change: Fix sanity tests

* vtable-test

* dont always create vec/map impls for every type

* comments

* make error consistent

* move method private

* progress

* durations!

* Automated change: Fix sanity tests

* fix

* fix

* fix

* Automated change: Fix sanity tests

* post-load

* Automated change: Fix sanity tests

* document JsonPostLoad() and add static_assert

* don't copy field names, to avoid length limitations

* use absl::Status

* accept either string or number for numeric values

* add test for direct data member of another struct type

* remove unused method

* add support for retaining part of the JSON wirthout processing

* update test for changes in Json::Parse() API

* add absl::optional support

* Automated change: Fix sanity tests

* fix tests, improve error messages, and add overload to parse to existing object

* remove overload of LoadFromJson()

* change special case for Json to instead use Json::Object

* fix build

* improve error structure, add missing types, and improve tests

* clang-format

* Automated change: Fix sanity tests

* fix build

* add LoadJsonObjectField(), add LoadFromJson() overload that takes an ErrorList parameter, and add tests for parsing bare top-level types

* fix msan

* Automated change: Fix sanity tests

* fix error message

* Automated change: Fix sanity tests

* add mechanism to conditionally disable individual fields

* fix build

Co-authored-by: Craig Tiller <craig.tiller@gmail.com>
Co-authored-by: ctiller <ctiller@users.noreply.github.com>
Co-authored-by: Craig Tiller <ctiller@google.com>
Co-authored-by: markdroth <markdroth@users.noreply.github.com>
pull/30490/head
Mark D. Roth 2 years ago committed by GitHub
parent c752a6b25e
commit af634e19b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      BUILD
  2. 38
      CMakeLists.txt
  3. 2
      Makefile
  4. 16
      build_autogenerated.yaml
  5. 1
      config.m4
  6. 1
      config.w32
  7. 4
      gRPC-C++.podspec
  8. 5
      gRPC-Core.podspec
  9. 3
      grpc.gemspec
  10. 2
      grpc.gyp
  11. 3
      package.xml
  12. 34
      src/core/lib/json/json_args.h
  13. 42
      src/core/lib/json/json_channel_args.h
  14. 204
      src/core/lib/json/json_object_loader.cc
  15. 544
      src/core/lib/json/json_object_loader.h
  16. 43
      src/core/lib/json/json_util.cc
  17. 1
      src/python/grpcio/grpc_core_dependencies.py
  18. 14
      test/core/json/BUILD
  19. 984
      test/core/json/json_object_loader_test.cc
  20. 3
      tools/doxygen/Doxyfile.c++.internal
  21. 3
      tools/doxygen/Doxyfile.core.internal
  22. 24
      tools/run_tests/generated/tests.json

42
BUILD

@ -7260,10 +7260,52 @@ grpc_cc_library(
"error",
"gpr_base",
"json",
"json_args",
"json_object_loader",
"time",
],
)
grpc_cc_library(
name = "json_args",
hdrs = ["src/core/lib/json/json_args.h"],
external_deps = ["absl/strings"],
deps = ["gpr_base"],
)
grpc_cc_library(
name = "json_object_loader",
srcs = ["src/core/lib/json/json_object_loader.cc"],
hdrs = ["src/core/lib/json/json_object_loader.h"],
external_deps = [
"absl/meta:type_traits",
"absl/status",
"absl/status:statusor",
"absl/strings",
"absl/types:optional",
],
deps = [
"gpr_base",
"json",
"json_args",
"time",
],
)
grpc_cc_library(
name = "json_channel_args",
hdrs = ["src/core/lib/json/json_channel_args.h"],
external_deps = [
"absl/strings",
"absl/types:optional",
],
deps = [
"channel_args",
"gpr",
"json_args",
],
)
### UPB Targets
grpc_upb_proto_library(

38
CMakeLists.txt generated

@ -1025,6 +1025,7 @@ if(gRPC_BUILD_TESTS)
endif()
add_dependencies(buildtests_cxx istio_echo_server_test)
add_dependencies(buildtests_cxx join_test)
add_dependencies(buildtests_cxx json_object_loader_test)
add_dependencies(buildtests_cxx json_test)
add_dependencies(buildtests_cxx json_token_test)
add_dependencies(buildtests_cxx jwt_verifier_test)
@ -2186,6 +2187,7 @@ add_library(grpc
src/core/lib/iomgr/wakeup_fd_nospecial.cc
src/core/lib/iomgr/wakeup_fd_pipe.cc
src/core/lib/iomgr/wakeup_fd_posix.cc
src/core/lib/json/json_object_loader.cc
src/core/lib/json/json_reader.cc
src/core/lib/json/json_util.cc
src/core/lib/json/json_writer.cc
@ -2794,6 +2796,7 @@ add_library(grpc_unsecure
src/core/lib/iomgr/wakeup_fd_nospecial.cc
src/core/lib/iomgr/wakeup_fd_pipe.cc
src/core/lib/iomgr/wakeup_fd_posix.cc
src/core/lib/json/json_object_loader.cc
src/core/lib/json/json_reader.cc
src/core/lib/json/json_util.cc
src/core/lib/json/json_writer.cc
@ -12623,6 +12626,41 @@ target_link_libraries(join_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(json_object_loader_test
test/core/json/json_object_loader_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(json_object_loader_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(json_object_loader_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_test_util
)
endif()
if(gRPC_BUILD_TESTS)

2
Makefile generated

@ -1548,6 +1548,7 @@ LIBGRPC_SRC = \
src/core/lib/iomgr/wakeup_fd_nospecial.cc \
src/core/lib/iomgr/wakeup_fd_pipe.cc \
src/core/lib/iomgr/wakeup_fd_posix.cc \
src/core/lib/json/json_object_loader.cc \
src/core/lib/json/json_reader.cc \
src/core/lib/json/json_util.cc \
src/core/lib/json/json_writer.cc \
@ -2020,6 +2021,7 @@ LIBGRPC_UNSECURE_SRC = \
src/core/lib/iomgr/wakeup_fd_nospecial.cc \
src/core/lib/iomgr/wakeup_fd_pipe.cc \
src/core/lib/iomgr/wakeup_fd_posix.cc \
src/core/lib/json/json_object_loader.cc \
src/core/lib/json/json_reader.cc \
src/core/lib/json/json_util.cc \
src/core/lib/json/json_writer.cc \

@ -849,6 +849,8 @@ libs:
- src/core/lib/iomgr/wakeup_fd_pipe.h
- src/core/lib/iomgr/wakeup_fd_posix.h
- src/core/lib/json/json.h
- src/core/lib/json/json_args.h
- src/core/lib/json/json_object_loader.h
- src/core/lib/json/json_util.h
- src/core/lib/load_balancing/lb_policy.h
- src/core/lib/load_balancing/lb_policy_factory.h
@ -1540,6 +1542,7 @@ libs:
- src/core/lib/iomgr/wakeup_fd_nospecial.cc
- src/core/lib/iomgr/wakeup_fd_pipe.cc
- src/core/lib/iomgr/wakeup_fd_posix.cc
- src/core/lib/json/json_object_loader.cc
- src/core/lib/json/json_reader.cc
- src/core/lib/json/json_util.cc
- src/core/lib/json/json_writer.cc
@ -2031,6 +2034,8 @@ libs:
- src/core/lib/iomgr/wakeup_fd_pipe.h
- src/core/lib/iomgr/wakeup_fd_posix.h
- src/core/lib/json/json.h
- src/core/lib/json/json_args.h
- src/core/lib/json/json_object_loader.h
- src/core/lib/json/json_util.h
- src/core/lib/load_balancing/lb_policy.h
- src/core/lib/load_balancing/lb_policy_factory.h
@ -2363,6 +2368,7 @@ libs:
- src/core/lib/iomgr/wakeup_fd_nospecial.cc
- src/core/lib/iomgr/wakeup_fd_pipe.cc
- src/core/lib/iomgr/wakeup_fd_posix.cc
- src/core/lib/json/json_object_loader.cc
- src/core/lib/json/json_reader.cc
- src/core/lib/json/json_util.cc
- src/core/lib/json/json_writer.cc
@ -7392,6 +7398,16 @@ targets:
- absl/types:variant
- absl/utility:utility
uses_polling: false
- name: json_object_loader_test
gtest: true
build: test
language: c++
headers: []
src:
- test/core/json/json_object_loader_test.cc
deps:
- grpc_test_util
uses_polling: false
- name: json_test
gtest: true
build: test

1
config.m4 generated

@ -607,6 +607,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/lib/iomgr/wakeup_fd_nospecial.cc \
src/core/lib/iomgr/wakeup_fd_pipe.cc \
src/core/lib/iomgr/wakeup_fd_posix.cc \
src/core/lib/json/json_object_loader.cc \
src/core/lib/json/json_reader.cc \
src/core/lib/json/json_util.cc \
src/core/lib/json/json_writer.cc \

1
config.w32 generated

@ -573,6 +573,7 @@ if (PHP_GRPC != "no") {
"src\\core\\lib\\iomgr\\wakeup_fd_nospecial.cc " +
"src\\core\\lib\\iomgr\\wakeup_fd_pipe.cc " +
"src\\core\\lib\\iomgr\\wakeup_fd_posix.cc " +
"src\\core\\lib\\json\\json_object_loader.cc " +
"src\\core\\lib\\json\\json_reader.cc " +
"src\\core\\lib\\json\\json_util.cc " +
"src\\core\\lib\\json\\json_writer.cc " +

4
gRPC-C++.podspec generated

@ -807,6 +807,8 @@ Pod::Spec.new do |s|
'src/core/lib/iomgr/wakeup_fd_pipe.h',
'src/core/lib/iomgr/wakeup_fd_posix.h',
'src/core/lib/json/json.h',
'src/core/lib/json/json_args.h',
'src/core/lib/json/json_object_loader.h',
'src/core/lib/json/json_util.h',
'src/core/lib/load_balancing/lb_policy.h',
'src/core/lib/load_balancing/lb_policy_factory.h',
@ -1659,6 +1661,8 @@ Pod::Spec.new do |s|
'src/core/lib/iomgr/wakeup_fd_pipe.h',
'src/core/lib/iomgr/wakeup_fd_posix.h',
'src/core/lib/json/json.h',
'src/core/lib/json/json_args.h',
'src/core/lib/json/json_object_loader.h',
'src/core/lib/json/json_util.h',
'src/core/lib/load_balancing/lb_policy.h',
'src/core/lib/load_balancing/lb_policy_factory.h',

5
gRPC-Core.podspec generated

@ -1312,6 +1312,9 @@ Pod::Spec.new do |s|
'src/core/lib/iomgr/wakeup_fd_posix.cc',
'src/core/lib/iomgr/wakeup_fd_posix.h',
'src/core/lib/json/json.h',
'src/core/lib/json/json_args.h',
'src/core/lib/json/json_object_loader.cc',
'src/core/lib/json/json_object_loader.h',
'src/core/lib/json/json_reader.cc',
'src/core/lib/json/json_util.cc',
'src/core/lib/json/json_util.h',
@ -2280,6 +2283,8 @@ Pod::Spec.new do |s|
'src/core/lib/iomgr/wakeup_fd_pipe.h',
'src/core/lib/iomgr/wakeup_fd_posix.h',
'src/core/lib/json/json.h',
'src/core/lib/json/json_args.h',
'src/core/lib/json/json_object_loader.h',
'src/core/lib/json/json_util.h',
'src/core/lib/load_balancing/lb_policy.h',
'src/core/lib/load_balancing/lb_policy_factory.h',

3
grpc.gemspec generated

@ -1225,6 +1225,9 @@ Gem::Specification.new do |s|
s.files += %w( src/core/lib/iomgr/wakeup_fd_posix.cc )
s.files += %w( src/core/lib/iomgr/wakeup_fd_posix.h )
s.files += %w( src/core/lib/json/json.h )
s.files += %w( src/core/lib/json/json_args.h )
s.files += %w( src/core/lib/json/json_object_loader.cc )
s.files += %w( src/core/lib/json/json_object_loader.h )
s.files += %w( src/core/lib/json/json_reader.cc )
s.files += %w( src/core/lib/json/json_util.cc )
s.files += %w( src/core/lib/json/json_util.h )

2
grpc.gyp generated

@ -899,6 +899,7 @@
'src/core/lib/iomgr/wakeup_fd_nospecial.cc',
'src/core/lib/iomgr/wakeup_fd_pipe.cc',
'src/core/lib/iomgr/wakeup_fd_posix.cc',
'src/core/lib/json/json_object_loader.cc',
'src/core/lib/json/json_reader.cc',
'src/core/lib/json/json_util.cc',
'src/core/lib/json/json_writer.cc',
@ -1339,6 +1340,7 @@
'src/core/lib/iomgr/wakeup_fd_nospecial.cc',
'src/core/lib/iomgr/wakeup_fd_pipe.cc',
'src/core/lib/iomgr/wakeup_fd_posix.cc',
'src/core/lib/json/json_object_loader.cc',
'src/core/lib/json/json_reader.cc',
'src/core/lib/json/json_util.cc',
'src/core/lib/json/json_writer.cc',

3
package.xml generated

@ -1207,6 +1207,9 @@
<file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_posix.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/iomgr/wakeup_fd_posix.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json_args.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json_object_loader.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json_object_loader.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json_reader.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json_util.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/json/json_util.h" role="src" />

@ -0,0 +1,34 @@
// Copyright 2020 gRPC authors.
//
// 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.
#ifndef GRPC_CORE_LIB_JSON_JSON_ARGS_H
#define GRPC_CORE_LIB_JSON_JSON_ARGS_H
#include <grpc/support/port_platform.h>
#include "absl/strings/string_view.h"
namespace grpc_core {
class JsonArgs {
public:
JsonArgs() = default;
virtual ~JsonArgs() = default;
virtual bool IsEnabled(absl::string_view /*key*/) const { return true; }
};
} // namespace grpc_core
#endif // GRPC_CORE_LIB_JSON_JSON_ARGS_H

@ -0,0 +1,42 @@
// Copyright 2022 gRPC authors.
//
// 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.
#ifndef GRPC_CORE_LIB_JSON_JSON_CHANNEL_ARGS_H
#define GRPC_CORE_LIB_JSON_JSON_CHANNEL_ARGS_H
#include <grpc/support/port_platform.h>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/json/json_args.h"
namespace grpc_core {
class JsonChannelArgs : public JsonArgs {
public:
explicit JsonChannelArgs(const ChannelArgs& args) : args_(args) {}
bool IsEnabled(absl::string_view key) const override {
return args_.GetBool(key).value_or(false);
}
private:
ChannelArgs args_;
};
} // namespace grpc_core
#endif // GRPC_CORE_LIB_JSON_JSON_CHANNEL_ARGS_H

@ -0,0 +1,204 @@
// Copyright 2020 gRPC authors.
//
// 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 <grpc/support/port_platform.h>
#include "src/core/lib/json/json_object_loader.h"
#include <algorithm>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/strip.h"
namespace grpc_core {
void ErrorList::PushField(absl::string_view ext) {
// Skip leading '.' for top-level field names.
if (fields_.empty()) absl::ConsumePrefix(&ext, ".");
fields_.emplace_back(std::string(ext));
}
void ErrorList::PopField() { fields_.pop_back(); }
void ErrorList::AddError(absl::string_view error) {
field_errors_[absl::StrJoin(fields_, "")].emplace_back(error);
}
bool ErrorList::FieldHasErrors() const {
return field_errors_.find(absl::StrJoin(fields_, "")) != field_errors_.end();
}
absl::Status ErrorList::status() const {
if (field_errors_.empty()) return absl::OkStatus();
std::vector<std::string> errors;
for (const auto& p : field_errors_) {
if (p.second.size() > 1) {
errors.emplace_back(absl::StrCat("field:", p.first, " errors:[",
absl::StrJoin(p.second, "; "), "]"));
} else {
errors.emplace_back(
absl::StrCat("field:", p.first, " error:", p.second[0]));
}
}
return absl::InvalidArgumentError(absl::StrCat(
"errors validating JSON: [", absl::StrJoin(errors, "; "), "]"));
}
namespace json_detail {
void LoadScalar::LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
ErrorList* errors) const {
// We accept either STRING or NUMBER for numeric values, as per
// https://developers.google.com/protocol-buffers/docs/proto3#json.
if (json.type() != Json::Type::STRING &&
(!IsNumber() || json.type() != Json::Type::NUMBER)) {
errors->AddError(
absl::StrCat("is not a ", IsNumber() ? "number" : "string"));
return;
}
return LoadInto(json.string_value(), dst, errors);
}
bool LoadString::IsNumber() const { return false; }
void LoadString::LoadInto(const std::string& value, void* dst,
ErrorList*) const {
*static_cast<std::string*>(dst) = value;
}
bool LoadDuration::IsNumber() const { return false; }
void LoadDuration::LoadInto(const std::string& value, void* dst,
ErrorList* errors) const {
absl::string_view buf(value);
if (!absl::ConsumeSuffix(&buf, "s")) {
errors->AddError("Not a duration (no s suffix)");
return;
}
buf = absl::StripAsciiWhitespace(buf);
auto decimal_point = buf.find('.');
int nanos = 0;
if (decimal_point != absl::string_view::npos) {
absl::string_view after_decimal = buf.substr(decimal_point + 1);
buf = buf.substr(0, decimal_point);
if (!absl::SimpleAtoi(after_decimal, &nanos)) {
errors->AddError("Not a duration (not a number of nanoseconds)");
return;
}
if (after_decimal.length() > 9) {
// We don't accept greater precision than nanos.
errors->AddError("Not a duration (too many digits after decimal)");
return;
}
for (size_t i = 0; i < (9 - after_decimal.length()); ++i) {
nanos *= 10;
}
}
int seconds;
if (!absl::SimpleAtoi(buf, &seconds)) {
errors->AddError("Not a duration (not a number of seconds)");
return;
}
*static_cast<Duration*>(dst) =
Duration::FromSecondsAndNanoseconds(seconds, nanos);
}
bool LoadNumber::IsNumber() const { return true; }
void LoadBool::LoadInto(const Json& json, const JsonArgs&, void* dst,
ErrorList* errors) const {
if (json.type() == Json::Type::JSON_TRUE) {
*static_cast<bool*>(dst) = true;
} else if (json.type() == Json::Type::JSON_FALSE) {
*static_cast<bool*>(dst) = false;
} else {
errors->AddError("is not a boolean");
}
}
void LoadUnprocessedJsonObject::LoadInto(const Json& json, const JsonArgs&,
void* dst, ErrorList* errors) const {
if (json.type() != Json::Type::OBJECT) {
errors->AddError("is not an object");
return;
}
*static_cast<Json::Object*>(dst) = json.object_value();
}
void LoadVector::LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const {
if (json.type() != Json::Type::ARRAY) {
errors->AddError("is not an array");
return;
}
const auto& array = json.array_value();
for (size_t i = 0; i < array.size(); ++i) {
ScopedField field(errors, absl::StrCat("[", i, "]"));
LoadOne(array[i], args, dst, errors);
}
}
void LoadMap::LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const {
if (json.type() != Json::Type::OBJECT) {
errors->AddError("is not an object");
return;
}
for (const auto& pair : json.object_value()) {
ScopedField field(errors, absl::StrCat("[\"", pair.first, "\"]"));
LoadOne(pair.second, args, pair.first, dst, errors);
}
}
bool LoadObject(const Json& json, const JsonArgs& args, const Element* elements,
size_t num_elements, void* dst, ErrorList* errors) {
if (json.type() != Json::Type::OBJECT) {
errors->AddError("is not an object");
return false;
}
for (size_t i = 0; i < num_elements; ++i) {
const Element& element = elements[i];
if (element.enable_key != nullptr && !args.IsEnabled(element.enable_key)) {
continue;
}
ScopedField field(errors, absl::StrCat(".", element.name));
const auto& it = json.object_value().find(element.name);
if (it == json.object_value().end()) {
if (element.optional) continue;
errors->AddError("field not present");
continue;
}
char* field_dst = static_cast<char*>(dst) + element.member_offset;
element.loader->LoadInto(it->second, args, field_dst, errors);
}
return true;
}
const Json* GetJsonObjectField(const Json::Object& json,
absl::string_view field, ErrorList* errors,
bool required) {
auto it = json.find(std::string(field));
if (it == json.end()) {
if (required) errors->AddError("field not present");
return nullptr;
}
return &it->second;
}
} // namespace json_detail
} // namespace grpc_core

@ -0,0 +1,544 @@
// Copyright 2020 gRPC authors.
//
// 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.
#ifndef GRPC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H
#define GRPC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H
#include <grpc/support/port_platform.h>
#include <cstdint>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include "absl/meta/type_traits.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/json/json_args.h"
// Provides a means to load JSON objects into C++ objects, with the aim of
// minimizing object code size.
//
// Usage:
// Given struct Foo:
// struct Foo {
// int a;
// int b;
// };
// We add a static JsonLoader() method to Foo to declare how to load the
// object from JSON, and an optional JsonPostLoad() method to do any
// necessary post-processing:
// struct Foo {
// int a;
// int b;
// static const JsonLoaderInterface* JsonLoader() {
// // Note: Field names must be string constants; they are not copied.
// static const auto* loader = JsonObjectLoader<Foo>()
// .Field("a", &Foo::a)
// .Field("b", &Foo::b)
// .Finish();
// return loader;
// }
// // Optional; omit if no post-processing needed.
// void JsonPostLoad(const Json& source, ErrorList* errors) { ++a; }
// };
// Now we can load Foo objects from JSON:
// absl::StatusOr<Foo> foo = LoadFromJson<Foo>(json);
namespace grpc_core {
// A list of errors that occurred during JSON parsing.
// If a non-empty list occurs during parsing, the parsing failed.
class ErrorList {
public:
// Record that we're reading some field.
void PushField(absl::string_view ext) GPR_ATTRIBUTE_NOINLINE;
// Record that we've finished reading that field.
void PopField() GPR_ATTRIBUTE_NOINLINE;
// Record that we've encountered an error.
void AddError(absl::string_view error) GPR_ATTRIBUTE_NOINLINE;
// Returns true if the current field has errors.
bool FieldHasErrors() const GPR_ATTRIBUTE_NOINLINE;
// Returns the resulting status of parsing.
absl::Status status() const;
// Return true if there are no errors.
bool ok() const { return field_errors_.empty(); }
size_t size() const { return field_errors_.size(); }
private:
// TODO(roth): If we don't actually have any fields for which we
// report more than one error, simplify this data structure.
std::map<std::string /*field_name*/, std::vector<std::string>> field_errors_;
std::vector<std::string> fields_;
};
// Note that we're reading a field, and remove it at the end of the scope.
class ScopedField {
public:
ScopedField(ErrorList* error_list, absl::string_view field_name)
: error_list_(error_list) {
error_list_->PushField(field_name);
}
~ScopedField() { error_list_->PopField(); }
private:
ErrorList* error_list_;
};
namespace json_detail {
// An un-typed JSON loader.
class LoaderInterface {
public:
// Convert json value to whatever type we're loading at dst.
// If errors occur, add them to error_list.
virtual void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const = 0;
protected:
virtual ~LoaderInterface() = default;
};
// Loads a scalar (string or number).
class LoadScalar : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override;
protected:
~LoadScalar() override = default;
private:
// true if we're loading a number, false if we're loading a string.
// We use a virtual function to store this decision in a vtable instead of
// needing an instance variable.
virtual bool IsNumber() const = 0;
virtual void LoadInto(const std::string& json, void* dst,
ErrorList* errors) const = 0;
};
// Load a string.
class LoadString : public LoadScalar {
protected:
~LoadString() override = default;
private:
bool IsNumber() const override;
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override;
};
// Load a Duration.
class LoadDuration : public LoadScalar {
protected:
~LoadDuration() override = default;
private:
bool IsNumber() const override;
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override;
};
// Load a number.
class LoadNumber : public LoadScalar {
protected:
~LoadNumber() override = default;
private:
bool IsNumber() const override;
};
// Load a signed number of type T.
template <typename T>
class TypedLoadSignedNumber : public LoadNumber {
protected:
~TypedLoadSignedNumber() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtoi(value, static_cast<T*>(dst))) {
errors->AddError("failed to parse number");
}
}
};
// Load an unsigned number of type T.
template <typename T>
class TypedLoadUnsignedNumber : public LoadNumber {
protected:
~TypedLoadUnsignedNumber() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtoi(value, static_cast<T*>(dst))) {
errors->AddError("failed to parse non-negative number");
}
}
};
// Load a float.
class LoadFloat : public LoadNumber {
protected:
~LoadFloat() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtof(value, static_cast<float*>(dst))) {
errors->AddError("failed to parse floating-point number");
}
}
};
// Load a double.
class LoadDouble : public LoadNumber {
protected:
~LoadDouble() override = default;
private:
void LoadInto(const std::string& value, void* dst,
ErrorList* errors) const override {
if (!absl::SimpleAtod(value, static_cast<double*>(dst))) {
errors->AddError("failed to parse floating-point number");
}
}
};
// Load a bool.
class LoadBool : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
ErrorList* errors) const override;
};
// Loads an unprocessed JSON object value.
class LoadUnprocessedJsonObject : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
ErrorList* errors) const override;
};
// Load a vector of some type.
class LoadVector : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override;
protected:
~LoadVector() override = default;
private:
virtual void LoadOne(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const = 0;
};
// Load a map of string->some type.
class LoadMap : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override;
protected:
~LoadMap() override = default;
private:
virtual void LoadOne(const Json& json, const JsonArgs& args,
const std::string& name, void* dst,
ErrorList* errors) const = 0;
};
// Fetch a LoaderInterface for some type.
template <typename T>
const LoaderInterface* LoaderForType();
// AutoLoader implements LoaderInterface for a type.
// The default asks the type for its LoaderInterface and then uses that.
// Classes that load from objects should provide a:
// static const JsonLoaderInterface* JsonLoader();
template <typename T>
class AutoLoader final : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
T::JsonLoader(args)->LoadInto(json, args, dst, errors);
}
};
// Specializations of AutoLoader for basic types.
template <>
class AutoLoader<std::string> final : public LoadString {};
template <>
class AutoLoader<Duration> final : public LoadDuration {};
template <>
class AutoLoader<int32_t> final : public TypedLoadSignedNumber<int32_t> {};
template <>
class AutoLoader<int64_t> final : public TypedLoadSignedNumber<int64_t> {};
template <>
class AutoLoader<uint32_t> final : public TypedLoadUnsignedNumber<uint32_t> {};
template <>
class AutoLoader<uint64_t> final : public TypedLoadUnsignedNumber<uint64_t> {};
template <>
class AutoLoader<float> final : public LoadFloat {};
template <>
class AutoLoader<double> final : public LoadDouble {};
template <>
class AutoLoader<bool> final : public LoadBool {};
template <>
class AutoLoader<Json::Object> final : public LoadUnprocessedJsonObject {};
// Specializations of AutoLoader for vectors.
template <typename T>
class AutoLoader<std::vector<T>> final : public LoadVector {
private:
void LoadOne(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const final {
auto* vec = static_cast<std::vector<T>*>(dst);
T value{};
LoaderForType<T>()->LoadInto(json, args, &value, errors);
vec->push_back(std::move(value));
}
};
// Specializations of AutoLoader for maps.
template <typename T>
class AutoLoader<std::map<std::string, T>> final : public LoadMap {
private:
void LoadOne(const Json& json, const JsonArgs& args, const std::string& name,
void* dst, ErrorList* errors) const final {
auto* map = static_cast<std::map<std::string, T>*>(dst);
T value{};
LoaderForType<T>()->LoadInto(json, args, &value, errors);
map->emplace(name, std::move(value));
}
};
// Specializations of AutoLoader for absl::optional<>.
template <typename T>
class AutoLoader<absl::optional<T>> final : public LoaderInterface {
public:
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
if (json.type() == Json::Type::JSON_NULL) return;
auto* opt = static_cast<absl::optional<T>*>(dst);
opt->emplace();
LoaderForType<T>()->LoadInto(json, args, &**opt, errors);
}
};
// Implementation of aforementioned LoaderForType.
// Simply keeps a static AutoLoader<T> and returns a pointer to that.
template <typename T>
const LoaderInterface* LoaderForType() {
static const auto* loader = new AutoLoader<T>();
return loader;
}
// Element describes one typed field to be loaded from a JSON object.
struct Element {
Element() = default;
template <typename A, typename B>
Element(const char* name, bool optional, B A::*p,
const LoaderInterface* loader, const char* enable_key)
: loader(loader),
member_offset(static_cast<uint16_t>(
reinterpret_cast<uintptr_t>(&(static_cast<A*>(nullptr)->*p)))),
optional(optional),
name(name),
enable_key(enable_key) {}
// The loader for this field.
const LoaderInterface* loader;
// Offset into the destination object to store the field.
uint16_t member_offset;
// Is this field optional?
bool optional;
// The name of the field.
const char* name;
// The key to use with JsonArgs to see if this field is enabled.
const char* enable_key;
};
// Vec<T, kSize> provides a constant array type that can be appended to by
// copying. It's setup so that most compilers can optimize away all of its
// operations.
template <typename T, size_t kSize>
class Vec {
public:
Vec(const Vec<T, kSize - 1>& other, const T& new_value) {
for (size_t i = 0; i < other.size(); i++) values_[i] = other.data()[i];
values_[kSize - 1] = new_value;
}
const T* data() const { return values_; }
size_t size() const { return kSize; }
private:
T values_[kSize];
};
template <typename T>
class Vec<T, 0> {
public:
const T* data() const { return nullptr; }
size_t size() const { return 0; }
};
// Given a list of elements, and a destination object, load the elements into
// the object from some parsed JSON.
// Returns false if the JSON object was not of type Json::Type::OBJECT.
bool LoadObject(const Json& json, const JsonArgs& args, const Element* elements,
size_t num_elements, void* dst, ErrorList* errors);
// Adaptor type - takes a compile time computed list of elements and implements
// LoaderInterface by calling LoadObject.
template <typename T, size_t kElemCount, typename Hidden = void>
class FinishedJsonObjectLoader final : public LoaderInterface {
public:
explicit FinishedJsonObjectLoader(const Vec<Element, kElemCount>& elements)
: elements_(elements) {}
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
LoadObject(json, args, elements_.data(), elements_.size(), dst, errors);
}
private:
GPR_NO_UNIQUE_ADDRESS Vec<Element, kElemCount> elements_;
};
// Specialization for when the object has a JsonPostLoad function exposed.
template <typename T, size_t kElemCount>
class FinishedJsonObjectLoader<T, kElemCount,
absl::void_t<decltype(&T::JsonPostLoad)>>
final : public LoaderInterface {
public:
explicit FinishedJsonObjectLoader(const Vec<Element, kElemCount>& elements)
: elements_(elements) {}
void LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const override {
// Call JsonPostLoad() only if json is a JSON object.
if (LoadObject(json, args, elements_.data(), elements_.size(), dst,
errors)) {
static_cast<T*>(dst)->JsonPostLoad(json, args, errors);
}
}
private:
GPR_NO_UNIQUE_ADDRESS Vec<Element, kElemCount> elements_;
};
// Builder type for JSON object loaders.
// Concatenate fields with Field, OptionalField, and then call Finish to obtain
// an object that implements LoaderInterface.
template <typename T, size_t kElemCount = 0>
class JsonObjectLoader final {
public:
JsonObjectLoader() {
static_assert(kElemCount == 0,
"Only initial loader step can have kElemCount==0.");
}
FinishedJsonObjectLoader<T, kElemCount>* Finish() const {
return new FinishedJsonObjectLoader<T, kElemCount>(elements_);
}
template <typename U>
JsonObjectLoader<T, kElemCount + 1> Field(
const char* name, U T::*p, const char* enable_key = nullptr) const {
return Field(name, false, p, enable_key);
}
template <typename U>
JsonObjectLoader<T, kElemCount + 1> OptionalField(
const char* name, U T::*p, const char* enable_key = nullptr) const {
return Field(name, true, p, enable_key);
}
JsonObjectLoader(const Vec<Element, kElemCount - 1>& elements,
Element new_element)
: elements_(elements, new_element) {}
private:
template <typename U>
JsonObjectLoader<T, kElemCount + 1> Field(const char* name, bool optional,
U T::*p,
const char* enable_key) const {
return JsonObjectLoader<T, kElemCount + 1>(
elements_, Element(name, optional, p, LoaderForType<U>(), enable_key));
}
GPR_NO_UNIQUE_ADDRESS Vec<Element, kElemCount> elements_;
};
const Json* GetJsonObjectField(const Json::Object& json,
absl::string_view field, ErrorList* errors,
bool required);
} // namespace json_detail
template <typename T>
using JsonObjectLoader = json_detail::JsonObjectLoader<T>;
using JsonLoaderInterface = json_detail::LoaderInterface;
template <typename T>
absl::StatusOr<T> LoadFromJson(const Json& json,
const JsonArgs& args = JsonArgs()) {
ErrorList error_list;
T result{};
json_detail::LoaderForType<T>()->LoadInto(json, args, &result, &error_list);
if (!error_list.ok()) return error_list.status();
return std::move(result);
}
template <typename T>
T LoadFromJson(const Json& json, const JsonArgs& args, ErrorList* error_list) {
T result{};
json_detail::LoaderForType<T>()->LoadInto(json, args, &result, error_list);
return result;
}
template <typename T>
absl::optional<T> LoadJsonObjectField(const Json::Object& json,
const JsonArgs& args,
absl::string_view field,
ErrorList* errors, bool required = true) {
ScopedField error_field(errors, absl::StrCat(".", field));
const Json* field_json =
json_detail::GetJsonObjectField(json, field, errors, required);
if (field_json == nullptr) return absl::nullopt;
T result{};
size_t starting_error_size = errors->size();
json_detail::LoaderForType<T>()->LoadInto(*field_json, args, &result, errors);
if (errors->size() > starting_error_size) return absl::nullopt;
return std::move(result);
}
} // namespace grpc_core
#endif // GRPC_CORE_LIB_JSON_JSON_OBJECT_LOADER_H

@ -20,46 +20,17 @@
#include "src/core/lib/json/json_util.h"
#include <string.h>
#include <grpc/support/string_util.h>
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gprpp/memory.h"
#include "src/core/lib/json/json_args.h"
#include "src/core/lib/json/json_object_loader.h"
namespace grpc_core {
bool ParseDurationFromJson(const Json& field, Duration* duration) {
if (field.type() != Json::Type::STRING) return false;
size_t len = field.string_value().size();
if (field.string_value()[len - 1] != 's') return false;
if (field.string_value() == Duration::Infinity().ToJsonString()) {
*duration = Duration::Infinity();
return true;
}
UniquePtr<char> buf(gpr_strdup(field.string_value().c_str()));
*(buf.get() + len - 1) = '\0'; // Remove trailing 's'.
char* decimal_point = strchr(buf.get(), '.');
int nanos = 0;
if (decimal_point != nullptr) {
*decimal_point = '\0';
nanos = gpr_parse_nonnegative_int(decimal_point + 1);
if (nanos == -1) {
return false;
}
int num_digits = static_cast<int>(strlen(decimal_point + 1));
if (num_digits > 9) { // We don't accept greater precision than nanos.
return false;
}
for (int i = 0; i < (9 - num_digits); ++i) {
nanos *= 10;
}
}
int seconds =
decimal_point == buf.get() ? 0 : gpr_parse_nonnegative_int(buf.get());
if (seconds == -1) return false;
*duration = Duration::FromSecondsAndNanoseconds(seconds, nanos);
return true;
json_detail::AutoLoader<Duration> loader;
ErrorList errors;
static_cast<json_detail::LoaderInterface&>(loader).LoadInto(
field, JsonArgs(), duration, &errors);
return errors.ok();
}
bool ExtractJsonBool(const Json& json, absl::string_view field_name,

@ -582,6 +582,7 @@ CORE_SOURCE_FILES = [
'src/core/lib/iomgr/wakeup_fd_nospecial.cc',
'src/core/lib/iomgr/wakeup_fd_pipe.cc',
'src/core/lib/iomgr/wakeup_fd_posix.cc',
'src/core/lib/json/json_object_loader.cc',
'src/core/lib/json/json_reader.cc',
'src/core/lib/json/json_util.cc',
'src/core/lib/json/json_writer.cc',

@ -47,3 +47,17 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
],
)
grpc_cc_test(
name = "json_object_loader_test",
srcs = ["json_object_loader_test.cc"],
external_deps = [
"gtest",
],
language = "C++",
uses_polling = False,
deps = [
"//:json_object_loader",
"//test/core/util:grpc_test_util",
],
)

@ -0,0 +1,984 @@
// Copyright 2021 gRPC authors.
//
// 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 "src/core/lib/json/json_object_loader.h"
#include <cstdint>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/str_join.h"
namespace grpc_core {
namespace {
template <typename T>
absl::StatusOr<T> Parse(absl::string_view json,
const JsonArgs& args = JsonArgs()) {
auto parsed = Json::Parse(json);
if (!parsed.ok()) return parsed.status();
return LoadFromJson<T>(*parsed, args);
}
//
// Signed integer tests
//
template <typename T>
class SignedIntegerTest : public ::testing::Test {};
TYPED_TEST_SUITE_P(SignedIntegerTest);
TYPED_TEST_P(SignedIntegerTest, IntegerFields) {
struct TestStruct {
TypeParam value = 0;
TypeParam optional_value = 0;
absl::optional<TypeParam> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Positive number.
auto test_struct = Parse<TestStruct>("{\"value\": 5}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, 5);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Negative number.
test_struct = Parse<TestStruct>("{\"value\": -5}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, -5);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Encoded in a JSON string.
test_struct = Parse<TestStruct>("{\"value\": \"5\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, 5);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": 5, \"optional_value\": 7, "
"\"absl_optional_value\": 9}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, 5);
EXPECT_EQ(test_struct->optional_value, 7);
EXPECT_EQ(test_struct->absl_optional_value, 9);
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": {}, "
"\"absl_optional_value\": true}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not a number; "
"field:optional_value error:is not a number; "
"field:value error:is not a number]")
<< test_struct.status();
}
REGISTER_TYPED_TEST_SUITE_P(SignedIntegerTest, IntegerFields);
using IntegerTypes = ::testing::Types<int32_t, int64_t>;
INSTANTIATE_TYPED_TEST_SUITE_P(My, SignedIntegerTest, IntegerTypes);
//
// Unsigned integer tests
//
template <typename T>
class UnsignedIntegerTest : public ::testing::Test {};
TYPED_TEST_SUITE_P(UnsignedIntegerTest);
TYPED_TEST_P(UnsignedIntegerTest, IntegerFields) {
struct TestStruct {
TypeParam value = 0;
TypeParam optional_value = 0;
absl::optional<TypeParam> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Positive number.
auto test_struct = Parse<TestStruct>("{\"value\": 5}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, 5);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Negative number.
test_struct = Parse<TestStruct>("{\"value\": -5}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:value error:failed to parse non-negative number]")
<< test_struct.status();
// Encoded in a JSON string.
test_struct = Parse<TestStruct>("{\"value\": \"5\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, 5);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": 5, \"optional_value\": 7, "
"\"absl_optional_value\": 9}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, 5);
EXPECT_EQ(test_struct->optional_value, 7);
ASSERT_TRUE(test_struct->absl_optional_value.has_value());
EXPECT_EQ(*test_struct->absl_optional_value, 9);
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": {}, "
"\"absl_optional_value\": true}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not a number; "
"field:optional_value error:is not a number; "
"field:value error:is not a number]")
<< test_struct.status();
}
REGISTER_TYPED_TEST_SUITE_P(UnsignedIntegerTest, IntegerFields);
using UnsignedIntegerTypes = ::testing::Types<uint32_t, uint64_t>;
INSTANTIATE_TYPED_TEST_SUITE_P(My, UnsignedIntegerTest, UnsignedIntegerTypes);
//
// Floating-point tests
//
template <typename T>
class FloatingPointTest : public ::testing::Test {};
TYPED_TEST_SUITE_P(FloatingPointTest);
TYPED_TEST_P(FloatingPointTest, FloatFields) {
struct TestStruct {
TypeParam value = 0;
TypeParam optional_value = 0;
absl::optional<TypeParam> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Positive number.
auto test_struct = Parse<TestStruct>("{\"value\": 5.2}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_NEAR(test_struct->value, 5.2, 0.0001);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Negative number.
test_struct = Parse<TestStruct>("{\"value\": -5.2}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_NEAR(test_struct->value, -5.2, 0.0001);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Encoded in a JSON string.
test_struct = Parse<TestStruct>("{\"value\": \"5.2\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_NEAR(test_struct->value, 5.2, 0.0001);
EXPECT_EQ(test_struct->optional_value, 0);
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": 5.2, \"optional_value\": 7.5, "
"\"absl_optional_value\": 9.8}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_NEAR(test_struct->value, 5.2, 0.0001);
EXPECT_NEAR(test_struct->optional_value, 7.5, 0.0001);
ASSERT_TRUE(test_struct->absl_optional_value.has_value());
EXPECT_NEAR(*test_struct->absl_optional_value, 9.8, 0.0001);
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": {}, "
"\"absl_optional_value\": true}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not a number; "
"field:optional_value error:is not a number; "
"field:value error:is not a number]")
<< test_struct.status();
}
REGISTER_TYPED_TEST_SUITE_P(FloatingPointTest, FloatFields);
using FloatingPointTypes = ::testing::Types<float, double>;
INSTANTIATE_TYPED_TEST_SUITE_P(My, FloatingPointTest, FloatingPointTypes);
//
// Boolean tests
//
TEST(JsonObjectLoader, BooleanFields) {
struct TestStruct {
bool value = false;
bool optional_value = true;
absl::optional<bool> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// True.
auto test_struct = Parse<TestStruct>("{\"value\": true}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, true);
EXPECT_EQ(test_struct->optional_value, true); // Unmodified.
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// False.
test_struct = Parse<TestStruct>("{\"value\": false}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, false);
EXPECT_EQ(test_struct->optional_value, true); // Unmodified.
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": true, \"optional_value\": false,"
"\"absl_optional_value\": true}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, true);
EXPECT_EQ(test_struct->optional_value, false);
EXPECT_EQ(test_struct->absl_optional_value, true);
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": {}, "
"\"absl_optional_value\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not a boolean; "
"field:optional_value error:is not a boolean; "
"field:value error:is not a boolean]")
<< test_struct.status();
}
//
// String tests
//
TEST(JsonObjectLoader, StringFields) {
struct TestStruct {
std::string value;
std::string optional_value;
absl::optional<std::string> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Valid string.
auto test_struct = Parse<TestStruct>("{\"value\": \"foo\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, "foo");
EXPECT_EQ(test_struct->optional_value, "");
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": \"foo\", \"optional_value\": \"bar\","
"\"absl_optional_value\": \"baz\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, "foo");
EXPECT_EQ(test_struct->optional_value, "bar");
EXPECT_EQ(test_struct->absl_optional_value, "baz");
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": {}, "
"\"absl_optional_value\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not a string; "
"field:optional_value error:is not a string; "
"field:value error:is not a string]")
<< test_struct.status();
}
//
// Duration tests
//
TEST(JsonObjectLoader, DurationFields) {
struct TestStruct {
Duration value = Duration::Zero();
Duration optional_value = Duration::Zero();
absl::optional<Duration> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Valid duration string.
auto test_struct = Parse<TestStruct>("{\"value\": \"3s\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, Duration::Seconds(3));
EXPECT_EQ(test_struct->optional_value, Duration::Zero());
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Invalid duration strings.
test_struct = Parse<TestStruct>(
"{\"value\": \"3sec\", \"optional_value\": \"foos\","
"\"absl_optional_value\": \"1.0123456789s\"}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:"
"Not a duration (too many digits after decimal); "
"field:optional_value error:"
"Not a duration (not a number of seconds); "
"field:value error:Not a duration (no s suffix)]")
<< test_struct.status();
test_struct = Parse<TestStruct>("{\"value\": \"3.xs\"}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:value error:Not a duration (not a number of nanoseconds)]")
<< test_struct.status();
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": \"3s\", \"optional_value\": \"3.2s\", "
"\"absl_optional_value\": \"10s\"}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->value, Duration::Seconds(3));
EXPECT_EQ(test_struct->optional_value, Duration::Milliseconds(3200));
EXPECT_EQ(test_struct->absl_optional_value, Duration::Seconds(10));
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": {}, "
"\"absl_optional_value\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not a string; "
"field:optional_value error:is not a string; "
"field:value error:is not a string]")
<< test_struct.status();
}
//
// Json::Object tests
//
TEST(JsonObjectLoader, JsonObjectFields) {
struct TestStruct {
Json::Object value;
Json::Object optional_value;
absl::optional<Json::Object> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Valid object.
auto test_struct = Parse<TestStruct>("{\"value\": {\"a\":1}}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(Json{test_struct->value}.Dump(), "{\"a\":1}");
EXPECT_EQ(Json{test_struct->optional_value}.Dump(), "{}");
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": {\"a\":1}, \"optional_value\": {\"b\":2}, "
"\"absl_optional_value\": {\"c\":3}}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(Json{test_struct->value}.Dump(), "{\"a\":1}");
EXPECT_EQ(Json{test_struct->optional_value}.Dump(), "{\"b\":2}");
ASSERT_TRUE(test_struct->absl_optional_value.has_value());
EXPECT_EQ(Json{*test_struct->absl_optional_value}.Dump(), "{\"c\":3}");
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": true, "
"\"absl_optional_value\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not an object; "
"field:optional_value error:is not an object; "
"field:value error:is not an object]")
<< test_struct.status();
}
//
// map<> tests
//
TEST(JsonObjectLoader, MapFields) {
struct TestStruct {
std::map<std::string, int32_t> value;
std::map<std::string, std::string> optional_value;
absl::optional<std::map<std::string, bool>> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Valid map.
auto test_struct = Parse<TestStruct>("{\"value\": {\"a\":1}}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_THAT(test_struct->value,
::testing::ElementsAre(::testing::Pair("a", 1)));
EXPECT_THAT(test_struct->optional_value, ::testing::ElementsAre());
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": {\"a\":1}, \"optional_value\": {\"b\":\"foo\"}, "
"\"absl_optional_value\": {\"c\":true}}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_THAT(test_struct->value,
::testing::ElementsAre(::testing::Pair("a", 1)));
EXPECT_THAT(test_struct->optional_value,
::testing::ElementsAre(::testing::Pair("b", "foo")));
ASSERT_TRUE(test_struct->absl_optional_value.has_value());
EXPECT_THAT(*test_struct->absl_optional_value,
::testing::ElementsAre(::testing::Pair("c", true)));
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": [], \"optional_value\": true, "
"\"absl_optional_value\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not an object; "
"field:optional_value error:is not an object; "
"field:value error:is not an object]")
<< test_struct.status();
// Wrong JSON type for map value.
test_struct = Parse<TestStruct>(
"{\"value\": {\"a\":\"foo\"}, \"optional_value\": {\"b\":true}, "
"\"absl_optional_value\": {\"c\":1}}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value[\"c\"] error:is not a boolean; "
"field:optional_value[\"b\"] error:is not a string; "
"field:value[\"a\"] error:failed to parse number]")
<< test_struct.status();
}
//
// vector<> tests
//
TEST(JsonObjectLoader, VectorFields) {
struct TestStruct {
std::vector<int32_t> value;
std::vector<std::string> optional_value;
absl::optional<std::vector<bool>> absl_optional_value;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("value", &TestStruct::value)
.OptionalField("optional_value", &TestStruct::optional_value)
.OptionalField("absl_optional_value",
&TestStruct::absl_optional_value)
.Finish();
return loader;
}
};
// Valid map.
auto test_struct = Parse<TestStruct>("{\"value\": [1, 2, 3]}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_THAT(test_struct->value, ::testing::ElementsAre(1, 2, 3));
EXPECT_THAT(test_struct->optional_value, ::testing::ElementsAre());
EXPECT_FALSE(test_struct->absl_optional_value.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:value error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"value\": [4, 5, 6], \"optional_value\": [\"foo\", \"bar\"], "
"\"absl_optional_value\": [true, false, true]}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_THAT(test_struct->value, ::testing::ElementsAre(4, 5, 6));
EXPECT_THAT(test_struct->optional_value,
::testing::ElementsAre("foo", "bar"));
ASSERT_TRUE(test_struct->absl_optional_value.has_value());
EXPECT_THAT(*test_struct->absl_optional_value,
::testing::ElementsAre(true, false, true));
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"value\": {}, \"optional_value\": true, "
"\"absl_optional_value\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value error:is not an array; "
"field:optional_value error:is not an array; "
"field:value error:is not an array]")
<< test_struct.status();
// Wrong JSON type for map value.
test_struct = Parse<TestStruct>(
"{\"value\": [\"foo\", \"bar\"], \"optional_value\": [true, false], "
"\"absl_optional_value\": [1, 2]}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_value[0] error:is not a boolean; "
"field:absl_optional_value[1] error:is not a boolean; "
"field:optional_value[0] error:is not a string; "
"field:optional_value[1] error:is not a string; "
"field:value[0] error:failed to parse number; "
"field:value[1] error:failed to parse number]")
<< test_struct.status();
}
//
// Nested struct tests
//
TEST(JsonObjectLoader, NestedStructFields) {
struct NestedStruct {
int32_t inner = 0;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader = JsonObjectLoader<NestedStruct>()
.Field("inner", &NestedStruct::inner)
.Finish();
return loader;
}
};
struct TestStruct {
NestedStruct outer;
NestedStruct optional_outer;
absl::optional<NestedStruct> absl_optional_outer;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("outer", &TestStruct::outer)
.OptionalField("optional_outer", &TestStruct::optional_outer)
.OptionalField("absl_optional_outer",
&TestStruct::absl_optional_outer)
.Finish();
return loader;
}
};
// Valid nested struct.
auto test_struct = Parse<TestStruct>("{\"outer\": {\"inner\": 1}}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->outer.inner, 1);
EXPECT_EQ(test_struct->optional_outer.inner, 0);
EXPECT_FALSE(test_struct->absl_optional_outer.has_value());
// Fails if required field is not present.
test_struct = Parse<TestStruct>("{}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:outer error:field not present]")
<< test_struct.status();
// Fails if inner required field is not present.
test_struct = Parse<TestStruct>("{\"outer\": {}}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(
test_struct.status().message(),
"errors validating JSON: [field:outer.inner error:field not present]")
<< test_struct.status();
// Optional fields present.
test_struct = Parse<TestStruct>(
"{\"outer\": {\"inner\":1}, \"optional_outer\": {\"inner\":2}, "
"\"absl_optional_outer\": {\"inner\":3}}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->outer.inner, 1);
EXPECT_EQ(test_struct->optional_outer.inner, 2);
ASSERT_TRUE(test_struct->absl_optional_outer.has_value());
EXPECT_EQ(test_struct->absl_optional_outer->inner, 3);
// Wrong JSON type.
test_struct = Parse<TestStruct>(
"{\"outer\": \"foo\", \"optional_outer\": true, "
"\"absl_optional_outer\": 1}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_outer error:is not an object; "
"field:optional_outer error:is not an object; "
"field:outer error:is not an object]")
<< test_struct.status();
// Wrong JSON type for inner value.
test_struct = Parse<TestStruct>(
"{\"outer\": {\"inner\":\"foo\"}, \"optional_outer\": {\"inner\":true}, "
"\"absl_optional_outer\": {\"inner\":[]}}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: ["
"field:absl_optional_outer.inner error:is not a number; "
"field:optional_outer.inner error:is not a number; "
"field:outer.inner error:failed to parse number]")
<< test_struct.status();
}
TEST(JsonObjectLoader, BareString) {
auto parsed = Parse<std::string>("\"foo\"");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_EQ(*parsed, "foo");
}
TEST(JsonObjectLoader, BareDuration) {
auto parsed = Parse<Duration>("\"1.5s\"");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_EQ(*parsed, Duration::Milliseconds(1500));
}
TEST(JsonObjectLoader, BareSignedInteger) {
auto parsed = Parse<int32_t>("5");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_EQ(*parsed, 5);
}
TEST(JsonObjectLoader, BareUnsignedInteger) {
auto parsed = Parse<uint32_t>("5");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_EQ(*parsed, 5);
}
TEST(JsonObjectLoader, BareFloat) {
auto parsed = Parse<float>("5.2");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_NEAR(*parsed, 5.2, 0.001);
}
TEST(JsonObjectLoader, BareBool) {
auto parsed = Parse<bool>("true");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_TRUE(*parsed);
}
TEST(JsonObjectLoader, BareVector) {
auto parsed = Parse<std::vector<int32_t>>("[1, 2, 3]");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_THAT(*parsed, ::testing::ElementsAre(1, 2, 3));
}
TEST(JsonObjectLoader, BareMap) {
auto parsed =
Parse<std::map<std::string, int32_t>>("{\"a\":1, \"b\":2, \"c\":3}");
ASSERT_TRUE(parsed.ok()) << parsed.status();
EXPECT_THAT(*parsed, ::testing::ElementsAre(::testing::Pair("a", 1),
::testing::Pair("b", 2),
::testing::Pair("c", 3)));
}
TEST(JsonObjectLoader, IgnoresUnsupportedFields) {
struct TestStruct {
int32_t a = 0;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>().Field("a", &TestStruct::a).Finish();
return loader;
}
};
auto test_struct = Parse<TestStruct>("{\"a\": 3, \"b\":false}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->a, 3);
}
TEST(JsonObjectLoader, IgnoresDisabledFields) {
class FakeJsonArgs : public JsonArgs {
public:
FakeJsonArgs() = default;
bool IsEnabled(absl::string_view key) const override {
return key != "disabled";
}
};
struct TestStruct {
int32_t a = 0;
int32_t b = 0;
int32_t c = 0;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>()
.Field("a", &TestStruct::a, "disabled")
.OptionalField("b", &TestStruct::b, "disabled")
.OptionalField("c", &TestStruct::c, "enabled")
.Finish();
return loader;
}
};
// Fields "a" and "b" have the wrong types, but we ignore them,
// because they're disabled.
auto test_struct =
Parse<TestStruct>("{\"a\":false, \"b\":false, \"c\":1}", FakeJsonArgs());
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->a, 0);
EXPECT_EQ(test_struct->b, 0);
EXPECT_EQ(test_struct->c, 1);
}
TEST(JsonObjectLoader, PostLoadHook) {
struct TestStruct {
int32_t a = 0;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader = JsonObjectLoader<TestStruct>()
.OptionalField("a", &TestStruct::a)
.Finish();
return loader;
}
void JsonPostLoad(const Json& /*source*/, const JsonArgs& /*args*/,
ErrorList* /*errors*/) {
++a;
}
};
auto test_struct = Parse<TestStruct>("{\"a\": 1}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->a, 2);
test_struct = Parse<TestStruct>("{}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->a, 1);
}
TEST(JsonObjectLoader, CustomValidationInPostLoadHook) {
struct TestStruct {
int32_t a = 0;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>().Field("a", &TestStruct::a).Finish();
return loader;
}
void JsonPostLoad(const Json& /*source*/, const JsonArgs& /*args*/,
ErrorList* errors) {
ScopedField field(errors, ".a");
if (!errors->FieldHasErrors() && a <= 0) {
errors->AddError("must be greater than 0");
}
}
};
// Value greater than 0.
auto test_struct = Parse<TestStruct>("{\"a\": 1}");
ASSERT_TRUE(test_struct.ok()) << test_struct.status();
EXPECT_EQ(test_struct->a, 1);
// Value 0, triggers custom validation.
test_struct = Parse<TestStruct>("{\"a\": 0}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:a error:must be greater than 0]")
<< test_struct.status();
// Invalid type, generates built-in parsing error, so custom
// validation will not generate a new error.
test_struct = Parse<TestStruct>("{\"a\": []}");
EXPECT_EQ(test_struct.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(test_struct.status().message(),
"errors validating JSON: [field:a error:is not a number]")
<< test_struct.status();
}
TEST(JsonObjectLoader, LoadFromJsonWithErrorList) {
struct TestStruct {
int32_t a = 0;
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
static const auto* loader =
JsonObjectLoader<TestStruct>().Field("a", &TestStruct::a).Finish();
return loader;
}
};
// Valid.
{
absl::string_view json_str = "{\"a\":1}";
auto json = Json::Parse(json_str);
ASSERT_TRUE(json.ok()) << json.status();
ErrorList errors;
TestStruct test_struct =
LoadFromJson<TestStruct>(*json, JsonArgs(), &errors);
ASSERT_TRUE(errors.ok()) << errors.status();
EXPECT_EQ(test_struct.a, 1);
}
// Invalid.
{
absl::string_view json_str = "{\"a\":\"foo\"}";
auto json = Json::Parse(json_str);
ASSERT_TRUE(json.ok()) << json.status();
ErrorList errors;
LoadFromJson<TestStruct>(*json, JsonArgs(), &errors);
absl::Status status = errors.status();
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(status.message(),
"errors validating JSON: [field:a error:failed to parse number]")
<< status;
}
}
TEST(JsonObjectLoader, LoadJsonObjectField) {
absl::string_view json_str = "{\"int\":1}";
auto json = Json::Parse(json_str);
ASSERT_TRUE(json.ok()) << json.status();
// Load a valid field.
{
ErrorList errors;
auto value = LoadJsonObjectField<int32_t>(json->object_value(), JsonArgs(),
"int", &errors);
ASSERT_TRUE(value.has_value()) << errors.status();
EXPECT_EQ(*value, 1);
EXPECT_TRUE(errors.ok());
}
// An optional field that is not present.
{
ErrorList errors;
auto value = LoadJsonObjectField<int32_t>(json->object_value(), JsonArgs(),
"not_present", &errors,
/*required=*/false);
EXPECT_FALSE(value.has_value());
EXPECT_TRUE(errors.ok());
}
// A required field that is not present.
{
ErrorList errors;
auto value = LoadJsonObjectField<int32_t>(json->object_value(), JsonArgs(),
"not_present", &errors);
EXPECT_FALSE(value.has_value());
auto status = errors.status();
EXPECT_THAT(status.code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(status.message(),
"errors validating JSON: ["
"field:not_present error:field not present]")
<< status;
}
// Value has the wrong type.
{
ErrorList errors;
auto value = LoadJsonObjectField<std::string>(json->object_value(),
JsonArgs(), "int", &errors);
EXPECT_FALSE(value.has_value());
auto status = errors.status();
EXPECT_THAT(status.code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(status.message(),
"errors validating JSON: [field:int error:is not a string]")
<< status;
}
}
} // namespace
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -2208,6 +2208,9 @@ src/core/lib/iomgr/wakeup_fd_pipe.h \
src/core/lib/iomgr/wakeup_fd_posix.cc \
src/core/lib/iomgr/wakeup_fd_posix.h \
src/core/lib/json/json.h \
src/core/lib/json/json_args.h \
src/core/lib/json/json_object_loader.cc \
src/core/lib/json/json_object_loader.h \
src/core/lib/json/json_reader.cc \
src/core/lib/json/json_util.cc \
src/core/lib/json/json_util.h \

@ -2001,6 +2001,9 @@ src/core/lib/iomgr/wakeup_fd_pipe.h \
src/core/lib/iomgr/wakeup_fd_posix.cc \
src/core/lib/iomgr/wakeup_fd_posix.h \
src/core/lib/json/json.h \
src/core/lib/json/json_args.h \
src/core/lib/json/json_object_loader.cc \
src/core/lib/json/json_object_loader.h \
src/core/lib/json/json_reader.cc \
src/core/lib/json/json_util.cc \
src/core/lib/json/json_util.h \

@ -4163,6 +4163,30 @@
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "json_object_loader_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save