Repository: omni-us/jsonargparse

Issue:
str argument is mistaken as a dict if it looks like 'Foo:'
<!-- If you like this project, please ⭐ star it https://github.com/omni-us/jsonargparse/ -->

## 🐛 Bug report

An init_args str is mistaken as a dict if it is `Foo:`, even though it is typed `str`

### To reproduce


```python
from jsonargparse import CLI


class Foo:
    pass


class Bar(Foo):
    def __init__(self, x: str):
        pass


def main(y: Bar):
    print(y)


if __name__ == "__main__":
    CLI(main, as_positional=False)

```
### Expected behavior
```
❯ python test_cli.py --y '__main__.Bar' --y.init_args.x 'Foo:'
usage: test_cli.py [-h] [--config CONFIG] [--print_config[=flags]]
                   [--y.help CLASS_PATH_OR_NAME] --y Y
test_cli.py: error: Parser key "y":
  Problem with given class_path '__main__.Bar':
    - Parser key "x":
      Expected a <class 'str'>. Got value: {'Foo': None}

❯ python test_cli.py --y '__main__.Bar' --y.init_args.x 'Foo' 
<__main__.Bar object at 0x7f02a3eb32b0>

```

### Environment

<!-- Fill in the list below. -->

- jsonargparse version (e.g., 4.8.0): 4.20.1
- Python version (e.g., 3.9): 3.9.16
- How jsonargparse was installed (e.g. `pip install jsonargparse[all]`): `pip install jsonargparse`
- OS (e.g., Linux): Linux


Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=263)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=263&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=263) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=263&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=263&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 25f63c1..08bd7b9 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -15,13 +15,18 @@ paths are considered internals and can change in minor and patch releases.
 v4.21.0 (2023-04-??)
 --------------------
 
+Fixed
+^^^^^
+- `str` parameter in subclass incorrectly parsed as dict with implicit `null`
+  value (`#262 <https://github.com/omni-us/jsonargparse/issues/262>`__).
+
 Changed
 ^^^^^^^
 - Switched from ``setup.cfg`` to ``pyproject.toml`` for configuration.
 - Removed ``build_sphinx`` from ``setup.py`` and documented how to build.
 - Include enum members in error when invalid value is given
-  `pytorch-lightning#17247
-  <https://github.com/Lightning-AI/lightning/issues/17247>`__.
+  (`pytorch-lightning#17247
+  <https://github.com/Lightning-AI/lightning/issues/17247>`__).
 
 
 v4.20.1 (2023-03-30)
diff --git a/jsonargparse/loaders_dumpers.py b/jsonargparse/loaders_dumpers.py
index b972dad..a7a1018 100644
--- a/jsonargparse/loaders_dumpers.py
+++ b/jsonargparse/loaders_dumpers.py
@@ -56,10 +56,13 @@ def yaml_load(stream):
         value = stream
     else:
         value = yaml.load(stream, Loader=DefaultLoader)
-    if isinstance(value, dict) and all(v is None for v in value.values()):
-        keys = {k for k in regex_curly_comma.split(stream) if k}
-        if len(keys) > 0 and keys == set(value.keys()):
+    if isinstance(value, dict) and value and all(v is None for v in value.values()):
+        if len(value) == 1 and stream.strip() == next(iter(value.keys())) + ':':
             value = stream
+        else:
+            keys = {k for k in regex_curly_comma.split(stream) if k}
+            if len(keys) > 0 and keys == set(value.keys()):
+                value = stream
     return value
 
 


Test patch:
diff --git a/jsonargparse_tests/test_loaders_dumpers.py b/jsonargparse_tests/test_loaders_dumpers.py
index a667090..cd89d3a 100755
--- a/jsonargparse_tests/test_loaders_dumpers.py
+++ b/jsonargparse_tests/test_loaders_dumpers.py
@@ -11,6 +11,7 @@ import yaml
 from jsonargparse import ActionConfigFile, ArgumentParser, set_dumper, set_loader
 from jsonargparse._common import parser_context
 from jsonargparse.loaders_dumpers import load_value, loaders, yaml_dump
+from jsonargparse_tests.base import mock_module
 
 
 class LoadersTests(unittest.TestCase):
@@ -36,6 +37,18 @@ class LoadersTests(unittest.TestCase):
         self.assertEqual('{one,two,three}', parser.parse_args(['--val={one,two,three}']).val)
 
 
+    def test_disable_implicit_null(self):
+        class Bar:
+            def __init__(self, x: str):
+                pass
+
+        with mock_module(Bar):
+            parser = ArgumentParser()
+            parser.add_subclass_arguments(Bar, 'bar')
+            cfg = parser.parse_args(['--bar=Bar', '--bar.x=Foo:'])
+            self.assertEqual('Foo:', cfg.bar.init_args.x)
+
+
     @unittest.skipIf(not find_spec('omegaconf'), 'omegaconf package is required')
     def test_parser_mode_omegaconf(self):
         parser = ArgumentParser(exit_on_error=False, parser_mode='omegaconf')