Repository: koxudaxi/datamodel-code-generator

Issue:
(🎁) Can we use heuristics to automatically detect the input type of json files?
It would be convenient if json files could be automatically detected if they are either schema or data files.

input:
```json
{
  "a": 1
}
```

```
👉 datamodel_code_generator --input test.json
# generated by datamodel-codegen:
#   filename:  test.json
#   timestamp: 2023-04-15T04:05:21+00:00

from __future__ import annotations

from typing import Any

from pydantic import BaseModel


class Model(BaseModel):
    __root__: Any
```

Comments:

Patch:
diff --git a/datamodel_code_generator/__init__.py b/datamodel_code_generator/__init__.py
index 1b7bcab6..b5ccb464 100644
--- a/datamodel_code_generator/__init__.py
+++ b/datamodel_code_generator/__init__.py
@@ -168,6 +168,37 @@ def is_openapi(text: str) -> bool:
     return 'openapi' in load_yaml(text)
 
 
+JSON_SCHEMA_URLS: Tuple[str, ...] = (
+    'http://json-schema.org/',
+    'https://json-schema.org/',
+)
+
+
+def is_schema(text: str) -> bool:
+    data = load_yaml(text)
+    if not isinstance(data, dict):
+        return False
+    schema = data.get('$schema')
+    if isinstance(schema, str) and any(
+        schema.startswith(u) for u in JSON_SCHEMA_URLS
+    ):  # pragma: no cover
+        return True
+    if isinstance(data.get('type'), str):
+        return True
+    if any(
+        isinstance(data.get(o), list)
+        for o in (
+            'allOf',
+            'anyOf',
+            'oneOf',
+        )
+    ):
+        return True
+    if isinstance(data.get('properties'), dict):
+        return True
+    return False
+
+
 class InputFileType(Enum):
     Auto = 'auto'
     OpenAPI = 'openapi'
@@ -304,11 +335,8 @@ def generate(
                 if isinstance(input_, Path)
                 else input_text
             )
