mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-02 04:04:19 +00:00
Fix #2775: Add parser option to skip unknown JSON fields
This commit is contained in:
@@ -320,6 +320,7 @@ struct IDLOptions {
|
||||
bool one_file;
|
||||
bool proto_mode;
|
||||
bool generate_all;
|
||||
bool skip_unexpected_fields_in_json;
|
||||
|
||||
// Possible options for the more general generator below.
|
||||
enum Language { kJava, kCSharp, kGo, kMAX };
|
||||
@@ -337,6 +338,7 @@ struct IDLOptions {
|
||||
one_file(false),
|
||||
proto_mode(false),
|
||||
generate_all(false),
|
||||
skip_unexpected_fields_in_json(false),
|
||||
lang(IDLOptions::kJava) {}
|
||||
};
|
||||
|
||||
@@ -477,7 +479,10 @@ private:
|
||||
CHECKED_ERROR ParseProtoDecl();
|
||||
CHECKED_ERROR ParseProtoCurliesOrIdent();
|
||||
CHECKED_ERROR ParseTypeFromProtoType(Type *type);
|
||||
|
||||
CHECKED_ERROR SkipAnyJsonValue();
|
||||
CHECKED_ERROR SkipJsonObject();
|
||||
CHECKED_ERROR SkipJsonArray();
|
||||
CHECKED_ERROR SkipJsonString();
|
||||
CHECKED_ERROR DoParse(const char *_source, const char **include_paths,
|
||||
const char *source_filename);
|
||||
|
||||
|
||||
@@ -673,24 +673,33 @@ CheckedError Parser::ParseTable(const StructDef &struct_def, std::string *value,
|
||||
EXPECT(opts.strict_json ? kTokenStringConstant : kTokenIdentifier);
|
||||
}
|
||||
auto field = struct_def.fields.Lookup(name);
|
||||
if (!field) return Error("unknown field: " + name);
|
||||
EXPECT(':');
|
||||
Value val = field->value;
|
||||
ECHECK(ParseAnyValue(val, field, fieldn));
|
||||
size_t i = field_stack_.size();
|
||||
// Hardcoded insertion-sort with error-check.
|
||||
// If fields are specified in order, then this loop exits immediately.
|
||||
for (; i > field_stack_.size() - fieldn; i--) {
|
||||
auto existing_field = field_stack_[i - 1].second;
|
||||
if (existing_field == field)
|
||||
return Error("field set more than once: " + field->name);
|
||||
if (existing_field->value.offset < field->value.offset) break;
|
||||
if (!field) {
|
||||
if (!opts.skip_unexpected_fields_in_json) {
|
||||
return Error("unknown field: " + name);
|
||||
} else {
|
||||
EXPECT(':');
|
||||
ECHECK(SkipAnyJsonValue());
|
||||
}
|
||||
} else {
|
||||
EXPECT(':');
|
||||
Value val = field->value;
|
||||
ECHECK(ParseAnyValue(val, field, fieldn));
|
||||
size_t i = field_stack_.size();
|
||||
// Hardcoded insertion-sort with error-check.
|
||||
// If fields are specified in order, then this loop exits immediately.
|
||||
for (; i > field_stack_.size() - fieldn; i--) {
|
||||
auto existing_field = field_stack_[i - 1].second;
|
||||
if (existing_field == field)
|
||||
return Error("field set more than once: " + field->name);
|
||||
if (existing_field->value.offset < field->value.offset) break;
|
||||
}
|
||||
field_stack_.insert(field_stack_.begin() + i, std::make_pair(val, field));
|
||||
fieldn++;
|
||||
}
|
||||
field_stack_.insert(field_stack_.begin() + i, std::make_pair(val, field));
|
||||
fieldn++;
|
||||
if (Is('}')) { NEXT(); break; }
|
||||
EXPECT(',');
|
||||
}
|
||||
|
||||
if (struct_def.fixed && fieldn != struct_def.fields.vec.size())
|
||||
return Error("struct: wrong number of initializers: " + struct_def.name);
|
||||
|
||||
@@ -1480,6 +1489,72 @@ CheckedError Parser::ParseTypeFromProtoType(Type *type) {
|
||||
return NoError();
|
||||
}
|
||||
|
||||
CheckedError Parser::SkipAnyJsonValue() {
|
||||
switch (token_) {
|
||||
case '{':
|
||||
ECHECK(SkipJsonObject());
|
||||
break;
|
||||
case kTokenStringConstant:
|
||||
ECHECK(SkipJsonString());
|
||||
break;
|
||||
case '[':
|
||||
ECHECK(SkipJsonArray());
|
||||
break;
|
||||
case kTokenIntegerConstant:
|
||||
EXPECT(kTokenIntegerConstant);
|
||||
break;
|
||||
case kTokenFloatConstant:
|
||||
EXPECT(kTokenFloatConstant);
|
||||
break;
|
||||
default:
|
||||
return Error(std::string("Unexpected token:") + std::string(1, token_));
|
||||
}
|
||||
return NoError();
|
||||
}
|
||||
|
||||
CheckedError Parser::SkipJsonObject() {
|
||||
EXPECT('{');
|
||||
size_t fieldn = 0;
|
||||
|
||||
while (true) {
|
||||
if ((!opts.strict_json || !fieldn) && Is('}')) break;
|
||||
|
||||
if (!Is(kTokenStringConstant))
|
||||
EXPECT(opts.strict_json ? kTokenStringConstant : kTokenIdentifier);
|
||||
|
||||
EXPECT(':');
|
||||
ECHECK(SkipAnyJsonValue());
|
||||
fieldn++;
|
||||
|
||||
if (Is('}')) break;
|
||||
EXPECT(',');
|
||||
}
|
||||
|
||||
NEXT();
|
||||
return NoError();
|
||||
}
|
||||
|
||||
CheckedError Parser::SkipJsonArray() {
|
||||
EXPECT('[');
|
||||
|
||||
while (true) {
|
||||
if (Is(']')) break;
|
||||
|
||||
ECHECK(SkipAnyJsonValue());
|
||||
|
||||
if (Is(']')) break;
|
||||
EXPECT(',');
|
||||
}
|
||||
|
||||
NEXT();
|
||||
return NoError();
|
||||
}
|
||||
|
||||
CheckedError Parser::SkipJsonString() {
|
||||
EXPECT(kTokenStringConstant);
|
||||
return NoError();
|
||||
}
|
||||
|
||||
bool Parser::Parse(const char *source, const char **include_paths,
|
||||
const char *source_filename) {
|
||||
return !DoParse(source, include_paths, source_filename).Check();
|
||||
|
||||
@@ -813,6 +813,26 @@ void UnicodeTest() {
|
||||
"\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\"}", true);
|
||||
}
|
||||
|
||||
void UnknownFieldsTest() {
|
||||
flatbuffers::IDLOptions opts;
|
||||
opts.skip_unexpected_fields_in_json = true;
|
||||
flatbuffers::Parser parser(opts);
|
||||
|
||||
TEST_EQ(parser.Parse("table T { str:string; i:int;}"
|
||||
"root_type T;"
|
||||
"{ str:\"test\","
|
||||
"unknown_int:10,"
|
||||
"unknown_float:1.0,"
|
||||
"unknown_array: [ 1, 2, 3, 4],"
|
||||
"unknown_object: { i: 10 },"
|
||||
"i:10}"), true);
|
||||
|
||||
std::string jsongen;
|
||||
parser.opts.indent_step = -1;
|
||||
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
|
||||
TEST_EQ(jsongen == "{str: \"test\",i: 10}", true);
|
||||
}
|
||||
|
||||
int main(int /*argc*/, const char * /*argv*/[]) {
|
||||
// Run our various test suites:
|
||||
|
||||
@@ -837,6 +857,7 @@ int main(int /*argc*/, const char * /*argv*/[]) {
|
||||
ScientificTest();
|
||||
EnumStringsTest();
|
||||
UnicodeTest();
|
||||
UnknownFieldsTest();
|
||||
|
||||
if (!testing_fails) {
|
||||
TEST_OUTPUT_LINE("ALL TESTS PASSED");
|
||||
|
||||
Reference in New Issue
Block a user