Repository: linkml/linkml

Issue:
jsonschema generation should generate additionalProperties: false for enclosing properties
we correctly generate

         "additionalProperties": false,

for each class

but we should also do this on the enclosing object


Discussion:

Patch:
diff --git a/AUTHORS b/AUTHORS
index 6d3bd0c7..aa67ab24 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,4 +1,4 @@
-Bill Duncan <wdduncan@users.noreply.github.com>
+Bill Duncan <wdduncan@gmail.com>
 Chris Mungall <cjm@berkeleybop.org>
 Donny Winston <donny@polyneme.xyz>
 EC2 Default User <ec2-user@ip-172-31-29-45.ec2.internal>
diff --git a/Pipfile b/Pipfile
index bb46927a..c28b5df5 100644
--- a/Pipfile
+++ b/Pipfile
@@ -32,6 +32,7 @@ sqlalchemy = "*"
 nbconvert = "*"
 nbformat = "*"
 ipykernel = "*"
+chardet = "*"
 
 [pipenv]
 allow_prereleases = true
diff --git a/linkml/generators/jsonschemagen.py b/linkml/generators/jsonschemagen.py
index b0396eda..7f8e502d 100644
--- a/linkml/generators/jsonschemagen.py
+++ b/linkml/generators/jsonschemagen.py
@@ -42,12 +42,13 @@ class JsonSchemaGenerator(Generator):
         # so we duplicate slots from inherited parents and mixins
         self.visit_all_slots = True
 
-    def visit_schema(self, inline: bool = False, **kwargs) -> None:
+    def visit_schema(self, inline: bool = False, not_closed=True, **kwargs) -> None:    
         self.inline = inline
         self.schemaobj = JsonObj(title=self.schema.name,
                                  type="object",
                                  properties={},
-                                 definitions=JsonObj())
+                                 definitions=JsonObj(),
+                                 additionalProperties=not_closed)
         for p, c in self.entryProperties.items():
             self.schemaobj['properties'][p] = {
                 'type': "array",
@@ -122,6 +123,9 @@ Note that declaring a slot as inlined: true will always inline the class
 @click.option("-t", "--top-class", help="""
 Top level class; slots of this class will become top level properties in the json-schema
 """)
+@click.option("--not-closed/--closed", default=True, help="""
+Set additionalProperties=False if closed otherwise true if not closed at the global level
+""")
 def cli(yamlfile, **kwargs):
     """ Generate JSON Schema representation of a biolink model """
     print(JsonSchemaGenerator(yamlfile, **kwargs).serialize(**kwargs))
diff --git a/notebooks/examples.ipynb b/notebooks/examples.ipynb
index fa0f2243..91a4609e 100644
--- a/notebooks/examples.ipynb
+++ b/notebooks/examples.ipynb
@@ -26,7 +26,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 1,
    "metadata": {
     "execution": {
      "iopub.execute_input": "2021-04-02T22:12:21.074327Z",
@@ -35,7 +35,19 @@
      "shell.execute_reply": "2021-04-02T22:12:21.669211Z"
     }
    },
-   "outputs": [],
+   "outputs": [
+    {
+     "ename": "ModuleNotFoundError",
+     "evalue": "No module named 'linkml.dumpers'",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
+      "\u001b[0;32m<ipython-input-1-3ba7d22fbdf6>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlinkml\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerators\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0myumlgen\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mYumlGenerator\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mlinkml_runtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mutils\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0myamlutils\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mDupCheckYamlLoader\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mlinkml\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdumpers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjson_dumper\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mdumps\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+      "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'linkml.dumpers'"
+     ]
+    }
+   ],
    "source": [
     "from IPython.core.display import display, HTML\n",
     "from types import ModuleType\n",
@@ -760,7 +772,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.2"
+   "version": "3.7.10"
   }
  },
  "nbformat": 4,


Test patch:
diff --git a/tests/test_scripts/test_gen_json_schema.py b/tests/test_scripts/test_gen_json_schema.py
index d2b3e32a..554b9f24 100644
--- a/tests/test_scripts/test_gen_json_schema.py
+++ b/tests/test_scripts/test_gen_json_schema.py
@@ -28,6 +28,11 @@ class GenJSONSchemaTestCase(ClickTestCase):
     def test_tree_root_args(self):
         self.do_test([env.input_path('roottest.yaml'), '-t', 'c2'], 'rootttest2.jsonld', add_yaml=False)
 
-
+    def test_tree_root_closed(self):
+        self.do_test([env.input_path('roottest.yaml'), '--closed'], 'rootttest3.jsonld', add_yaml=False)
+    
+    def test_tree_root_closed(self):
+        self.do_test([env.input_path('roottest.yaml'), '--not-closed'], 'rootttest4.jsonld', add_yaml=False)
+        
 if __name__ == '__main__':
     unittest.main()