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
```


Discussion:
@KotlinIsland 
Do you have an example?
The CLI tries to parse json file as JSON Schema.
https://github.com/koxudaxi/datamodel-code-generator/blob/b48cb94edc76851eca0ff637bf81831330ea5808/datamodel_code_generator/__init__.py#L301-L310

So here we use check if the file is an OpenAPI, but could we do an additional check to see if the file is not a schema at all, but just a normal json file?
@KotlinIsland 
How can we recognize whether JSON Schema or JSON data?
If we apply the strict rule, current behavior will be broken.
I think some use `auto` to convert rough JSON Schema :thinking: 
Yes, I'm not certain if it's possible to determine if a file is schema or data.

Resolving patch:
diff --git a/datamodel_code_generator/__init__.py b/datamodel_code_generator/__init__.py
--- 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 get_header_and_first_line(csv_file: IO[str]) -> Dict[str, Any]:
             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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- /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)