diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h --- a/llvm/include/llvm/Support/JSON.h +++ b/llvm/include/llvm/Support/JSON.h @@ -741,10 +741,9 @@ /// \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 @@ 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 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 --- a/llvm/unittests/Support/JSONTest.cpp +++ b/llvm/unittests/Support/JSONTest.cpp @@ -375,10 +375,8 @@ } 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 @@ std::map> R; CustomStruct ExpectedStruct = {"foo", 42, true}; std::map> 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 @@ "foo": [ /* error: expected object */ 123, - { ... }, { ... } ] })"; @@ -443,6 +434,10 @@ // Optional 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) {