-            input_file_type = (
-                InputFileType.OpenAPI
-                if is_openapi(input_text_)  # type: ignore
-                else InputFileType.JsonSchema
-            )
+            assert isinstance(input_text_, str)
+            input_file_type = infer_input_type(input_text_)
             print(
                 inferred_message.format(input_file_type.value),
                 file=sys.stderr,
@@ -483,6 +511,14 @@ def generate(
             file.close()
 
 
+def infer_input_type(text: str) -> InputFileType:
+    if is_openapi(text):
+        return InputFileType.OpenAPI
+    elif is_schema(text):
+        return InputFileType.JsonSchema
+    return InputFileType.Json
+
+
 inferred_message = (
     'The input file type was determined to be: {}\nThis can be specificied explicitly with the '
     '`--input-file-type` option.'


Test patch:
diff --git a/tests/data/jsonschema/items_boolean.json b/tests/data/jsonschema/items_boolean.json
index e12ab0fc..8038870f 100644
--- a/tests/data/jsonschema/items_boolean.json
+++ b/tests/data/jsonschema/items_boolean.json
@@ -1,5 +1,5 @@
 {
-  "$schema ": "http://json-schema.org/draft-07/schema#",
+  "$schema": "http://json-schema.org/draft-07/schema#",
   "type": "object",
   "properties": {
     "example": {
diff --git a/tests/data/jsonschema/root_id.json b/tests/data/jsonschema/root_id.json
index 5dee01e1..b62bcde3 100644
--- a/tests/data/jsonschema/root_id.json
+++ b/tests/data/jsonschema/root_id.json
@@ -1,6 +1,6 @@
 {
   "$id": "https://example.com/root_id.json",
-  "$schema ": "http://json-schema.org/draft-07/schema#",
+  "$schema": "http://json-schema.org/draft-07/schema#",
   "definitions": {
     "Person": {
       "$ref": "person.json"
diff --git a/tests/data/jsonschema/root_id_absolute_url.json b/tests/data/jsonschema/root_id_absolute_url.json
index 9ea3e152..4ac23adc 100644
--- a/tests/data/jsonschema/root_id_absolute_url.json
+++ b/tests/data/jsonschema/root_id_absolute_url.json
@@ -1,6 +1,6 @@
 {
   "$id": "https://example.com/root_id.json",
-  "$schema ": "http://json-schema.org/draft-07/schema#",
+  "$schema": "http://json-schema.org/draft-07/schema#",
   "definitions": {
     "Person": {
       "$ref": "person.json"
diff --git a/tests/data/jsonschema/root_id_ref.json b/tests/data/jsonschema/root_id_ref.json
index 4e73314c..f7de9985 100644
--- a/tests/data/jsonschema/root_id_ref.json
+++ b/tests/data/jsonschema/root_id_ref.json
@@ -1,5 +1,5 @@
 {
-  "$schema ": "http://json-schema.org/draft-07/schema#",
+  "$schema": "http://json-schema.org/draft-07/schema#",
   "definitions": {
     "Person": {
       "$ref": "root_id.json#/definitions/Person"
diff --git a/tests/data/jsonschema/root_id_self_ref.json b/tests/data/jsonschema/root_id_self_ref.json
index 45dcfe64..5c8acb50 100644
--- a/tests/data/jsonschema/root_id_self_ref.json
+++ b/tests/data/jsonschema/root_id_self_ref.json
@@ -1,6 +1,6 @@
 {
   "$id": "https://example.com/root_id_self_ref.json",
-  "$schema ": "http://json-schema.org/draft-07/schema#",
+  "$schema": "http://json-schema.org/draft-07/schema#",
   "definitions": {
     "Person": {
       "$ref": "person.json"
diff --git a/tests/data/openapi/complex_reference.json b/tests/data/openapi/complex_reference.json
index 24fee52c..1d89f9d2 100644
--- a/tests/data/openapi/complex_reference.json
+++ b/tests/data/openapi/complex_reference.json
@@ -1,4 +1,5 @@
 {
+    "openapi": "3.0.0",
     "components": {
         "schemas": {
             "A": {
diff --git a/tests/data/openapi/datetime.yaml b/tests/data/openapi/datetime.yaml
index 21288b3f..9c0a73b1 100644
--- a/tests/data/openapi/datetime.yaml
+++ b/tests/data/openapi/datetime.yaml
@@ -1,3 +1,4 @@
+openapi: "3.0.0"
 components:
   schemas:
     InventoryItem:
diff --git a/tests/data/openapi/definitions.yaml b/tests/data/openapi/definitions.yaml
index 7e6b7d3d..2e99635d 100644
--- a/tests/data/openapi/definitions.yaml
+++ b/tests/data/openapi/definitions.yaml
@@ -1,3 +1,4 @@
+openapi: "3.0.0"
 schemas:
   Problem:
     properties:
diff --git a/tests/data/openapi/discriminator.yaml b/tests/data/openapi/discriminator.yaml
index 334c50f9..9a611ae1 100644
--- a/tests/data/openapi/discriminator.yaml
+++ b/tests/data/openapi/discriminator.yaml
@@ -1,3 +1,4 @@
+openapi: "3.0.0"
 components:
   schemas:
     ObjectBase:
diff --git a/tests/data/openapi/override_required_all_of.yaml b/tests/data/openapi/override_required_all_of.yaml
index 95726152..9c5eeee8 100644
--- a/tests/data/openapi/override_required_all_of.yaml
+++ b/tests/data/openapi/override_required_all_of.yaml
@@ -1,3 +1,4 @@
+openapi: "3.0.0"
 components:
   schemas:
     ObjectBase:
diff --git a/tests/data/openapi/x_enum_varnames.yaml b/tests/data/openapi/x_enum_varnames.yaml
index 5a986655..1b778769 100644
--- a/tests/data/openapi/x_enum_varnames.yaml
+++ b/tests/data/openapi/x_enum_varnames.yaml
@@ -1,4 +1,4 @@
-openapi: 3.0
+openapi: "3.0.0"
 components:
   schemas:
     string:
diff --git a/tests/root_id.json b/tests/root_id.json
index 9ea3e152..4ac23adc 100644
--- a/tests/root_id.json
+++ b/tests/root_id.json
@@ -1,6 +1,6 @@
 {
   "$id": "https://example.com/root_id.json",
-  "$schema ": "http://json-schema.org/draft-07/schema#",
+  "$schema": "http://json-schema.org/draft-07/schema#",
   "definitions": {
     "Person": {
       "$ref": "person.json"
diff --git a/tests/test_infer_input_type.py b/tests/test_infer_input_type.py
new file mode 100644
index 00000000..27b74afd
--- /dev/null
+++ b/tests/test_infer_input_type.py
@@ -0,0 +1,46 @@
+from pathlib import Path
+
+from datamodel_code_generator import InputFileType, infer_input_type
+
+DATA_PATH: Path = Path(__file__).parent / 'data'
+
+
+def test_infer_input_type():
+    def assert_infer_input_type(file: Path, raw_data_type: InputFileType) -> None:
+        __tracebackhide__ = True
+        if file.is_dir():
+            return
+        if file.suffix not in ('.yaml', '.json'):
+            return
+        result = infer_input_type(file.read_text())
+        assert result == raw_data_type, f'{file} was the wrong type!'
+
+    for file in (DATA_PATH / 'json').rglob('*'):
+        if str(file).endswith('broken.json'):
+            continue
+        assert_infer_input_type(file, InputFileType.Json)
+    for file in (DATA_PATH / 'jsonschema').rglob('*'):
+        if str(file).endswith(('external_child.json', 'external_child.yaml')):
+            continue
+        if 'reference_same_hierarchy_directory' in str(file):
+            continue
+        assert_infer_input_type(file, InputFileType.JsonSchema)
+    for file in (DATA_PATH / 'openapi').rglob('*'):
+        if str(file).endswith(
+            (
+                'aliases.json',
+                'extra_data.json',
+                'invalid.yaml',
+                'list.json',
+                'empty_data.json',
+                'root_model.yaml',
+                'json_pointer.yaml',
+                'const.json',
+            )
+        ):
+            continue
+
+        if str(file).endswith('not.json'):
+            assert_infer_input_type(file, InputFileType.Json)
+            continue
+        assert_infer_input_type(file, InputFileType.OpenAPI)