diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 1980f2218..362105319 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -443,6 +443,13 @@ template class Array { return_type operator[](uoffset_t i) const { return Get(i); } + // If this is a Vector of enums, T will be its storage type, not the enum + // type. This function makes it convenient to retrieve value with enum + // type E. + template E GetEnum(uoffset_t i) const { + return static_cast(Get(i)); + } + const_iterator begin() const { return const_iterator(Data(), 0); } const_iterator end() const { return const_iterator(Data(), size()); } @@ -518,10 +525,12 @@ template class Array, length> { static_assert(flatbuffers::is_same::value, "unexpected type T"); public: + typedef const void* return_type; + const uint8_t *Data() const { return data_; } // Make idl_gen_text.cpp::PrintContainer happy. - const void *operator[](uoffset_t) const { + return_type operator[](uoffset_t) const { FLATBUFFERS_ASSERT(false); return nullptr; } diff --git a/src/idl_gen_text.cpp b/src/idl_gen_text.cpp index 4c00472df..e4d21824b 100644 --- a/src/idl_gen_text.cpp +++ b/src/idl_gen_text.cpp @@ -23,301 +23,337 @@ namespace flatbuffers { -static bool GenStruct(const StructDef &struct_def, const Table *table, - int indent, const IDLOptions &opts, std::string *_text); +struct PrintScalarTag {}; +struct PrintPointerTag {}; +template struct PrintTag { typedef PrintScalarTag type; }; +template<> struct PrintTag { typedef PrintPointerTag type; }; -// If indentation is less than 0, that indicates we don't want any newlines -// either. -const char *NewLine(const IDLOptions &opts) { - return opts.indent_step >= 0 ? "\n" : ""; -} - -int Indent(const IDLOptions &opts) { return std::max(opts.indent_step, 0); } - -// Output an identifier with or without quotes depending on strictness. -void OutputIdentifier(const std::string &name, const IDLOptions &opts, - std::string *_text) { - std::string &text = *_text; - if (opts.strict_json) text += "\""; - text += name; - if (opts.strict_json) text += "\""; -} - -// Print (and its template specialization below for pointers) generate text -// for a single FlatBuffer value into JSON format. -// The general case for scalars: -template -bool Print(T val, Type type, int /*indent*/, const uint8_t * /*prev_val*/, - soffset_t /*vector_index*/, const IDLOptions &opts, - std::string *_text) { - std::string &text = *_text; - if (type.enum_def && opts.output_enum_identifiers) { - std::vector enum_values; - if (auto ev = type.enum_def->ReverseLookup(static_cast(val))) { - enum_values.push_back(ev); - } else if (val && type.enum_def->attributes.Lookup("bit_flags")) { - for (auto it = type.enum_def->Vals().begin(), - e = type.enum_def->Vals().end(); - it != e; ++it) { - if ((*it)->GetAsUInt64() & static_cast(val)) - enum_values.push_back(*it); - } - } - if (!enum_values.empty()) { - text += '\"'; - for (auto it = enum_values.begin(), e = enum_values.end(); it != e; ++it) - text += (*it)->name + ' '; - text[text.length() - 1] = '\"'; - return true; - } +struct JsonPrinter { + // If indentation is less than 0, that indicates we don't want any newlines + // either. + void AddNewLine() { + if (opts.indent_step >= 0) text += '\n'; } - if (type.base_type == BASE_TYPE_BOOL) { - text += val != 0 ? "true" : "false"; - } else { + void AddIndent(int ident) { text.append(ident, ' '); } + + int Indent() const { return std::max(opts.indent_step, 0); } + + // Output an identifier with or without quotes depending on strictness. + void OutputIdentifier(const std::string &name) { + if (opts.strict_json) text += '\"'; + text += name; + if (opts.strict_json) text += '\"'; + } + + // Print (and its template specialization below for pointers) generate text + // for a single FlatBuffer value into JSON format. + // The general case for scalars: + template + bool PrintScalar(T val, const Type &type, int /*indent*/) { + if (IsBool(type.base_type)) { + text += val != 0 ? "true" : "false"; + return true; // done + } + + if (opts.output_enum_identifiers && type.enum_def) { + const auto &enum_def = *type.enum_def; + if (auto ev = enum_def.ReverseLookup(static_cast(val))) { + text += '\"'; + text += ev->name; + text += '\"'; + return true; // done + } else if (val && enum_def.attributes.Lookup("bit_flags")) { + const auto entry_len = text.length(); + const auto u64 = static_cast(val); + uint64_t mask = 0; + text += '\"'; + for (auto it = enum_def.Vals().begin(), e = enum_def.Vals().end(); + it != e; ++it) { + auto f = (*it)->GetAsUInt64(); + if (f & u64) { + mask |= f; + text += (*it)->name; + text += ' '; + } + } + // Don't slice if (u64 != mask) + if (mask && (u64 == mask)) { + text[text.length() - 1] = '\"'; + return true; // done + } + text.resize(entry_len); // restore + } + // print as numeric value + } + text += NumToString(val); + return true; } - return true; -} - -// Print a vector or an array of JSON values, comma seperated, wrapped in "[]". -template -bool PrintContainer(const Container &c, size_t size, Type type, int indent, - const uint8_t *prev_val, const IDLOptions &opts, - std::string *_text) { - std::string &text = *_text; - text += "["; - text += NewLine(opts); - for (uoffset_t i = 0; i < size; i++) { - if (i) { - if (!opts.protobuf_ascii_alike) text += ","; - text += NewLine(opts); - } - text.append(indent + Indent(opts), ' '); - if (IsStruct(type)) { - if (!Print(reinterpret_cast(c.Data() + - i * type.struct_def->bytesize), - type, indent + Indent(opts), nullptr, -1, opts, _text)) { - return false; - } - } else { - if (!Print(c[i], type, indent + Indent(opts), prev_val, - static_cast(i), opts, _text)) { - return false; - } - } + void AddComma() { + if (!opts.protobuf_ascii_alike) text += ','; } - text += NewLine(opts); - text.append(indent, ' '); - text += "]"; - return true; -} -template -bool PrintVector(const Vector &v, Type type, int indent, - const uint8_t *prev_val, const IDLOptions &opts, - std::string *_text) { - return PrintContainer>(v, v.size(), type, indent, prev_val, opts, - _text); -} - -// Print an array a sequence of JSON values, comma separated, wrapped in "[]". -template -bool PrintArray(const Array &a, size_t size, Type type, int indent, - const IDLOptions &opts, std::string *_text) { - return PrintContainer>(a, size, type, indent, nullptr, - opts, _text); -} - -// Specialization of Print above for pointer types. -template<> -bool Print(const void *val, Type type, int indent, - const uint8_t *prev_val, soffset_t vector_index, - const IDLOptions &opts, std::string *_text) { - switch (type.base_type) { - case BASE_TYPE_UNION: { - // If this assert hits, you have an corrupt buffer, a union type field - // was not present or was out of range. - FLATBUFFERS_ASSERT(prev_val); - auto union_type_byte = *prev_val; // Always a uint8_t. - if (vector_index >= 0) { - auto type_vec = reinterpret_cast *>( - prev_val + ReadScalar(prev_val)); - union_type_byte = type_vec->Get(static_cast(vector_index)); + // Print a vector or an array of JSON values, comma seperated, wrapped in + // "[]". + template + bool PrintContainer(PrintScalarTag, const Container &c, size_t size, + const Type &type, int indent, const uint8_t *) { + const auto elem_indent = indent + Indent(); + text += '['; + AddNewLine(); + for (uoffset_t i = 0; i < size; i++) { + if (i) { + AddComma(); + AddNewLine(); } - auto enum_val = type.enum_def->ReverseLookup(union_type_byte, true); - if (enum_val) { - return Print(val, enum_val->union_type, indent, nullptr, - -1, opts, _text); - } else { + AddIndent(elem_indent); + if (!PrintScalar(c[i], type, elem_indent)) { return false; } + } + AddNewLine(); + AddIndent(indent); + text += ']'; + return true; + } + + // Print a vector or an array of JSON values, comma seperated, wrapped in + // "[]". + template + bool PrintContainer(PrintPointerTag, const Container &c, size_t size, + const Type &type, int indent, const uint8_t *prev_val) { + const auto is_struct = IsStruct(type); + const auto elem_indent = indent + Indent(); + text += '['; + AddNewLine(); + for (uoffset_t i = 0; i < size; i++) { + if (i) { + AddComma(); + AddNewLine(); + } + AddIndent(elem_indent); + auto ptr = is_struct ? reinterpret_cast( + c.Data() + type.struct_def->bytesize * i) + : c[i]; + if (!PrintOffset(ptr, type, elem_indent, prev_val, + static_cast(i))) { return false; } } - case BASE_TYPE_STRUCT: - return GenStruct(*type.struct_def, reinterpret_cast(val), - indent, opts, _text); - case BASE_TYPE_STRING: { - auto s = reinterpret_cast(val); - return EscapeString(s->c_str(), s->size(), _text, opts.allow_non_utf8, - opts.natural_utf8); - } - case BASE_TYPE_VECTOR: { - const auto vec_type = type.VectorType(); - // Call PrintVector above specifically for each element type: - // clang-format off - switch (vec_type.base_type) { + AddNewLine(); + AddIndent(indent); + text += ']'; + return true; + } + + template + bool PrintVector(const void *val, const Type &type, int indent, + const uint8_t *prev_val) { + typedef Vector Container; + typedef typename PrintTag::type tag; + auto &vec = *reinterpret_cast(val); + return PrintContainer(tag(), vec, vec.size(), type, indent, + prev_val); + } + + // Print an array a sequence of JSON values, comma separated, wrapped in "[]". + template + bool PrintArray(const void *val, size_t size, const Type &type, int indent) { + typedef Array Container; + typedef typename PrintTag::type tag; + auto &arr = *reinterpret_cast(val); + return PrintContainer(tag(), arr, size, type, indent, nullptr); + } + + bool PrintOffset(const void *val, const Type &type, int indent, + const uint8_t *prev_val, soffset_t vector_index) { + switch (type.base_type) { + case BASE_TYPE_UNION: { + // If this assert hits, you have an corrupt buffer, a union type field + // was not present or was out of range. + FLATBUFFERS_ASSERT(prev_val); + auto union_type_byte = *prev_val; // Always a uint8_t. + if (vector_index >= 0) { + auto type_vec = reinterpret_cast *>( + prev_val + ReadScalar(prev_val)); + union_type_byte = type_vec->Get(static_cast(vector_index)); + } + auto enum_val = type.enum_def->ReverseLookup(union_type_byte, true); + if (enum_val) { + return PrintOffset(val, enum_val->union_type, indent, nullptr, -1); + } else { + return false; + } + } + case BASE_TYPE_STRUCT: + return GenStruct(*type.struct_def, reinterpret_cast(val), + indent); + case BASE_TYPE_STRING: { + auto s = reinterpret_cast(val); + return EscapeString(s->c_str(), s->size(), &text, opts.allow_non_utf8, + opts.natural_utf8); + } + case BASE_TYPE_VECTOR: { + const auto vec_type = type.VectorType(); + // Call PrintVector above specifically for each element type: + // clang-format off + switch (vec_type.base_type) { #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \ case BASE_TYPE_ ## ENUM: \ if (!PrintVector( \ - *reinterpret_cast *>(val), \ - vec_type, indent, prev_val, opts, _text)) { \ + val, vec_type, indent, prev_val)) { \ return false; \ } \ break; FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD + } + // clang-format on + return true; } - // clang-format on - return true; - } - case BASE_TYPE_ARRAY: { - const auto vec_type = type.VectorType(); - // Call PrintArray above specifically for each element type: - // clang-format off - switch (vec_type.base_type) { - #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \ - case BASE_TYPE_ ## ENUM: \ - if (!PrintArray( \ - *reinterpret_cast *>(val), \ - type.fixed_length, \ - vec_type, indent, opts, _text)) { \ - return false; \ - } \ - break; - FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD) - // Arrays of scalars or structs are only possible. - FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD) - #undef FLATBUFFERS_TD - case BASE_TYPE_ARRAY: FLATBUFFERS_ASSERT(0); - } - // clang-format on - return true; - } - default: FLATBUFFERS_ASSERT(0); return false; - } -} - -template static T GetFieldDefault(const FieldDef &fd) { - T val; - auto check = StringToNumber(fd.value.constant.c_str(), &val); - (void)check; - FLATBUFFERS_ASSERT(check); - return val; -} - -// Generate text for a scalar field. -template -static bool GenField(const FieldDef &fd, const Table *table, bool fixed, - const IDLOptions &opts, int indent, std::string *_text) { - return Print( - fixed ? reinterpret_cast(table)->GetField( - fd.value.offset) - : table->GetField(fd.value.offset, GetFieldDefault(fd)), - fd.value.type, indent, nullptr, -1, opts, _text); -} - -static bool GenStruct(const StructDef &struct_def, const Table *table, - int indent, const IDLOptions &opts, std::string *_text); - -// Generate text for non-scalar field. -static bool GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed, - int indent, const uint8_t *prev_val, - const IDLOptions &opts, std::string *_text) { - const void *val = nullptr; - if (fixed) { - // The only non-scalar fields in structs are structs or arrays. - FLATBUFFERS_ASSERT(IsStruct(fd.value.type) || IsArray(fd.value.type)); - val = reinterpret_cast(table)->GetStruct( - fd.value.offset); - } else if (fd.flexbuffer) { - auto vec = table->GetPointer *>(fd.value.offset); - auto root = flexbuffers::GetRoot(vec->data(), vec->size()); - root.ToString(true, opts.strict_json, *_text); - return true; - } else if (fd.nested_flatbuffer) { - auto vec = table->GetPointer *>(fd.value.offset); - auto root = GetRoot(vec->data()); - return GenStruct(*fd.nested_flatbuffer, root, indent, opts, _text); - } else { - val = IsStruct(fd.value.type) - ? table->GetStruct(fd.value.offset) - : table->GetPointer(fd.value.offset); - } - return Print(val, fd.value.type, indent, prev_val, -1, opts, _text); -} - -// Generate text for a struct or table, values separated by commas, indented, -// and bracketed by "{}" -static bool GenStruct(const StructDef &struct_def, const Table *table, - int indent, const IDLOptions &opts, std::string *_text) { - std::string &text = *_text; - text += "{"; - int fieldout = 0; - const uint8_t *prev_val = nullptr; - for (auto it = struct_def.fields.vec.begin(); - it != struct_def.fields.vec.end(); ++it) { - FieldDef &fd = **it; - auto is_present = struct_def.fixed || table->CheckField(fd.value.offset); - auto output_anyway = opts.output_default_scalars_in_json && - IsScalar(fd.value.type.base_type) && !fd.deprecated; - if (is_present || output_anyway) { - if (fieldout++) { - if (!opts.protobuf_ascii_alike) text += ","; - } - text += NewLine(opts); - text.append(indent + Indent(opts), ' '); - OutputIdentifier(fd.name, opts, _text); - if (!opts.protobuf_ascii_alike || - (fd.value.type.base_type != BASE_TYPE_STRUCT && - fd.value.type.base_type != BASE_TYPE_VECTOR)) - text += ":"; - text += " "; - switch (fd.value.type.base_type) { + case BASE_TYPE_ARRAY: { + const auto vec_type = type.VectorType(); + // Call PrintArray above specifically for each element type: // clang-format off + switch (vec_type.base_type) { #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \ case BASE_TYPE_ ## ENUM: \ - if (!GenField(fd, table, struct_def.fixed, \ - opts, indent + Indent(opts), _text)) { \ + if (!PrintArray( \ + val, type.fixed_length, vec_type, indent)) { \ + return false; \ + } \ + break; + FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD) + // Arrays of scalars or structs are only possible. + FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD) + #undef FLATBUFFERS_TD + case BASE_TYPE_ARRAY: FLATBUFFERS_ASSERT(0); + } + // clang-format on + return true; + } + default: FLATBUFFERS_ASSERT(0); return false; + } + } + + template static T GetFieldDefault(const FieldDef &fd) { + T val; + auto check = StringToNumber(fd.value.constant.c_str(), &val); + (void)check; + FLATBUFFERS_ASSERT(check); + return val; + } + + // Generate text for a scalar field. + template + bool GenField(const FieldDef &fd, const Table *table, bool fixed, + int indent) { + return PrintScalar( + fixed ? reinterpret_cast(table)->GetField( + fd.value.offset) + : table->GetField(fd.value.offset, GetFieldDefault(fd)), + fd.value.type, indent); + } + + // Generate text for non-scalar field. + bool GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed, + int indent, const uint8_t *prev_val) { + const void *val = nullptr; + if (fixed) { + // The only non-scalar fields in structs are structs or arrays. + FLATBUFFERS_ASSERT(IsStruct(fd.value.type) || IsArray(fd.value.type)); + val = reinterpret_cast(table)->GetStruct( + fd.value.offset); + } else if (fd.flexbuffer) { + auto vec = table->GetPointer *>(fd.value.offset); + auto root = flexbuffers::GetRoot(vec->data(), vec->size()); + root.ToString(true, opts.strict_json, text); + return true; + } else if (fd.nested_flatbuffer) { + auto vec = table->GetPointer *>(fd.value.offset); + auto root = GetRoot
(vec->data()); + return GenStruct(*fd.nested_flatbuffer, root, indent); + } else { + val = IsStruct(fd.value.type) + ? table->GetStruct(fd.value.offset) + : table->GetPointer(fd.value.offset); + } + return PrintOffset(val, fd.value.type, indent, prev_val, -1); + } + + // Generate text for a struct or table, values separated by commas, indented, + // and bracketed by "{}" + bool GenStruct(const StructDef &struct_def, const Table *table, int indent) { + text += '{'; + int fieldout = 0; + const uint8_t *prev_val = nullptr; + const auto elem_indent = indent + Indent(); + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + FieldDef &fd = **it; + auto is_present = struct_def.fixed || table->CheckField(fd.value.offset); + auto output_anyway = opts.output_default_scalars_in_json && + IsScalar(fd.value.type.base_type) && !fd.deprecated; + if (is_present || output_anyway) { + if (fieldout++) { AddComma(); } + AddNewLine(); + AddIndent(elem_indent); + OutputIdentifier(fd.name); + if (!opts.protobuf_ascii_alike || + (fd.value.type.base_type != BASE_TYPE_STRUCT && + fd.value.type.base_type != BASE_TYPE_VECTOR)) + text += ':'; + text += ' '; + // clang-format off + switch (fd.value.type.base_type) { + #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \ + case BASE_TYPE_ ## ENUM: \ + if (!GenField(fd, table, struct_def.fixed, elem_indent)) { \ return false; \ } \ break; - FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD) + FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD) #undef FLATBUFFERS_TD // Generate drop-thru case statements for all pointer types: #define FLATBUFFERS_TD(ENUM, ...) \ case BASE_TYPE_ ## ENUM: - FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD) - FLATBUFFERS_GEN_TYPE_ARRAY(FLATBUFFERS_TD) + FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD) + FLATBUFFERS_GEN_TYPE_ARRAY(FLATBUFFERS_TD) #undef FLATBUFFERS_TD - if (!GenFieldOffset(fd, table, struct_def.fixed, indent + Indent(opts), - prev_val, opts, _text)) { - return false; - } + if (!GenFieldOffset(fd, table, struct_def.fixed, elem_indent, prev_val)) { + return false; + } break; + } // clang-format on - } - // Track prev val for use with union types. - if (struct_def.fixed) { - prev_val = reinterpret_cast(table) + fd.value.offset; - } else { - prev_val = table->GetAddressOf(fd.value.offset); + // Track prev val for use with union types. + if (struct_def.fixed) { + prev_val = reinterpret_cast(table) + fd.value.offset; + } else { + prev_val = table->GetAddressOf(fd.value.offset); + } } } + AddNewLine(); + AddIndent(indent); + text += '}'; + return true; } - text += NewLine(opts); - text.append(indent, ' '); - text += "}"; + + JsonPrinter(const Parser &parser, std::string &dest) + : opts(parser.opts), text(dest) { + text.reserve(1024); // Reduce amount of inevitable reallocs. + } + + const IDLOptions &opts; + std::string &text; +}; + +static bool GenerateTextImpl(const Parser &parser, const Table *table, + const StructDef &struct_def, std::string *_text) { + JsonPrinter printer(parser, *_text); + if (!printer.GenStruct(struct_def, table, 0)) { return false; } + printer.AddNewLine(); return true; } @@ -326,31 +362,21 @@ bool GenerateTextFromTable(const Parser &parser, const void *table, const std::string &table_name, std::string *_text) { auto struct_def = parser.LookupStruct(table_name); if (struct_def == nullptr) { return false; } - auto &text = *_text; - text.reserve(1024); // Reduce amount of inevitable reallocs. auto root = static_cast(table); - if (!GenStruct(*struct_def, root, 0, parser.opts, &text)) { return false; } - text += NewLine(parser.opts); - return true; + return GenerateTextImpl(parser, root, *struct_def, _text); } // Generate a text representation of a flatbuffer in JSON format. bool GenerateText(const Parser &parser, const void *flatbuffer, std::string *_text) { - std::string &text = *_text; FLATBUFFERS_ASSERT(parser.root_struct_def_); // call SetRootType() - text.reserve(1024); // Reduce amount of inevitable reallocs. auto root = parser.opts.size_prefixed ? GetSizePrefixedRoot
(flatbuffer) : GetRoot
(flatbuffer); - if (!GenStruct(*parser.root_struct_def_, root, 0, parser.opts, _text)) { - return false; - } - text += NewLine(parser.opts); - return true; + return GenerateTextImpl(parser, root, *parser.root_struct_def_, _text); } -std::string TextFileName(const std::string &path, - const std::string &file_name) { +static std::string TextFileName(const std::string &path, + const std::string &file_name) { return path + file_name + ".json"; } diff --git a/tests/test.cpp b/tests/test.cpp index c63502b83..2b5d760b5 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -650,6 +650,19 @@ void JsonEnumsTest() { auto result = GenerateText(parser, builder.GetBufferPointer(), &jsongen); TEST_EQ(result, true); TEST_EQ(std::string::npos != jsongen.find("color: \"Red Blue\""), true); + // Test forward compatibility with 'output_enum_identifiers = true'. + // Current Color doesn't have '(1u << 2)' field, let's add it. + builder.Clear(); + std::string future_json; + auto future_name = builder.CreateString("future bitflag_enum"); + MonsterBuilder future_color(builder); + future_color.add_name(future_name); + future_color.add_color( + static_cast((1u << 2) | Color_Blue | Color_Red)); + FinishMonsterBuffer(builder, future_color.Finish()); + result = GenerateText(parser, builder.GetBufferPointer(), &future_json); + TEST_EQ(result, true); + TEST_EQ(std::string::npos != future_json.find("color: 13"), true); } #if defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0)