From f20204180dbf58a53dcaf7929f0f996930d40bf3 Mon Sep 17 00:00:00 2001 From: schoetbi Date: Mon, 10 Jul 2017 17:05:59 +0200 Subject: [PATCH] Json schema (#4369) * Added empty generator for json schema (idl_gen_json_schema.cpp) #4360 * JsonSchemaGenerator: output of tables implemented current problems: - typenames are not correct - array types need to be deduced #4360 * JsonSchemaGenerator: Corrected generation of typenames Current problems: enum types not written correctly #4360 * JsonSchemaGenerator: Added generation of enum types #4360 * idl_gen_json_schema.cpp: Write required properties to schema #4360 * idl_gen_json_schema.cpp: Export Types including namespace #4360 * idl_gen_json_schema.cpp: Fixed Json format #4360 * idl_gen_json_schema.cpp: Formatted according to google code style #4360 * Checked in monster_test.bfbs with changes from master * Added idl_gen_json_schema.cpp in CMakeLists.txt * generate_code.bat: Added generation of json schema * Added todo.md * generate_code.sh: Added generation of json schema * Addressed some review issues - removed command line arg -S - removed new lines - fixed codestyle in template functions - removed usage of stringstream - idented json schema #4360 * removed auto in idl_gen_json_schema.cpp * idl_gen_json_schema.cpp: changed iterator declarations to auto #4360 * deleted todo.md * idl_gen_json_schema.cpp: Removed keyword "override" so that vs2010 can compile * idl_gen_json_schema.cpp: switch statement in GenType handeles all enum-members * idl_gen_json_schema.cpp: Removed cerr output * idl_gen_json_schema.cpp: Avoid vector copying * idl_gen_json_schema.cpp: Fixed identation of json schema output * idl_gen_json_schema.cpp: Do not output empty descriptions --- CMakeLists.txt | 1 + include/flatbuffers/idl.h | 7 ++ src/flatc_main.cpp | 7 +- src/idl_gen_json_schema.cpp | 224 +++++++++++++++++++++++++++++++++ tests/generate_code.bat | 1 + tests/generate_code.sh | 1 + tests/monster_test.bfbs | Bin 4336 -> 3736 bytes tests/monster_test.schema.json | 105 ++++++++++++++++ 8 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 src/idl_gen_json_schema.cpp create mode 100644 tests/monster_test.schema.json diff --git a/CMakeLists.txt b/CMakeLists.txt index d3c6a7afba6..53d453bb164 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ set(FlatBuffers_Compiler_SRCS src/idl_gen_python.cpp src/idl_gen_fbs.cpp src/idl_gen_grpc.cpp + src/idl_gen_json_schema.cpp src/flatc.cpp src/flatc_main.cpp grpc/src/compiler/schema_interface.h diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index d40481245a7..4e27e30c40f 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -380,6 +380,7 @@ struct IDLOptions { kJson = 1 << 7, kBinary = 1 << 8, kTs = 1 << 9, + kJsonSchema = 1 << 10, kMAX }; @@ -716,6 +717,12 @@ extern bool GeneratePython(const Parser &parser, const std::string &path, const std::string &file_name); +// Generate Json schema file +// See idl_gen_json_schema.cpp. +extern bool GenerateJsonSchema(const Parser &parser, + const std::string &path, + const std::string &file_name); + // Generate C# files from the definitions in the Parser object. // See idl_gen_csharp.cpp. extern bool GenerateCSharp(const Parser &parser, diff --git a/src/flatc_main.cpp b/src/flatc_main.cpp index cb8f217b068..02d01c032cd 100644 --- a/src/flatc_main.cpp +++ b/src/flatc_main.cpp @@ -96,7 +96,12 @@ int main(int argc, const char *argv[]) { flatbuffers::IDLOptions::kPhp, "Generate PHP files for tables/structs", flatbuffers::GeneralMakeRule }, - }; + { flatbuffers::GenerateJsonSchema, nullptr, "--jsonschema", "JsonSchema", true, + nullptr, + flatbuffers::IDLOptions::kJsonSchema, + "Generate Json schema", + flatbuffers::GeneralMakeRule }, + }; flatbuffers::FlatCompiler::InitParams params; params.generators = generators; diff --git a/src/idl_gen_json_schema.cpp b/src/idl_gen_json_schema.cpp new file mode 100644 index 00000000000..e519415dd6b --- /dev/null +++ b/src/idl_gen_json_schema.cpp @@ -0,0 +1,224 @@ +/* +* Copyright 2014 Google Inc. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "flatbuffers/code_generators.h" +#include "flatbuffers/idl.h" +#include "flatbuffers/util.h" +#include + +namespace flatbuffers { + +static std::string GeneratedFileName(const std::string &path, + const std::string &file_name) { + return path + file_name + ".schema.json"; +} + +namespace jsons { + +std::string GenNativeType(BaseType type) { + switch (type) { + case BASE_TYPE_BOOL: + return "boolean"; + case BASE_TYPE_CHAR: + case BASE_TYPE_UCHAR: + case BASE_TYPE_SHORT: + case BASE_TYPE_USHORT: + case BASE_TYPE_INT: + case BASE_TYPE_UINT: + case BASE_TYPE_LONG: + case BASE_TYPE_ULONG: + case BASE_TYPE_FLOAT: + case BASE_TYPE_DOUBLE: + return "number"; + case BASE_TYPE_STRING: + return "string"; + default: + return ""; + } +} + +template std::string GenFullName(const T *enum_def) { + std::string full_name; + const auto &name_spaces = enum_def->defined_namespace->components; + for (auto ns = name_spaces.cbegin(); ns != name_spaces.cend(); ++ns) { + full_name.append(*ns + "_"); + } + full_name.append(enum_def->name); + return full_name; +} + +template std::string GenTypeRef(const T *enum_def) { + return "\"$ref\" : \"#/definitions/" + GenFullName(enum_def) + "\""; +} + +std::string GenType(const std::string &name) { + return "\"type\" : \"" + name + "\""; +} + +std::string GenType(const Type &type) { + if (type.base_type == BASE_TYPE_CHAR && type.enum_def != nullptr) { + // it is a reference to an enum type + return GenTypeRef(type.enum_def); + } + switch (type.base_type) { + case BASE_TYPE_VECTOR: { + std::string typeline; + typeline.append("\"type\" : \"array\", \"items\" : { "); + if (type.element == BASE_TYPE_STRUCT) { + typeline.append(GenTypeRef(type.struct_def)); + } else { + typeline.append(GenType(GenNativeType(type.element))); + } + typeline.append(" }"); + return typeline; + } + case BASE_TYPE_STRUCT: { + return GenTypeRef(type.struct_def); + } + case BASE_TYPE_UNION: { + std::string union_type_string("\"anyOf\": ["); + const auto &union_types = type.enum_def->vals.vec; + for (auto ut = union_types.cbegin(); ut < union_types.cend(); ++ut) { + auto &union_type = *ut; + if (union_type->union_type.base_type == BASE_TYPE_NONE) { + continue; + } + if (union_type->union_type.base_type == BASE_TYPE_STRUCT) { + union_type_string.append("{ " + GenTypeRef(union_type->union_type.struct_def) + " }"); + } + if (union_type != *type.enum_def->vals.vec.rbegin()) { + union_type_string.append(","); + } + } + union_type_string.append("]"); + return union_type_string; + } + case BASE_TYPE_UTYPE: + return GenTypeRef(type.enum_def); + default: + return GenType(GenNativeType(type.base_type)); + } +} + +class JsonSchemaGenerator : public BaseGenerator { + private: + CodeWriter code_; + + public: + JsonSchemaGenerator(const Parser &parser, const std::string &path, + const std::string &file_name) + : BaseGenerator(parser, path, file_name, "", "") {} + + explicit JsonSchemaGenerator(const BaseGenerator &base_generator) + : BaseGenerator(base_generator) {} + + bool generate() { + code_.Clear(); + code_ += "{"; + code_ += " \"$schema\": \"http://json-schema.org/draft-04/schema#\","; + code_ += " \"definitions\": {"; + for (auto e = parser_.enums_.vec.cbegin(); + e != parser_.enums_.vec.cend(); + ++e) { + code_ += " \"" + GenFullName(*e) + "\" : {"; + code_ += " " + GenType("string") + ","; + std::string enumdef(" \"enum\": ["); + for (auto enum_value = (*e)->vals.vec.begin(); + enum_value != (*e)->vals.vec.end(); + ++enum_value) { + enumdef.append("\"" + (*enum_value)->name + "\""); + if (*enum_value != (*e)->vals.vec.back()) { + enumdef.append(", "); + } + } + enumdef.append("]"); + code_ += enumdef; + code_ += " },"; // close type + } + for (auto s = parser_.structs_.vec.cbegin(); + s != parser_.structs_.vec.cend(); + ++s) { + const auto &structure = *s; + code_ += " \"" + GenFullName(structure) + "\" : {"; + code_ += " " + GenType("object") + ","; + std::string comment; + const auto &comment_lines = structure->doc_comment; + for (auto comment_line = comment_lines.cbegin(); + comment_line != comment_lines.cend(); + ++comment_line) { + comment.append(*comment_line); + } + if (comment.size() > 0) { + code_ += " \"description\" : \"" + comment + "\","; + } + code_ += " \"properties\" : {"; + + const auto &properties = structure->fields.vec; + for (auto prop = properties.cbegin(); prop != properties.cend(); ++prop) { + const auto &property = *prop; + std::string typeLine(" \"" + property->name + "\" : { " + GenType(property->value.type) + " }"); + if (property != properties.back()) { + typeLine.append(","); + } + code_ += typeLine; + } + std::vector requiredProperties; + std::copy_if(properties.begin(), properties.end(), + back_inserter(requiredProperties), + [](FieldDef const *prop) { return prop->required; }); + if (requiredProperties.size() > 0) { + code_ += " },"; // close properties + std::string required_string(" \"required\" : [ "); + for (auto req_prop = requiredProperties.cbegin(); + req_prop != requiredProperties.cend(); + ++req_prop) { + required_string.append("\"" + (*req_prop)->name + "\""); + if (*req_prop != requiredProperties.back()) { + required_string.append(", "); + } + } + required_string.append("]"); + code_ += required_string; + } else { + code_ += " }"; // close properties + } + + std::string closeType(" }"); + if (*s != parser_.structs_.vec.back()) { + closeType.append(","); + } + code_ += closeType; // close type + } + code_ += " },"; // close definitions + + // mark root type + code_ += " \"$ref\" : \"#/definitions/" + + GenFullName(parser_.root_struct_def_) + "\""; + + code_ += "}"; // close schema root + const std::string file_path = GeneratedFileName(path_, file_name_); + const std::string final_code = code_.ToString(); + return SaveFile(file_path.c_str(), final_code, false); + } +}; +} // namespace jsons + +bool GenerateJsonSchema(const Parser &parser, const std::string &path, + const std::string &file_name) { + jsons::JsonSchemaGenerator generator(parser, path, file_name); + return generator.generate(); +} +} // namespace flatbuffers diff --git a/tests/generate_code.bat b/tests/generate_code.bat index 2abfe9b3c74..ca97b1b6756 100644 --- a/tests/generate_code.bat +++ b/tests/generate_code.bat @@ -18,3 +18,4 @@ if "%1"=="-b" set buildtype=%2 ..\%buildtype%\flatc.exe --cpp --java --csharp --go --binary --python --js --php --grpc --gen-mutable --gen-object-api --no-includes -I include_test monster_test.fbs monsterdata_test.json ..\%buildtype%\flatc.exe --cpp --java --csharp --go --binary --python --js --php --gen-mutable -o namespace_test namespace_test\namespace_test1.fbs namespace_test\namespace_test2.fbs ..\%buildtype%\flatc.exe --binary --schema -I include_test monster_test.fbs +..\%buildtype%\flatc.exe --jsonschema --schema -I include_test monster_test.fbs diff --git a/tests/generate_code.sh b/tests/generate_code.sh index 8e3ac5112ce..221fafb09fd 100755 --- a/tests/generate_code.sh +++ b/tests/generate_code.sh @@ -18,6 +18,7 @@ ../flatc --cpp --java --csharp --go --binary --python --js --ts --php --gen-mutable --no-fb-import -o namespace_test namespace_test/namespace_test1.fbs namespace_test/namespace_test2.fbs ../flatc --cpp --gen-mutable --gen-object-api -o union_vector ./union_vector/union_vector.fbs ../flatc -b --schema --bfbs-comments -I include_test monster_test.fbs +../flatc --jsonschema --schema -I include_test monster_test.fbs cd ../samples ../flatc --cpp --gen-mutable --gen-object-api monster.fbs cd ../reflection diff --git a/tests/monster_test.bfbs b/tests/monster_test.bfbs index 619025902f577441e5df0870cbba38cb55ee3ae0..ea66514bed9a621beb538e7ab06bd15cedffbba7 100644 GIT binary patch literal 3736 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 ge+QUR