/// \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 {
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;
}
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) {
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));
"foo": [
/* error: expected object */
123,
- { ... },
{ ... }
]
})";
// 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) {