[llvm] 31a575b - [JSON] Add ObjectMapper::mapOptional to validate optional data.
Sam McCall via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 12 03:58:45 PDT 2020
Author: Sam McCall
Date: 2020-10-12T12:48:08+02:00
New Revision: 31a575bbc0fc925c29bcd6fc49b5a100ad22ae63
URL: https://github.com/llvm/llvm-project/commit/31a575bbc0fc925c29bcd6fc49b5a100ad22ae63
DIFF: https://github.com/llvm/llvm-project/commit/31a575bbc0fc925c29bcd6fc49b5a100ad22ae63.diff
LOG: [JSON] Add ObjectMapper::mapOptional to validate optional data.
Currently the idiom for mapping optional fields is:
ObjectMapper O(Val, P);
if (!O.map("required1", Out.R1) || !O.map("required2", Out.R2))
return false;
O.map("optional1", Out.O1); // ignore result
return true;
If `optional1` is present but malformed, then we won't detect/report
that error. We may even leave `Out` in an incomplete state while returning true.
Instead, we'd often prefer to ignore `optional1` if it is absent, but otherwise
behave just like map().
Differential Revision: https://reviews.llvm.org/D89128
Added:
Modified:
llvm/include/llvm/Support/JSON.h
llvm/unittests/Support/JSONTest.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h
index 455673e42e97..9a8f915eeef7 100644
--- a/llvm/include/llvm/Support/JSON.h
+++ b/llvm/include/llvm/Support/JSON.h
@@ -741,10 +741,9 @@ template <typename T> Value toJSON(const llvm::Optional<T> &Opt) {
/// \code
/// bool fromJSON(const Value &E, MyStruct &R, Path P) {
/// ObjectMapper O(E, P);
-/// if (!O || !O.map("mandatory_field", R.MandatoryField))
-/// return false; // error details are already reported
-/// O.map("optional_field", R.OptionalField);
-/// return true;
+/// // When returning false, error details were already reported.
+/// return O && O.map("mandatory_field", R.MandatoryField) &&
+/// O.mapOptional("optional_field", R.OptionalField);
/// }
/// \endcode
class ObjectMapper {
@@ -780,6 +779,16 @@ class ObjectMapper {
return true;
}
+ /// Maps a property to a field, if it exists.
+ /// If the property exists and is invalid, reports an error.
+ /// If the property does not exist, Out is unchanged.
+ template <typename T> bool mapOptional(StringLiteral Prop, T &Out) {
+ assert(*this && "Must check this is an object before calling map()");
+ if (const Value *E = O->get(Prop))
+ return fromJSON(*E, Out, P.field(Prop));
+ return true;
+ }
+
private:
const Object *O;
Path P;
diff --git a/llvm/unittests/Support/JSONTest.cpp b/llvm/unittests/Support/JSONTest.cpp
index 9f17c98b4db4..ed9a72d36b06 100644
--- a/llvm/unittests/Support/JSONTest.cpp
+++ b/llvm/unittests/Support/JSONTest.cpp
@@ -375,10 +375,8 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
}
bool fromJSON(const Value &E, CustomStruct &R, Path P) {
ObjectMapper O(E, P);
- if (!O || !O.map("str", R.S) || !O.map("int", R.I))
- return false;
- O.map("bool", R.B);
- return true;
+ return O && O.map("str", R.S) && O.map("int", R.I) &&
+ O.mapOptional("bool", R.B);
}
static std::string errorContext(const Value &V, const Path::Root &R) {
@@ -392,24 +390,18 @@ TEST(JSONTest, Deserialize) {
std::map<std::string, std::vector<CustomStruct>> R;
CustomStruct ExpectedStruct = {"foo", 42, true};
std::map<std::string, std::vector<CustomStruct>> Expected;
- Value J = Object{
- {"foo",
- Array{
- Object{
- {"str", "foo"},
- {"int", 42},
- {"bool", true},
- {"unknown", "ignored"},
- },
- Object{{"str", "bar"}},
- Object{
- {"str", "baz"}, {"bool", "string"}, // OK, deserialize ignores.
- },
- }}};
+ Value J = Object{{"foo", Array{
+ Object{
+ {"str", "foo"},
+ {"int", 42},
+ {"bool", true},
+ {"unknown", "ignored"},
+ },
+ Object{{"str", "bar"}},
+ }}};
Expected["foo"] = {
CustomStruct("foo", 42, true),
CustomStruct("bar", llvm::None, false),
- CustomStruct("baz", llvm::None, false),
};
Path::Root Root("CustomStruct");
ASSERT_TRUE(fromJSON(J, R, Root));
@@ -423,7 +415,6 @@ TEST(JSONTest, Deserialize) {
"foo": [
/* error: expected object */
123,
- { ... },
{ ... }
]
})";
@@ -443,6 +434,10 @@ TEST(JSONTest, Deserialize) {
// Optional<T> must parse as the correct type if present.
EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"int", "string"}}, V, Root));
EXPECT_EQ("expected integer at CustomStruct.int", toString(Root.getError()));
+
+ // mapOptional must parse as the correct type if present.
+ EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"bool", "string"}}, V, Root));
+ EXPECT_EQ("expected boolean at CustomStruct.bool", toString(Root.getError()));
}
TEST(JSONTest, ParseDeserialize) {
More information about the llvm-commits
mailing list