mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 18:54:02 +01:00
[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
This commit is contained in:
parent
030fbbba7c
commit
4c407017f3
@ -741,10 +741,9 @@ template <typename T> Value toJSON(const llvm::Optional<T> &Opt) {
|
|||||||
/// \code
|
/// \code
|
||||||
/// bool fromJSON(const Value &E, MyStruct &R, Path P) {
|
/// bool fromJSON(const Value &E, MyStruct &R, Path P) {
|
||||||
/// ObjectMapper O(E, P);
|
/// ObjectMapper O(E, P);
|
||||||
/// if (!O || !O.map("mandatory_field", R.MandatoryField))
|
/// // When returning false, error details were already reported.
|
||||||
/// return false; // error details are already reported
|
/// return O && O.map("mandatory_field", R.MandatoryField) &&
|
||||||
/// O.map("optional_field", R.OptionalField);
|
/// O.mapOptional("optional_field", R.OptionalField);
|
||||||
/// return true;
|
|
||||||
/// }
|
/// }
|
||||||
/// \endcode
|
/// \endcode
|
||||||
class ObjectMapper {
|
class ObjectMapper {
|
||||||
@ -780,6 +779,16 @@ public:
|
|||||||
return true;
|
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:
|
private:
|
||||||
const Object *O;
|
const Object *O;
|
||||||
Path P;
|
Path P;
|
||||||
|
@ -375,10 +375,8 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
|
|||||||
}
|
}
|
||||||
bool fromJSON(const Value &E, CustomStruct &R, Path P) {
|
bool fromJSON(const Value &E, CustomStruct &R, Path P) {
|
||||||
ObjectMapper O(E, P);
|
ObjectMapper O(E, P);
|
||||||
if (!O || !O.map("str", R.S) || !O.map("int", R.I))
|
return O && O.map("str", R.S) && O.map("int", R.I) &&
|
||||||
return false;
|
O.mapOptional("bool", R.B);
|
||||||
O.map("bool", R.B);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string errorContext(const Value &V, const Path::Root &R) {
|
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;
|
std::map<std::string, std::vector<CustomStruct>> R;
|
||||||
CustomStruct ExpectedStruct = {"foo", 42, true};
|
CustomStruct ExpectedStruct = {"foo", 42, true};
|
||||||
std::map<std::string, std::vector<CustomStruct>> Expected;
|
std::map<std::string, std::vector<CustomStruct>> Expected;
|
||||||
Value J = Object{
|
Value J = Object{{"foo", Array{
|
||||||
{"foo",
|
Object{
|
||||||
Array{
|
{"str", "foo"},
|
||||||
Object{
|
{"int", 42},
|
||||||
{"str", "foo"},
|
{"bool", true},
|
||||||
{"int", 42},
|
{"unknown", "ignored"},
|
||||||
{"bool", true},
|
},
|
||||||
{"unknown", "ignored"},
|
Object{{"str", "bar"}},
|
||||||
},
|
}}};
|
||||||
Object{{"str", "bar"}},
|
|
||||||
Object{
|
|
||||||
{"str", "baz"}, {"bool", "string"}, // OK, deserialize ignores.
|
|
||||||
},
|
|
||||||
}}};
|
|
||||||
Expected["foo"] = {
|
Expected["foo"] = {
|
||||||
CustomStruct("foo", 42, true),
|
CustomStruct("foo", 42, true),
|
||||||
CustomStruct("bar", llvm::None, false),
|
CustomStruct("bar", llvm::None, false),
|
||||||
CustomStruct("baz", llvm::None, false),
|
|
||||||
};
|
};
|
||||||
Path::Root Root("CustomStruct");
|
Path::Root Root("CustomStruct");
|
||||||
ASSERT_TRUE(fromJSON(J, R, Root));
|
ASSERT_TRUE(fromJSON(J, R, Root));
|
||||||
@ -423,7 +415,6 @@ TEST(JSONTest, Deserialize) {
|
|||||||
"foo": [
|
"foo": [
|
||||||
/* error: expected object */
|
/* error: expected object */
|
||||||
123,
|
123,
|
||||||
{ ... },
|
|
||||||
{ ... }
|
{ ... }
|
||||||
]
|
]
|
||||||
})";
|
})";
|
||||||
@ -443,6 +434,10 @@ TEST(JSONTest, Deserialize) {
|
|||||||
// Optional<T> must parse as the correct type if present.
|
// Optional<T> must parse as the correct type if present.
|
||||||
EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"int", "string"}}, V, Root));
|
EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"int", "string"}}, V, Root));
|
||||||
EXPECT_EQ("expected integer at CustomStruct.int", toString(Root.getError()));
|
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) {
|
TEST(JSONTest, ParseDeserialize) {
|
||||||
|
Loading…
Reference in New Issue
Block a user