[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