Repository: omni-us/jsonargparse

Issue:
Cannot parse pydantic BaseModel as dict value since version4.29
<!-- If you like this project, please ⭐ star it https://github.com/omni-us/jsonargparse/ -->

## 🐛 Bug report



### To reproduce

1. Using the CLI function
The code as follows can work normally in version 4.28, but fails in versions 4.29/4.30/4.31/4.32.
```python
from typing import Dict, Optional

from jsonargparse import CLI
from pydantic import BaseModel


class InOutPuts(BaseModel):
    inputs: list[str]
    outputs: list[str]


class TrainModel(BaseModel):
    infer: Optional[Dict[str, InOutPuts]] = None


def train_func(model: TrainModel) -> None:
    print(model.infer)


if __name__ == '__main__':
    CLI(train_func)

```
2. given yaml config
```
model:
  infer:
    infer_model:
      inputs: [ 'input1','input2' ]
      outputs: [ 'logit1','logit2' ]
```
report error as follows:

```bash
error: Validation failed: Parser key "model.infer":
  Does not validate against any of the Union subtypes
  Subtypes: (typing.Dict[str, hirec.model.model_json.InOutPuts], <class 'NoneType'>)
  Errors:
    - No action for key "infer_model" to set its default.
    - Expected a <class 'NoneType'>
  Given value type: <class 'dict'>
  Given value: {'infer_model': Namespace(inputs=['id'], outputs=['dcn_logit'])}
```

### Expected behavior
parse config without error


### Environment

- jsonargparse version:4.32.0
- Python version:3.10.8
- How jsonargparse was installed: pip install jsonargparse[all]
- OS:windows11 & centos8


Discussion:
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=559) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=559&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=559&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=559&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=559&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=559&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=559)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=559) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=559&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=559&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=559&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=559&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=559&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=559)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index eeeff79..e531ed7 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.32.1 (2024-08-??)
+--------------------
+
+Fixed
+^^^^^
+- ``dict`` types not correctly forwarding previous nested values when parsing
+  (`#559 <https://github.com/omni-us/jsonargparse/pull/559>`__).
+
+
 v4.32.0 (2024-07-19)
 --------------------
 
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index aec416d..4c90a74 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -904,7 +904,12 @@ def adapt_typehints(
                         for t in sub_add_kwargs["linked_targets"]
                     }
                 else:
-                    kwargs = adapt_kwargs
+                    kwargs = adapt_kwargs.copy()
+                if kwargs.get("prev_val"):
+                    if isinstance(kwargs["prev_val"], dict):
+                        kwargs["prev_val"] = kwargs["prev_val"].get(k)
+                    else:
+                        kwargs["prev_val"] = None
                 val[k] = adapt_typehints(v, subtypehints[1], **kwargs)
         if get_import_path(typehint.__class__) == "typing._TypedDictMeta":
             if typehint.__total__:


Test patch:
diff --git a/jsonargparse_tests/test_dataclass_like.py b/jsonargparse_tests/test_dataclass_like.py
index 9d884c4..963eeaf 100644
--- a/jsonargparse_tests/test_dataclass_like.py
+++ b/jsonargparse_tests/test_dataclass_like.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import dataclasses
+import json
 from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar, Union
 from unittest.mock import patch
 
@@ -684,6 +685,13 @@ if pydantic_support:
         def __init__(self, a: Optional[PydanticModel] = None):
             self.a = a
 
+    class NestedModel(pydantic.BaseModel):
+        inputs: List[str]
+        outputs: List[str]
+
+    class PydanticNestedDict(pydantic.BaseModel):
+        nested: Optional[Dict[str, NestedModel]] = None
+
 
 def none(x):
     return x
@@ -817,6 +825,23 @@ class TestPydantic:
         assert cfg.b.class_path == f"{__name__}.OptionalPydantic"
         assert cfg.b.init_args == Namespace(a=Namespace(p1="x", p2=3))
 
+    def test_nested_dict(self, parser):
+        parser.add_argument("--config", action="config")
+        parser.add_argument("--model", type=PydanticNestedDict)
+        model = {
+            "nested": {
+                "key": {
+                    "inputs": ["a", "b"],
+                    "outputs": ["x", "y"],
+                }
+            }
+        }
+        cfg = parser.parse_args(["--model", json.dumps(model)])
+        assert cfg.model.nested["key"] == Namespace(inputs=["a", "b"], outputs=["x", "y"])
+        init = parser.instantiate_classes(cfg)
+        assert isinstance(init.model, PydanticNestedDict)
+        assert isinstance(init.model.nested["key"], NestedModel)
+
 
 # attrs tests