From 7663dabb344c9e047558394c6d336c3c58650f0b Mon Sep 17 00:00:00 2001 From: William Hammond Date: Sun, 20 Jul 2025 11:06:40 -0700 Subject: [PATCH] [JSON Schema] Generate conditionals to associate union data with their _type --- src/idl_gen_json_schema.cpp | 131 +++++++++++++++++++++++--- tests/monster_test.schema.json | 167 ++++++++++++++++++++++++++++++--- 2 files changed, 272 insertions(+), 26 deletions(-) diff --git a/src/idl_gen_json_schema.cpp b/src/idl_gen_json_schema.cpp index ed891ab2e26..5c7e57b4a9d 100644 --- a/src/idl_gen_json_schema.cpp +++ b/src/idl_gen_json_schema.cpp @@ -119,23 +119,42 @@ static std::string GenType(const Type &type) { return GenTypeRef(type.struct_def); } case BASE_TYPE_UNION: { - std::string union_type_string("\"anyOf\": ["); + std::string union_type_string("\"oneOf\": ["); const auto &union_types = type.enum_def->Vals(); + bool first = true; + for (auto ut = union_types.cbegin(); ut < union_types.cend(); ++ut) { const 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) { + if (!first) union_type_string.append(","); + first = false; + union_type_string.append( - "{ " + GenTypeRef(union_type->union_type.struct_def) + " }"); - } - if (union_type != *type.enum_def->Vals().rbegin()) { - union_type_string.append(","); + "{ \"type\": \"object\", \"properties\": { "); + union_type_string.append("\"type\": { \"const\": \"" + + union_type->name + "\" }, "); + union_type_string.append( + "\"value\": { " + GenTypeRef(union_type->union_type.struct_def) + + " }"); + union_type_string.append(" }, \"required\": [\"type\", \"value\"] }"); } } union_type_string.append("]"); return union_type_string; } - case BASE_TYPE_UTYPE: return GenTypeRef(type.enum_def); + case BASE_TYPE_UTYPE: { + std::string enumdef = GenType("string") + ", \"enum\": ["; + for (auto enum_value = type.enum_def->Vals().begin(); + enum_value != type.enum_def->Vals().end(); ++enum_value) { + enumdef.append("\"" + (*enum_value)->name + "\""); + if (*enum_value != type.enum_def->Vals().back()) { + enumdef.append(", "); + } + } + enumdef.append("]"); + return enumdef; + } default: { return GenBaseType(type); } @@ -221,15 +240,34 @@ class JsonSchemaGenerator : public BaseGenerator { for (auto e = parser_.enums_.vec.cbegin(); e != parser_.enums_.vec.cend(); ++e) { code_ += Indent(2) + "\"" + GenFullName(*e) + "\" : {" + NewLine(); - code_ += Indent(3) + GenType("string") + "," + NewLine(); - auto enumdef(Indent(3) + "\"enum\": ["); - for (auto enum_value = (*e)->Vals().begin(); - enum_value != (*e)->Vals().end(); ++enum_value) { - enumdef.append("\"" + (*enum_value)->name + "\""); - if (*enum_value != (*e)->Vals().back()) { enumdef.append(", "); } + + if ((*e)->is_union) { + std::string union_type_string("\"anyOf\": ["); + const auto &union_types = (*e)->Vals(); + for (auto ut = union_types.cbegin(); ut < union_types.cend(); ++ut) { + const 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 != *union_types.rbegin()) { + union_type_string.append(","); + } + } + union_type_string.append("]"); + code_ += Indent(3) + union_type_string + NewLine(); + } else { + code_ += Indent(3) + GenType("string") + "," + NewLine(); + auto enumdef(Indent(3) + "\"enum\": ["); + for (auto enum_value = (*e)->Vals().begin(); + enum_value != (*e)->Vals().end(); ++enum_value) { + enumdef.append("\"" + (*enum_value)->name + "\""); + if (*enum_value != (*e)->Vals().back()) { enumdef.append(", "); } + } + enumdef.append("]"); + code_ += enumdef + NewLine(); } - enumdef.append("]"); - code_ += enumdef + NewLine(); code_ += Indent(2) + "}," + NewLine(); // close type } for (auto s = parser_.structs_.vec.cbegin(); @@ -276,6 +314,71 @@ class JsonSchemaGenerator : public BaseGenerator { code_ += typeLine + NewLine(); } code_ += Indent(3) + "}," + NewLine(); // close properties + std::vector union_conditions; + + for (auto prop = properties.cbegin(); prop != properties.cend(); ++prop) { + const auto &property = *prop; + + // Add conditionals to point the correct underlying data to the _type + if (property->value.type.base_type == BASE_TYPE_UNION) { + const auto &union_types = property->value.type.enum_def->Vals(); + const std::string type_field_name = property->name + "_type"; + + for (auto ut = union_types.cbegin(); ut < union_types.cend(); ++ut) { + const auto &union_type = *ut; + if (union_type->union_type.base_type == BASE_TYPE_NONE) { + std::string none_condition; + none_condition += Indent(4) + "{" + NewLine(); + none_condition += Indent(5) + "\"if\": {" + NewLine(); + none_condition += Indent(6) + "\"properties\": {" + NewLine(); + none_condition += Indent(7) + "\"" + type_field_name + + "\": { \"const\": \"" + union_type->name + + "\" }" + NewLine(); + none_condition += Indent(6) + "}" + NewLine(); + none_condition += Indent(5) + "}," + NewLine(); + none_condition += Indent(5) + "\"then\": {" + NewLine(); + none_condition += Indent(6) + "\"not\": {" + NewLine(); + none_condition += Indent(7) + "\"required\": [\"" + + property->name + "\"]" + NewLine(); + none_condition += Indent(6) + "}" + NewLine(); + none_condition += Indent(5) + "}" + NewLine(); + none_condition += Indent(4) + "}"; + + union_conditions.push_back(none_condition); + } + if (union_type->union_type.base_type == BASE_TYPE_STRUCT) { + std::string condition; + condition += Indent(4) + "{" + NewLine(); + condition += Indent(5) + "\"if\": {" + NewLine(); + condition += Indent(6) + "\"properties\": {" + NewLine(); + condition += Indent(7) + "\"" + type_field_name + + "\": { \"const\": \"" + union_type->name + "\" }" + + NewLine(); + condition += Indent(6) + "}" + NewLine(); + condition += Indent(5) + "}," + NewLine(); + condition += Indent(5) + "\"then\": {" + NewLine(); + condition += Indent(6) + "\"properties\": {" + NewLine(); + condition += Indent(7) + "\"" + property->name + "\": { " + + GenTypeRef(union_type->union_type.struct_def) + + " }" + NewLine(); + condition += Indent(6) + "}" + NewLine(); + condition += Indent(5) + "}" + NewLine(); + condition += Indent(4) + "}"; + + union_conditions.push_back(condition); + } + } + } + } + if (!union_conditions.empty()) { + code_ += Indent(3) + "\"allOf\": [" + NewLine(); + for (size_t i = 0; i < union_conditions.size(); ++i) { + code_ += union_conditions[i]; + if (i < union_conditions.size() - 1) { code_ += ","; } + code_ += NewLine(); + } + code_ += Indent(3) + "]," + NewLine(); + } std::vector requiredProperties; std::copy_if(properties.begin(), properties.end(), diff --git a/tests/monster_test.schema.json b/tests/monster_test.schema.json index edcfbd9e249..9d6a7265b0d 100644 --- a/tests/monster_test.schema.json +++ b/tests/monster_test.schema.json @@ -18,16 +18,13 @@ "enum": ["LongOne", "LongTwo", "LongBig"] }, "MyGame_Example_Any" : { - "type" : "string", - "enum": ["NONE", "Monster", "TestSimpleTableWithEnum", "MyGame_Example2_Monster"] + "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" },{ "$ref" : "#/definitions/MyGame_Example2_Monster" }] }, "MyGame_Example_AnyUniqueAliases" : { - "type" : "string", - "enum": ["NONE", "M", "TS", "M2"] + "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" },{ "$ref" : "#/definitions/MyGame_Example2_Monster" }] }, "MyGame_Example_AnyAmbiguousAliases" : { - "type" : "string", - "enum": ["NONE", "M1", "M2", "M3"] + "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_Monster" }] }, "MyGame_OtherNameSpace_Unused" : { "type" : "object", @@ -200,10 +197,10 @@ "$ref" : "#/definitions/MyGame_Example_Color" }, "test_type" : { - "$ref" : "#/definitions/MyGame_Example_Any" + "type" : "string", "enum": ["NONE", "Monster", "TestSimpleTableWithEnum", "MyGame_Example2_Monster"] }, "test" : { - "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" },{ "$ref" : "#/definitions/MyGame_Example2_Monster" }] + "oneOf": [{ "type": "object", "properties": { "type": { "const": "Monster" }, "value": { "$ref" : "#/definitions/MyGame_Example_Monster" } }, "required": ["type", "value"] },{ "type": "object", "properties": { "type": { "const": "TestSimpleTableWithEnum" }, "value": { "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" } }, "required": ["type", "value"] },{ "type": "object", "properties": { "type": { "const": "MyGame_Example2_Monster" }, "value": { "$ref" : "#/definitions/MyGame_Example2_Monster" } }, "required": ["type", "value"] }] }, "test4" : { "type" : "array", "items" : {"$ref" : "#/definitions/MyGame_Example_Test"} @@ -309,16 +306,16 @@ "type" : "array", "items" : {"type" : "integer", "minimum" : 0, "maximum" : 18446744073709551615} }, "any_unique_type" : { - "$ref" : "#/definitions/MyGame_Example_AnyUniqueAliases" + "type" : "string", "enum": ["NONE", "M", "TS", "M2"] }, "any_unique" : { - "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" },{ "$ref" : "#/definitions/MyGame_Example2_Monster" }] + "oneOf": [{ "type": "object", "properties": { "type": { "const": "M" }, "value": { "$ref" : "#/definitions/MyGame_Example_Monster" } }, "required": ["type", "value"] },{ "type": "object", "properties": { "type": { "const": "TS" }, "value": { "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" } }, "required": ["type", "value"] },{ "type": "object", "properties": { "type": { "const": "M2" }, "value": { "$ref" : "#/definitions/MyGame_Example2_Monster" } }, "required": ["type", "value"] }] }, "any_ambiguous_type" : { - "$ref" : "#/definitions/MyGame_Example_AnyAmbiguousAliases" + "type" : "string", "enum": ["NONE", "M1", "M2", "M3"] }, "any_ambiguous" : { - "anyOf": [{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_Monster" },{ "$ref" : "#/definitions/MyGame_Example_Monster" }] + "oneOf": [{ "type": "object", "properties": { "type": { "const": "M1" }, "value": { "$ref" : "#/definitions/MyGame_Example_Monster" } }, "required": ["type", "value"] },{ "type": "object", "properties": { "type": { "const": "M2" }, "value": { "$ref" : "#/definitions/MyGame_Example_Monster" } }, "required": ["type", "value"] },{ "type": "object", "properties": { "type": { "const": "M3" }, "value": { "$ref" : "#/definitions/MyGame_Example_Monster" } }, "required": ["type", "value"] }] }, "vector_of_enums" : { "type" : "array", "items" : {"$ref" : "#/definitions/MyGame_Example_Color"} @@ -366,6 +363,152 @@ "type" : "number" } }, + "allOf": [ + { + "if": { + "properties": { + "test_type": { "const": "NONE" } + } + }, + "then": { + "not": { + "required": ["test"] + } + } + }, + { + "if": { + "properties": { + "test_type": { "const": "Monster" } + } + }, + "then": { + "properties": { + "test": { "$ref" : "#/definitions/MyGame_Example_Monster" } + } + } + }, + { + "if": { + "properties": { + "test_type": { "const": "TestSimpleTableWithEnum" } + } + }, + "then": { + "properties": { + "test": { "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" } + } + } + }, + { + "if": { + "properties": { + "test_type": { "const": "MyGame_Example2_Monster" } + } + }, + "then": { + "properties": { + "test": { "$ref" : "#/definitions/MyGame_Example2_Monster" } + } + } + }, + { + "if": { + "properties": { + "any_unique_type": { "const": "NONE" } + } + }, + "then": { + "not": { + "required": ["any_unique"] + } + } + }, + { + "if": { + "properties": { + "any_unique_type": { "const": "M" } + } + }, + "then": { + "properties": { + "any_unique": { "$ref" : "#/definitions/MyGame_Example_Monster" } + } + } + }, + { + "if": { + "properties": { + "any_unique_type": { "const": "TS" } + } + }, + "then": { + "properties": { + "any_unique": { "$ref" : "#/definitions/MyGame_Example_TestSimpleTableWithEnum" } + } + } + }, + { + "if": { + "properties": { + "any_unique_type": { "const": "M2" } + } + }, + "then": { + "properties": { + "any_unique": { "$ref" : "#/definitions/MyGame_Example2_Monster" } + } + } + }, + { + "if": { + "properties": { + "any_ambiguous_type": { "const": "NONE" } + } + }, + "then": { + "not": { + "required": ["any_ambiguous"] + } + } + }, + { + "if": { + "properties": { + "any_ambiguous_type": { "const": "M1" } + } + }, + "then": { + "properties": { + "any_ambiguous": { "$ref" : "#/definitions/MyGame_Example_Monster" } + } + } + }, + { + "if": { + "properties": { + "any_ambiguous_type": { "const": "M2" } + } + }, + "then": { + "properties": { + "any_ambiguous": { "$ref" : "#/definitions/MyGame_Example_Monster" } + } + } + }, + { + "if": { + "properties": { + "any_ambiguous_type": { "const": "M3" } + } + }, + "then": { + "properties": { + "any_ambiguous": { "$ref" : "#/definitions/MyGame_Example_Monster" } + } + } + } + ], "required" : ["name"], "additionalProperties" : false },