diff --git a/CMakeLists.txt b/CMakeLists.txt index d3c6a7afb..53d453bb1 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 d40481245..4e27e30c4 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 cb8f217b0..02d01c032 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 000000000..e519415dd --- /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 2abfe9b3c..ca97b1b67 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 8e3ac5112..221fafb09 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 619025902..ea66514be 100644 Binary files a/tests/monster_test.bfbs and b/tests/monster_test.bfbs differ diff --git a/tests/monster_test.schema.json b/tests/monster_test.schema.json new file mode 100644 index 000000000..7c58f7066 --- /dev/null +++ b/tests/monster_test.schema.json @@ -0,0 +1,105 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "MyGame_OtherNameSpace_FromInclude" : { + "type" : "string", + "enum": ["IncludeVal"] + }, + "MyGame_Example_Color" : { + "type" : "string", + "enum": ["Red", "Green", "Blue"] + }, + "MyGame_Example_Any" : { + "type" : "string", + "enum": ["NONE", "Monster", "TestSimpleTableWithEnum", "MyGame_Example2_Monster"] + }, + "MyGame_OtherNameSpace_Unused" : { + "type" : "object", + "properties" : { + } + }, + "MyGame_Example2_Monster" : { + "type" : "object", + "properties" : { + } + }, + "MyGame_Example_Test" : { + "type" : "object", + "properties" : { + "a" : { "type" : "number" }, + "b" : { "type" : "number" } + } + }, + "MyGame_Example_TestSimpleTableWithEnum" : { + "type" : "object", + "properties" : { + "color" : { "$ref" : "#/definitions/MyGame_Example_Color" } + } + }, + "MyGame_Example_Vec3" : { + "type" : "object", + "properties" : { + "x" : { "type" : "number" }, + "y" : { "type" : "number" }, + "z" : { "type" : "number" }, + "test1" : { "type" : "number" }, + "test2" : { "$ref" : "#/definitions/MyGame_Example_Color" }, + "test3" : { "$ref" : "#/definitions/MyGame_Example_Test" } + } + }, + "MyGame_Example_Ability" : { + "type" : "object", + "properties" : { + "id" : { "type" : "number" }, + "distance" : { "type" : "number" } + } + }, + "MyGame_Example_Stat" : { + "type" : "object", + "properties" : { + "id" : { "type" : "string" }, + "val" : { "type" : "number" }, + "count" : { "type" : "number" } + } + }, + "MyGame_Example_Monster" : { + "type" : "object", + "description" : " an example documentation comment: monster object", + "properties" : { + "pos" : { "$ref" : "#/definitions/MyGame_Example_Vec3" }, + "mana" : { "type" : "number" }, + "hp" : { "type" : "number" }, + "name" : { "type" : "string" }, + "friendly" : { "type" : "boolean" }, + "inventory" : { "type" : "array", "items" : { "type" : "number" } }, + "color" : { "$ref" : "#/definitions/MyGame_Example_Color" }, + "test_type" : { "$ref" : "#/definitions/MyGame_Example_Any" }, + "test" : { "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" },{ "$ref" : "#/definitions/MyGame_Example2_Monster" }] }, + "test4" : { "type" : "array", "items" : { "$ref" : "#/definitions/MyGame_Example_Test" } }, + "testarrayofstring" : { "type" : "array", "items" : { "type" : "string" } }, + "testarrayoftables" : { "type" : "array", "items" : { "$ref" : "#/definitions/MyGame_Example_Monster" } }, + "enemy" : { "$ref" : "#/definitions/MyGame_Example_Monster" }, + "testnestedflatbuffer" : { "type" : "array", "items" : { "type" : "number" } }, + "testempty" : { "$ref" : "#/definitions/MyGame_Example_Stat" }, + "testbool" : { "type" : "boolean" }, + "testhashs32_fnv1" : { "type" : "number" }, + "testhashu32_fnv1" : { "type" : "number" }, + "testhashs64_fnv1" : { "type" : "number" }, + "testhashu64_fnv1" : { "type" : "number" }, + "testhashs32_fnv1a" : { "type" : "number" }, + "testhashu32_fnv1a" : { "type" : "number" }, + "testhashs64_fnv1a" : { "type" : "number" }, + "testhashu64_fnv1a" : { "type" : "number" }, + "testarrayofbools" : { "type" : "array", "items" : { "type" : "boolean" } }, + "testf" : { "type" : "number" }, + "testf2" : { "type" : "number" }, + "testf3" : { "type" : "number" }, + "testarrayofstring2" : { "type" : "array", "items" : { "type" : "string" } }, + "testarrayofsortedstruct" : { "type" : "array", "items" : { "$ref" : "#/definitions/MyGame_Example_Ability" } }, + "flex" : { "type" : "array", "items" : { "type" : "number" } } + }, + "required" : [ "name"] + } + }, + "$ref" : "#/definitions/MyGame_Example_Monster" +}