Repository: omni-us/jsonargparse

Issue:
Repeated Keys in Nested List
This was my first day trying out this module 
I notice that repeated key names may lead to undesirable results. Even if they are separated by level

I have given out some examples I tested out. It really only causes issues for two of them








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=397)

[![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=397&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=397&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=397&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=397&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=397&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=397&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=397&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=397&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=397&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=397&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=397&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=397&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=397) 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=397&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=397&metric=new_duplicated_lines_density&view=list)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified lines are covered by tests :white_check_mark:
> Comparison is base [(`863be75`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/863be75d91728f350cb7aa76b1ce139eecf225d7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00% compared to head [(`f693b7e`)](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #397   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           21        21           
  Lines         5819      5819           
=========================================
  Hits          5819      5819           
```

| [Flag](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [py3.10](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.03% <100.00%> (ø)` | |
| [py3.10_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (ø)` | |
| [py3.10_pydantic1](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.46% <33.33%> (+0.01%)` | :arrow_up: |
| [py3.10_pydantic2](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.18% <33.33%> (+0.01%)` | :arrow_up: |
| [py3.10_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (ø)` | |
| [py3.11](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.02% <100.00%> (ø)` | |
| [py3.11_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (ø)` | |
| [py3.11_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (ø)` | |
| [py3.7](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.62% <100.00%> (ø)` | |
| [py3.7_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.47% <100.00%> (ø)` | |
| [py3.7_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.46% <100.00%> (ø)` | |
| [py3.8](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.77% <100.00%> (ø)` | |
| [py3.8_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.50% <100.00%> (ø)` | |
| [py3.8_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.48% <100.00%> (ø)` | |
| [py3.9](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.62% <100.00%> (ø)` | |
| [py3.9_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.36% <100.00%> (ø)` | |
| [py3.9_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.36% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/\_core.py](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL19jb3JlLnB5) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/397?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 81acd96..79e1c54 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,6 +12,15 @@ The semantic versioning only considers the public API as described in
 paths are considered internals and can change in minor and patch releases.
 
 
+v4.25.1 (2023-10-??)
+--------------------
+
+Fixed
+^^^^^
+- Failures with subcommands and default_config_files when keys are repeated
+  (`#160 <https://github.com/omni-us/jsonargparse/issues/160>`__).
+
+
 v4.25.0 (2023-09-25)
 --------------------
 
diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py
index 778613c..0914993 100644
--- a/jsonargparse/_core.py
+++ b/jsonargparse/_core.py
@@ -616,6 +616,7 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
         cfg_path: Union[str, os.PathLike] = "",
         ext_vars: Optional[dict] = None,
         prev_cfg: Optional[Namespace] = None,
+        key: Optional[str] = None,
     ) -> Namespace:
         """Loads a configuration string into a namespace.
 
@@ -633,6 +634,8 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
             raise TypeError(f"Problems parsing config: {ex}") from ex
         if cfg_dict is None:
             return Namespace()
+        if key and isinstance(cfg_dict, dict):
+            cfg_dict = cfg_dict.get(key, {})
         if not isinstance(cfg_dict, dict):
             raise TypeError(f"Unexpected config: {cfg_str}")
         return self._apply_actions(cfg_dict, prev_cfg=prev_cfg)
@@ -954,9 +957,7 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
         default_config_files = self._get_default_config_files()
         for key, default_config_file in default_config_files:
             with change_to_path_dir(default_config_file), parser_context(parent_parser=self):
-                cfg_file = self._load_config_parser_mode(default_config_file.get_content())
-                if key is not None:
-                    cfg_file = cfg_file.get(key)
+                cfg_file = self._load_config_parser_mode(default_config_file.get_content(), key=key)
                 cfg = self.merge_config(cfg_file, cfg)
                 try:
                     with _ActionPrintConfig.skip_print_config():


Test patch:
diff --git a/jsonargparse_tests/test_subcommands.py b/jsonargparse_tests/test_subcommands.py
index ae46995..a629eb3 100644
--- a/jsonargparse_tests/test_subcommands.py
+++ b/jsonargparse_tests/test_subcommands.py
@@ -212,6 +212,37 @@ def test_subcommand_print_config_default_env(subparser):
     assert yaml.safe_load(out) == {"o": 1}
 
 
+def test_subcommand_default_config_repeated_keys(parser, subparser, tmp_cwd):
+    defaults = tmp_cwd / "defaults.json"
+    defaults.write_text('{"test":{"test":"value"}}')
+    parser.default_config_files = [defaults]
+    subparser.add_argument("--test")
+    subcommands = parser.add_subcommands()
+    subcommands.add_subcommand("test", subparser)
+
+    cfg = parser.parse_args([], with_meta=False)
+    assert cfg == Namespace(subcommand="test", test=Namespace(test="value"))
+    cfg = parser.parse_args(["test", "--test=x"], with_meta=False)
+    assert cfg == Namespace(subcommand="test", test=Namespace(test="x"))
+
+
+def test_subsubcommand_default_config_repeated_keys(parser, subparser, tmp_cwd):
+    defaults = tmp_cwd / "defaults.json"
+    defaults.write_text('{"test":{"test":{"test":"value"}}}')
+    parser.default_config_files = [defaults]
+    subsubparser = ArgumentParser()
+    subsubparser.add_argument("--test")
+    subcommands1 = parser.add_subcommands()
+    subcommands1.add_subcommand("test", subparser)
+    subcommands2 = subparser.add_subcommands()
+    subcommands2.add_subcommand("test", subsubparser)
+
+    cfg = parser.parse_args([], with_meta=False)
+    assert cfg.as_dict() == {"subcommand": "test", "test": {"subcommand": "test", "test": {"test": "value"}}}
+    cfg = parser.parse_args(["test", "test", "--test=x"], with_meta=False)
+    assert cfg.as_dict() == {"subcommand": "test", "test": {"subcommand": "test", "test": {"test": "x"}}}
+
+
 def test_subcommand_required_arg_in_default_config(parser, subparser, tmp_cwd):
     Path("config.yaml").write_text("output: test\nprepare:\n  media: test\n")
     parser.default_config_files = ["config.yaml"]