Repository: omni-us/jsonargparse
Pull request #652 (2024-12-24 11:30:44)

Refactor to eventually remove pyyaml as a required dependency
## What does this PR do?

Preliminary implementation for #229

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/main/CONTRIBUTING.rst)?
- [n/a] Did you update **the documentation**? (readme and public docstrings)
- [x] Did you write **unit tests** such that there is 100% coverage on related code? (required for bug fixes and new features)
- [x] Did you verify that new and existing **tests pass locally**?
- [x] Did you make sure that all changes preserve **backward compatibility**?
- [x] Did you update **the CHANGELOG**? (not for typos, docs, test updates, or minor internal changes/refactors)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/652?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`c934d25`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/c934d256ebc8f017c5377b3d99141d9f28054a08?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`86e762f`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/86e762fdc068236a8692bcd7619e9f6d62737441?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

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


```diff
@@            Coverage Diff            @@
##              main      #652   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           22        22           
  Lines         6509      6509           
=========================================
  Hits          6509      6509           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/652?dropdown=coverage&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 1936118..e628031 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,9 +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.35.1 (2024-12-??)
+v4.36.0 (2024-12-??)
 --------------------
 
+Added
+^^^^^
+- Support without ``pyyaml``, though only an internal refactor prior to eventual
+  removal of ``pyyaml`` as a required dependency in v5.0.0 (`#652
+  <https://github.com/omni-us/jsonargparse/pull/652>`__).
+
 Fixed
 ^^^^^
 - Help for ``Protocol`` types not working correctly (`#645
diff --git a/jsonargparse/_actions.py b/jsonargparse/_actions.py
index 47061fa..7ae6572 100644
--- a/jsonargparse/_actions.py
+++ b/jsonargparse/_actions.py
@@ -201,7 +201,7 @@ class ActionConfigFile(Action):
                         raise ex_path
                     cfg_path = None
                     cfg_file = parser.parse_string(value, **kwargs)
-                except (TypeError,) + get_loader_exceptions() as ex_str:
+                except (TypeError, ValueError) + get_loader_exceptions() as ex_str:  # type: ignore[misc]
                     raise TypeError(f'Parser key "{dest}": {ex_str}') from ex_str
             else:
                 cfg_file = parser.parse_path(value, **kwargs)
diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py
index d2ae627..ee503b6 100644
--- a/jsonargparse/_core.py
+++ b/jsonargparse/_core.py
@@ -78,6 +78,7 @@ from ._optionals import (
     get_config_read_mode,
     import_fsspec,
     import_jsonnet,
+    pyyaml_available,
 )
 from ._parameter_resolvers import UnknownDefault
 from ._signatures import SignatureArguments
@@ -231,7 +232,7 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
         logger: Union[bool, str, dict, logging.Logger] = False,
         version: Optional[str] = None,
         print_config: Optional[str] = "--print_config",
-        parser_mode: str = "yaml",
+        parser_mode: str = "yaml" if pyyaml_available else "json",
         dump_header: Optional[List[str]] = None,
         default_config_files: Optional[List[Union[str, os.PathLike]]] = None,
         default_env: bool = False,
@@ -250,7 +251,7 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
             logger: Configures the logger, see :class:`.LoggerProperty`.
             version: Program version which will be printed by the --version argument.
             print_config: Name for print config argument, ``%s`` is replaced by config dest, set None to disable.
-            parser_mode: Mode for parsing config files: ``'yaml'``, ``'jsonnet'`` or ones added via :func:`.set_loader`.
+            parser_mode: Mode for parsing values: ``yaml``, ``json``, ``jsonnet`` or added via :func:`.set_loader`.
             dump_header: Header to include as comment when dumping a config object.
             default_config_files: Default config file locations, e.g. ``['~/.config/myapp/*.yaml']``.
             default_env: Set the default value on whether to parse environment variables.
diff --git a/jsonargparse/_jsonnet.py b/jsonargparse/_jsonnet.py
index 4137648..63a9009 100644
--- a/jsonargparse/_jsonnet.py
+++ b/jsonargparse/_jsonnet.py
@@ -11,6 +11,7 @@ from ._optionals import (
     get_jsonschema_exceptions,
     import_jsonnet,
     import_jsonschema,
+    pyyaml_available,
 )
 from ._typehints import ActionTypeHint
 from ._util import NoneType, Path, argument_error
@@ -45,10 +46,11 @@ class ActionJsonnet(Action):
             if schema is not None:
                 jsonvalidator = import_jsonschema("ActionJsonnet")[1]
                 if isinstance(schema, str):
-                    with parser_context(load_value_mode="yaml"):
+                    mode = "yaml" if pyyaml_available else "json"
+                    with parser_context(load_value_mode=mode):
                         try:
                             schema = load_value(schema)
-                        except get_loader_exceptions() as ex:
+                        except get_loader_exceptions(mode) as ex:
                             raise ValueError(f"Problems parsing schema: {ex}") from ex
                 jsonvalidator.check_schema(schema)
                 self._validator = ActionJsonSchema._extend_jsonvalidator_with_default(jsonvalidator)(schema)
@@ -162,7 +164,7 @@ class ActionJsonnet(Action):
             fname = jsonnet(absolute=False) if isinstance(jsonnet, Path) else jsonnet
             snippet = fpath.get_content()
         try:
-            with parser_context(load_value_mode="yaml"):
+            with parser_context(load_value_mode="yaml" if pyyaml_available else "json"):
                 values = load_value(_jsonnet.evaluate_snippet(fname, snippet, ext_vars=ext_vars, ext_codes=ext_codes))
         except RuntimeError as ex:
             raise argument_error(f'Problems evaluating jsonnet "{fname}": {ex}') from ex
diff --git a/jsonargparse/_jsonschema.py b/jsonargparse/_jsonschema.py
index c85e33e..a7b9d2f 100644
--- a/jsonargparse/_jsonschema.py
+++ b/jsonargparse/_jsonschema.py
@@ -10,6 +10,7 @@ from ._namespace import strip_meta
 from ._optionals import (
     get_jsonschema_exceptions,
     import_jsonschema,
+    pyyaml_available,
 )
 from ._util import parse_value_or_config
 
@@ -35,11 +36,12 @@ class ActionJsonSchema(Action):
         """
         if schema is not None:
             if isinstance(schema, str):
-                with parser_context(load_value_mode="yaml"):
+                mode = "yaml" if pyyaml_available else "json"
+                with parser_context(load_value_mode=mode):
                     try:
                         schema = load_value(schema)
-                    except get_loader_exceptions() as ex:
-                        raise ValueError(f"Problems parsing schema :: {ex}") from ex
+                    except get_loader_exceptions(mode) as ex:
+                        raise ValueError(f"Problems parsing schema: {ex}") from ex
             jsonvalidator = import_jsonschema("ActionJsonSchema")[1]
             jsonvalidator.check_schema(schema)
             self._validator = self._extend_jsonvalidator_with_default(jsonvalidator)(schema)
diff --git a/jsonargparse/_loaders_dumpers.py b/jsonargparse/_loaders_dumpers.py
index ee436c9..9d76d24 100644
--- a/jsonargparse/_loaders_dumpers.py
+++ b/jsonargparse/_loaders_dumpers.py
@@ -2,12 +2,10 @@
 
 import inspect
 import re
-from typing import Any, Callable, Dict, Tuple, Type
-
-import yaml
+from typing import Any, Callable, Dict, Optional, Tuple, Type
 
 from ._common import load_value_mode, parent_parser
-from ._optionals import import_jsonnet, omegaconf_support
+from ._optionals import import_jsonnet, omegaconf_support, pyyaml_available
 from ._type_checking import ArgumentParser
 
 __all__ = [
@@ -17,44 +15,55 @@ __all__ = [
 ]
 
 
-class DefaultLoader(getattr(yaml, "CSafeLoader", yaml.SafeLoader)):  # type: ignore[misc]
-    pass
+yaml_default_loader = None
+
 
+def get_yaml_default_loader():
+    global yaml_default_loader
+    if yaml_default_loader:
+        return yaml_default_loader
 
-# https://stackoverflow.com/a/37958106/2732151
-def remove_implicit_resolver(cls, tag_to_remove):
-    if "yaml_implicit_resolvers" not in cls.__dict__:
-        cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy()
+    import yaml
 
-    for first_letter, mappings in cls.yaml_implicit_resolvers.items():
-        cls.yaml_implicit_resolvers[first_letter] = [(tag, regexp) for tag, regexp in mappings if tag != tag_to_remove]
+    class DefaultLoader(getattr(yaml, "CSafeLoader", yaml.SafeLoader)):
+        pass
 
+    # https://stackoverflow.com/a/37958106/2732151
+    def remove_implicit_resolver(cls, tag_to_remove):
+        if "yaml_implicit_resolvers" not in cls.__dict__:
+            cls.yaml_implicit_resolvers = cls.yaml_implicit_resolvers.copy()
 
-remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:timestamp")
-remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:float")
+        for first_letter, mappings in cls.yaml_implicit_resolvers.items():
+            cls.yaml_implicit_resolvers[first_letter] = [
+                (tag, regexp) for tag, regexp in mappings if tag != tag_to_remove
+            ]
 
+    remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:timestamp")
+    remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:float")
 
-DefaultLoader.add_implicit_resolver(
-    "tag:yaml.org,2002:float",
-    re.compile(
-        """^(?:
-     [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
-    |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
-    |\\.[0-9_]+(?:[eE][-+][0-9]+)?
-    |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
-    |[-+]?\\.(?:inf|Inf|INF)
-    |\\.(?:nan|NaN|NAN))$""",
-        re.X,
-    ),
-    list("-+0123456789."),
-)
+    DefaultLoader.add_implicit_resolver(
+        "tag:yaml.org,2002:float",
+        re.compile(
+            """^(?:
+        [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
+        |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
+        |\\.[0-9_]+(?:[eE][-+][0-9]+)?
+        |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
+        |[-+]?\\.(?:inf|Inf|INF)
+        |\\.(?:nan|NaN|NAN))$""",
+            re.X,
+        ),
+        list("-+0123456789."),
+    )
+
+    yaml_default_loader = DefaultLoader
+    return DefaultLoader
 
 
 def yaml_load(stream):
-    if stream.strip() == "-":
-        value = stream
-    else:
-        value = yaml.load(stream, Loader=DefaultLoader)
+    import yaml
+
+    value = yaml.load(stream, Loader=get_yaml_default_loader())
     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
@@ -65,6 +74,15 @@ def yaml_load(stream):
     return value
 
 
+def json_load(stream):
+    import json
+
+    try:
+        return json.loads(stream)
+    except json.JSONDecodeError:
+        return stream
+
+
 def jsonnet_load(stream, path="", ext_vars=None):
     from ._jsonnet import ActionJsonnet
 
@@ -72,26 +90,21 @@ def jsonnet_load(stream, path="", ext_vars=None):
     _jsonnet = import_jsonnet("jsonnet_load")
     try:
         val = _jsonnet.evaluate_snippet(path, stream, ext_vars=ext_vars, ext_codes=ext_codes)
-    except RuntimeError as ex:
+    except RuntimeError:
         try:
-            return yaml_load(stream)
-        except pyyaml_exceptions:
+            return json_or_yaml_load(stream)
+        except json_or_yaml_loader_exceptions as ex:
             raise ValueError(str(ex)) from ex
-    return yaml_load(val)
+    return json_or_yaml_load(val)
 
 
 loaders: Dict[str, Callable] = {
     "yaml": yaml_load,
+    "json": json_load,
     "jsonnet": jsonnet_load,
 }
 
-pyyaml_exceptions = (yaml.YAMLError,)
-jsonnet_exceptions = pyyaml_exceptions + (ValueError,)
-
-loader_exceptions: Dict[str, Tuple[Type[Exception], ...]] = {
-    "yaml": pyyaml_exceptions,
-    "jsonnet": jsonnet_exceptions,
-}
+loader_exceptions: Dict[str, Tuple[Type[Exception], ...]] = {}
 
 
 def get_load_value_mode() -> str:
@@ -103,11 +116,28 @@ def get_load_value_mode() -> str:
     return mode
 
 
-def get_loader_exceptions():
-    return loader_exceptions[get_load_value_mode()]
+def get_loader_exceptions(mode: Optional[str] = None) -> Tuple[Type[Exception], ...]:
+    if mode is None:
+        mode = get_load_value_mode()
+    if mode not in loader_exceptions:
+        if mode == "yaml":
+            loader_exceptions[mode] = (__import__("yaml").YAMLError,)
+        elif mode == "json":
+            loader_exceptions[mode] = tuple()
+        elif mode == "jsonnet":
+            return get_loader_exceptions("yaml" if pyyaml_available else "json")
+    return loader_exceptions[mode]
+
+
+json_or_yaml_load = yaml_load if pyyaml_available else json_load
+json_or_yaml_loader_exceptions = get_loader_exceptions("yaml" if pyyaml_available else "json")
 
 
 def load_value(value: str, simple_types: bool = False, **kwargs):
+    if not value:
+        return None
+    elif value.strip() == "-":
+        return value
     loader = loaders[get_load_value_mode()]
     if kwargs:
         params = set(list(inspect.signature(loader).parameters)[1:])
@@ -131,6 +161,8 @@ dump_json_kwargs = {
 
 
 def yaml_dump(data):
+    import yaml
+
     return yaml.safe_dump(data, **dump_yaml_kwargs)
 
 
@@ -184,7 +216,11 @@ def dump_using_format(parser: "ArgumentParser", data: dict, dump_format: str) ->
     return dump
 
 
-def set_loader(mode: str, loader_fn: Callable[[str], Any], exceptions: Tuple[Type[Exception], ...] = pyyaml_exceptions):
+def set_loader(
+    mode: str,
+    loader_fn: Callable[[str], Any],
+    exceptions: Tuple[Type[Exception], ...] = tuple(),
+):
     """Sets the value loader function to be used when parsing with a certain mode.
 
     The ``loader_fn`` function must accept as input a single str type parameter
@@ -197,7 +233,7 @@ def set_loader(mode: str, loader_fn: Callable[[str], Any], exceptions: Tuple[Typ
         mode: The parser mode for which to set its loader function. Example: "yaml".
         loader_fn: The loader function to set. Example: ``yaml.safe_load``.
         exceptions: Exceptions that the loader can raise when load fails.
-            Example: (yaml.parser.ParserError, yaml.scanner.ScannerError).
+            Example: (yaml.YAMLError,).
     """
     loaders[mode] = loader_fn
     loader_exceptions[mode] = exceptions
diff --git a/jsonargparse/_namespace.py b/jsonargparse/_namespace.py
index a760dc5..872f859 100644
--- a/jsonargparse/_namespace.py
+++ b/jsonargparse/_namespace.py
@@ -354,6 +354,9 @@ def dict_to_namespace(cfg_dict: Union[Dict[str, Any], Namespace]) -> Namespace:
 
 
 # Temporal to provide backward compatibility in pytorch-lightning
-import yaml  # noqa: E402
+from importlib.util import find_spec  # noqa: E402
 
-yaml.SafeDumper.add_representer(Namespace, lambda d, x: d.represent_mapping("tag:yaml.org,2002:map", x.as_dict()))
+if find_spec("yaml"):
+    import yaml
+
+    yaml.SafeDumper.add_representer(Namespace, lambda d, x: d.represent_mapping("tag:yaml.org,2002:map", x.as_dict()))
diff --git a/jsonargparse/_optionals.py b/jsonargparse/_optionals.py
index e1f2f01..fb0dacc 100644
--- a/jsonargparse/_optionals.py
+++ b/jsonargparse/_optionals.py
@@ -14,6 +14,7 @@ __all__ = [
 ]
 
 
+pyyaml_available = bool(find_spec("yaml"))
 typing_extensions_support = find_spec("typing_extensions") is not None
 typeshed_client_support = find_spec("typeshed_client") is not None
 jsonschema_support = find_spec("jsonschema") is not None
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index 986d1d0..d3a89d0 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -56,9 +56,9 @@ from ._common import (
 )
 from ._loaders_dumpers import (
     get_loader_exceptions,
+    json_or_yaml_load,
+    json_or_yaml_loader_exceptions,
     load_value,
-    pyyaml_exceptions,
-    yaml_load,
 )
 from ._namespace import Namespace
 from ._optionals import (
@@ -762,8 +762,8 @@ def adapt_typehints(
     # Basic types
     elif typehint in leaf_types:
         if isinstance(val, str) and typehint is not str:
-            with suppress(*pyyaml_exceptions):
-                val = yaml_load(val)
+            with suppress(*json_or_yaml_loader_exceptions):
+                val = json_or_yaml_load(val)
         if typehint is float and isinstance(val, int) and not isinstance(val, bool):
             val = float(val)
         if not isinstance(val, typehint) or (typehint in (int, float) and isinstance(val, bool)):
diff --git a/pyproject.toml b/pyproject.toml
index f184bfc..14d0e01 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -191,7 +191,7 @@ Villegas = "Villegas"
 [tool.tox]
 legacy_tox_ini = """
 [tox]
-envlist = py{38,39,310,311,312,313}-{all,no}-extras,omegaconf,pydantic-v1
+envlist = py{38,39,310,311,312,313}-{all,no}-extras,omegaconf,pydantic-v1,without-pyyaml
 skip_missing_interpreters = true
 
 [testenv]
@@ -232,4 +232,10 @@ commands =
     "
     pip install "pydantic>=2"
     python -m pytest {posargs} {envtmpdir}/test_dataclass_like.py
+
+[testenv:without-pyyaml]
+extras = test,coverage,all
+commands =
+    pip uninstall -y argcomplete omegaconf pyyaml reconplogger responses ruyaml types-PyYAML
+    python -m pytest {posargs}
 """


Test patch:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index fc61938..76f0610 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -122,6 +122,19 @@ jobs:
           name: junit_pydantic
           path: ./junit_py*
 
+  without-pyyaml:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.10"
+          cache: pip
+      - run: |
+          pip install .[test,all]
+          pip uninstall -y argcomplete omegaconf pyyaml reconplogger responses ruyaml types-PyYAML
+          pytest
+
   build-package:
     runs-on: ubuntu-latest
     steps:
@@ -245,7 +258,7 @@ jobs:
   pypi-publish:
     if: startsWith(github.ref, 'refs/tags/v')
     runs-on: ubuntu-latest
-    needs: [linux, windows, macos, omegaconf, pydantic-v1, installed-package, doctest, mypy]
+    needs: [linux, windows, macos, omegaconf, pydantic-v1, without-pyyaml, installed-package, doctest, mypy]
     environment:
       name: pypi
       url: https://pypi.org/p/jsonargparse
diff --git a/jsonargparse_tests/conftest.py b/jsonargparse_tests/conftest.py
index cfd9e10..b40ea10 100644
--- a/jsonargparse_tests/conftest.py
+++ b/jsonargparse_tests/conftest.py
@@ -13,10 +13,12 @@ from unittest.mock import MagicMock, patch
 import pytest
 
 from jsonargparse import ArgumentParser
+from jsonargparse._loaders_dumpers import json_dump, json_load, yaml_dump, yaml_load
 from jsonargparse._optionals import (
     docstring_parser_support,
     fsspec_support,
     jsonschema_support,
+    pyyaml_available,
     set_docstring_parse_options,
     url_support,
 )
@@ -32,6 +34,14 @@ columns_env = {"COLUMNS": "200"}
 is_cpython = platform.python_implementation() == "CPython"
 is_posix = os.name == "posix"
 
+json_or_yaml_dump = yaml_dump if pyyaml_available else json_dump
+json_or_yaml_load = yaml_load if pyyaml_available else json_load
+
+skip_if_no_pyyaml = pytest.mark.skipif(
+    not pyyaml_available,
+    reason="PyYAML package is required",
+)
+
 skip_if_not_cpython = pytest.mark.skipif(
     not is_cpython,
     reason="only supported in CPython",
diff --git a/jsonargparse_tests/test_actions.py b/jsonargparse_tests/test_actions.py
index 3ee8120..3418109 100644
--- a/jsonargparse_tests/test_actions.py
+++ b/jsonargparse_tests/test_actions.py
@@ -12,7 +12,7 @@ from jsonargparse import (
     Namespace,
     strip_meta,
 )
-from jsonargparse_tests.conftest import get_parser_help
+from jsonargparse_tests.conftest import get_parser_help, json_or_yaml_dump
 
 # ActionConfigFile tests
 
@@ -21,12 +21,12 @@ def test_action_config_file(parser, tmp_cwd):
     rel_yaml_file = Path("subdir", "config.yaml")
     abs_yaml_file = (tmp_cwd / rel_yaml_file).resolve()
     abs_yaml_file.parent.mkdir()
-    abs_yaml_file.write_text("val: yaml\n")
+    abs_yaml_file.write_text(json_or_yaml_dump({"val": "yaml"}))
 
     parser.add_argument("--cfg", action="config")
     parser.add_argument("--val")
 
-    cfg = parser.parse_args([f"--cfg={abs_yaml_file}", f"--cfg={rel_yaml_file}", "--cfg", "val: arg"])
+    cfg = parser.parse_args([f"--cfg={abs_yaml_file}", f"--cfg={rel_yaml_file}", "--cfg", '{"val": "arg"}'])
     assert 3 == len(cfg.cfg)
     assert "arg" == cfg.val
     assert str(abs_yaml_file) == cfg.cfg[0].absolute
@@ -176,9 +176,9 @@ def composed_parsers(tmp_path_factory):
     yaml_inner2 = tmp_path / "inner2.yaml"
     yaml_inner3 = tmp_path / "inner3.yaml"
 
-    yaml_main.write_text("opt1: opt1_yaml\ninner2: inner2.yaml\n")
-    yaml_inner2.write_text("opt2: opt2_yaml\ninner3: inner3.yaml\n")
-    yaml_inner3.write_text("opt3: opt3_yaml\n")
+    yaml_main.write_text(json_or_yaml_dump({"opt1": "opt1_yaml", "inner2": "inner2.yaml"}))
+    yaml_inner2.write_text(json_or_yaml_dump({"opt2": "opt2_yaml", "inner3": "inner3.yaml"}))
+    yaml_inner3.write_text(json_or_yaml_dump({"opt3": "opt3_yaml"}))
 
     return parser, yaml_main, yaml_inner2, yaml_inner3
 
@@ -240,11 +240,11 @@ def test_action_parser_parse_args_subconfig_string(composed_parsers):
     parser = composed_parsers[0]
 
     expected = {"opt2": "opt2_str", "inner3": {"opt3": "opt3_str"}}
-    cfg = parser.parse_args([f"--inner2={expected}"], with_meta=False)
+    cfg = parser.parse_args([f"--inner2={json_or_yaml_dump(expected)}"], with_meta=False)
     assert expected == cfg.inner2.as_dict()
 
     expected = {"opt3": "opt3_str"}
-    cfg = parser.parse_args([f"--inner2.inner3={expected}"], with_meta=False)
+    cfg = parser.parse_args([f"--inner2.inner3={json_or_yaml_dump(expected)}"], with_meta=False)
     assert expected == cfg.inner2.inner3.as_dict()
 
 
diff --git a/jsonargparse_tests/test_cli.py b/jsonargparse_tests/test_cli.py
index 22798a6..1a2f46a 100644
--- a/jsonargparse_tests/test_cli.py
+++ b/jsonargparse_tests/test_cli.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import asyncio
+import json
 import os
 import sys
 from contextlib import redirect_stderr, redirect_stdout, suppress
@@ -11,12 +12,11 @@ from typing import Callable, Literal, Optional
 from unittest.mock import patch
 
 import pytest
-import yaml
 
 from jsonargparse import CLI, capture_parser, lazy_instance
 from jsonargparse._optionals import docstring_parser_support, ruyaml_support
 from jsonargparse.typing import final
-from jsonargparse_tests.conftest import skip_if_docstring_parser_unavailable
+from jsonargparse_tests.conftest import json_or_yaml_dump, json_or_yaml_load, skip_if_docstring_parser_unavailable
 
 
 def get_cli_stdout(*args, **kwargs) -> str:
@@ -214,12 +214,12 @@ def test_single_class_help_docstring_parse_error():
 
 def test_single_class_print_config_after_subcommand():
     out = get_cli_stdout(Class1, args=["0", "method1", "2", "--print_config"])
-    assert "m1: 2" == out.strip()
+    assert {"m1": 2} == json_or_yaml_load(out)
 
 
 def test_single_class_print_config_before_subcommand():
     out = get_cli_stdout(Class1, args=["--print_config", "0", "method1", "2"])
-    cfg = yaml.safe_load(out)
+    cfg = json_or_yaml_load(out)
     assert cfg == {"i1": "0", "method1": {"m1": 2}}
 
 
@@ -305,17 +305,17 @@ def test_function_and_class_subsubcommand_help():
 
 def test_function_and_class_print_config_after_subsubcommand():
     out = get_cli_stdout([cmd1, Cmd2, cmd3], args=["Cmd2", "method2", "--print_config"])
-    assert "m2: 0\n" == out
+    assert {"m2": 0} == json_or_yaml_load(out)
 
 
 def test_function_and_class_print_config_in_between_subcommands():
     out = get_cli_stdout([cmd1, Cmd2, cmd3], args=["Cmd2", "--print_config", "method2"])
-    assert "i1: d\nmethod2:\n  m2: 0\n" == out
+    assert {"i1": "d", "method2": {"m2": 0}} == json_or_yaml_load(out)
 
 
 def test_function_and_class_print_config_before_subcommands():
     out = get_cli_stdout([cmd1, Cmd2, cmd3], args=["--print_config", "Cmd2", "method2"])
-    assert "Cmd2:\n  i1: d\n  method2:\n    m2: 0\n" == out
+    assert {"Cmd2": {"i1": "d", "method2": {"m2": 0}}} == json_or_yaml_load(out)
 
 
 @skip_if_docstring_parser_unavailable
@@ -499,9 +499,9 @@ class C:
 
 
 def test_subclass_type_config_file(tmp_cwd):
-    a_yaml = {"class_path": f"{__name__}.A", "init_args": {"p1": "a yaml"}}
-    Path("config.yaml").write_text("a: a.yaml\n")
-    Path("a.yaml").write_text(yaml.safe_dump(a_yaml))
+    a_conf = {"class_path": f"{__name__}.A", "init_args": {"p1": "a yaml"}}
+    Path("config.yaml").write_text(json_or_yaml_dump({"a": "a.yaml"}))
+    Path("a.yaml").write_text(json_or_yaml_dump(a_conf))
 
     out = get_cli_stdout(C, args=["--config=config.yaml", "cmd_a"])
     assert "a yaml\n" == out
@@ -509,8 +509,9 @@ def test_subclass_type_config_file(tmp_cwd):
     out = get_cli_stdout(C, args=["cmd_a", "--help"])
     assert "--config" not in out
 
-    Path("config.yaml").write_text("a: a.yaml\nb: b.yaml\n")
-    Path("b.yaml").write_text(f"class_path: {__name__}.B\ninit_args:\n  a: a.yaml\n")
+    b_conf = {"class_path": f"{__name__}.B", "init_args": {"a": "a.yaml"}}
+    Path("config.yaml").write_text(json_or_yaml_dump({"a": "a.yaml", "b": "b.yaml"}))
+    Path("b.yaml").write_text(json_or_yaml_dump(b_conf))
 
     out = get_cli_stdout(C, args=["--config=config.yaml", "cmd_b"])
     assert "a yaml\n" == out
@@ -527,10 +528,10 @@ def run_bf(b: BF):
 
 
 def test_final_and_subclass_type_config_file(tmp_cwd):
-    a_yaml = {"class_path": f"{__name__}.A", "init_args": {"p1": "a yaml"}}
-    Path("config.yaml").write_text("b: b.yaml\n")
-    Path("b.yaml").write_text("a: a.yaml\n")
-    Path("a.yaml").write_text(yaml.safe_dump(a_yaml))
+    a_conf = {"class_path": f"{__name__}.A", "init_args": {"p1": "a yaml"}}
+    Path("config.yaml").write_text(json_or_yaml_dump({"b": "b.yaml"}))
+    Path("b.yaml").write_text(json_or_yaml_dump({"a": "a.yaml"}))
+    Path("a.yaml").write_text(json_or_yaml_dump(a_conf))
 
     out = CLI(run_bf, args=["--config=config.yaml"])
     assert "a yaml" == out
@@ -574,4 +575,4 @@ def test_async_instance():
             "init_args": {"time": 0.0, "require_async": True},
         }
     }
-    assert "done" == CLI(run_async_instance, args=[f"--config={config}"])
+    assert "done" == CLI(run_async_instance, args=[f"--config={json.dumps(config)}"])
diff --git a/jsonargparse_tests/test_core.py b/jsonargparse_tests/test_core.py
index e82711d..e2c5b00 100644
--- a/jsonargparse_tests/test_core.py
+++ b/jsonargparse_tests/test_core.py
@@ -11,7 +11,6 @@ from random import randint, shuffle
 from unittest.mock import patch
 
 import pytest
-import yaml
 
 from jsonargparse import (
     SUPPRESS,
@@ -27,7 +26,7 @@ from jsonargparse import (
 )
 from jsonargparse._formatters import get_env_var
 from jsonargparse._namespace import NSKeyError
-from jsonargparse._optionals import jsonnet_support, jsonschema_support, ruyaml_support
+from jsonargparse._optionals import jsonnet_support, jsonschema_support, pyyaml_available, ruyaml_support
 from jsonargparse.typing import Path_fc, Path_fr, path_type
 from jsonargparse_tests.conftest import (
     capture_logs,
@@ -35,9 +34,12 @@ from jsonargparse_tests.conftest import (
     get_parse_args_stdout,
     get_parser_help,
     is_cpython,
+    json_or_yaml_dump,
+    json_or_yaml_load,
     responses_activate,
     skip_if_docstring_parser_unavailable,
     skip_if_fsspec_unavailable,
+    skip_if_no_pyyaml,
     skip_if_not_posix,
     skip_if_responses_unavailable,
     skip_if_running_as_root,
@@ -152,10 +154,10 @@ def test_parse_args_choices_config(parser):
     parser.add_argument("--cfg", action="config")
     parser.add_argument("--ch1", choices="ABC")
     parser.add_argument("--ch2", type=str, choices=["v1", "v2"])
-    assert parser.parse_args(["--cfg=ch1: B"]).ch1 == "B"
-    assert parser.parse_args(["--cfg=ch2: v2"]).ch2 == "v2"
-    pytest.raises(ArgumentError, lambda: parser.parse_args(["--cfg=ch1: D"]))
-    pytest.raises(ArgumentError, lambda: parser.parse_args(["--cfg=ch2: v0"]))
+    assert parser.parse_args(['--cfg={"ch1": "B"}']).ch1 == "B"
+    assert parser.parse_args(['--cfg={"ch2": "v2"}']).ch2 == "v2"
+    pytest.raises(ArgumentError, lambda: parser.parse_args(['--cfg={"ch1": "D"}']))
+    pytest.raises(ArgumentError, lambda: parser.parse_args(['--cfg={"ch2": "v0"}']))
 
 
 def test_parse_args_non_hashable_choice(parser):
@@ -163,7 +165,7 @@ def test_parse_args_non_hashable_choice(parser):
     parser.add_argument("--cfg", action="config")
     parser.add_argument("--ch1", choices=choices.keys())
     with pytest.raises(ArgumentError) as ctx:
-        parser.parse_args(["--cfg=ch1: [1,2]"])
+        parser.parse_args(['--cfg={"ch1": [1,2]}'])
     ctx.match("not among choices")
 
 
@@ -349,14 +351,14 @@ def test_precedence_of_sources(tmp_cwd, subtests):
     parser.add_argument("--op2")
     parser.add_argument("--cfg", action="config")
 
-    input1_config_file.write_text("op1: from input config file")
-    input2_config_file.write_text("op2: unused")
+    input1_config_file.write_text(json_or_yaml_dump({"op1": "from input config file"}))
+    input2_config_file.write_text(json_or_yaml_dump({"op2": "unused"}))
 
     # parse_env precedence
     with subtests.test("parse_env parser default"):
         assert "from parser default" == parser.parse_env({}).op1
     with subtests.test("parse_env default config file"):
-        default_config_file.write_text("op1: from default config file")
+        default_config_file.write_text(json_or_yaml_dump({"op1": "from default config file"}))
         assert "from default config file" == parser.parse_env({}).op1
     with subtests.test("parse_env environment config string"):
         env = {"APP_CFG": '{"op1": "from env config"}'}
@@ -370,7 +372,7 @@ def test_precedence_of_sources(tmp_cwd, subtests):
     with subtests.test("parse_path parser default"):
         assert "from parser default" == parser.parse_path(input2_config_file).op1
     with subtests.test("parse_path default config file"):
-        default_config_file.write_text("op1: from default config file")
+        default_config_file.write_text(json_or_yaml_dump({"op1": "from default config file"}))
         assert "from default config file" == parser.parse_path(input2_config_file).op1
     env = {"APP_CFG": str(input1_config_file)}
     with subtests.test("parse_path environment config file"), patch.dict(os.environ, env):
@@ -387,7 +389,7 @@ def test_precedence_of_sources(tmp_cwd, subtests):
     with subtests.test("parse_args parser default"):
         assert "from parser default" == parser.parse_args([]).op1
     with subtests.test("parse_args default config file"):
-        default_config_file.write_text("op1: from default config file")
+        default_config_file.write_text(json_or_yaml_dump({"op1": "from default config file"}))
         assert "from default config file" == parser.parse_args([]).op1
     env = {"APP_CFG": str(input1_config_file)}
     with subtests.test("parse_args environment config file"), patch.dict(os.environ, env):
@@ -458,22 +460,27 @@ def test_dump_complete(dump_parser):
 
 def test_dump_incomplete(dump_parser):
     dump = dump_parser.dump(Namespace(op1=456))
-    assert "op1: 456" == dump.strip()
+    assert {"op1": 456} == json_or_yaml_load(dump)
 
 
 @pytest.mark.skipif(not is_cpython, reason="requires __setattr__ insertion order")
 def test_dump_formats(dump_parser):
     cfg = dump_parser.get_defaults()
-    assert dump_parser.dump(cfg) == "op1: 123\nop2: abc\n"
-    assert dump_parser.dump(cfg, format="yaml") == dump_parser.dump(cfg)
+    if pyyaml_available:
+        assert dump_parser.dump(cfg) == "op1: 123\nop2: abc\n"
+        assert dump_parser.dump(cfg, format="yaml") == dump_parser.dump(cfg)
     assert dump_parser.dump(cfg, format="json") == '{"op1":123,"op2":"abc"}'
     assert dump_parser.dump(cfg, format="json_indented") == '{\n  "op1": 123,\n  "op2": "abc"\n}\n'
     pytest.raises(ValueError, lambda: dump_parser.dump(cfg, format="invalid"))
 
 
 def test_dump_skip_default_simple(dump_parser):
-    assert "{}\n" == dump_parser.dump(dump_parser.get_defaults(), skip_default=True)
-    assert "op2: xyz\n" == dump_parser.dump(Namespace(op1=123, op2="xyz"), skip_default=True)
+    dump = dump_parser.dump(dump_parser.get_defaults(), skip_default=True)
+    expected = "{}\n" if pyyaml_available else "{}"
+    assert dump == expected
+    dump = dump_parser.dump(Namespace(op1=123, op2="xyz"), skip_default=True)
+    expected = "op2: xyz\n" if pyyaml_available else '{"op2":"xyz"}'
+    assert dump == expected
 
 
 def test_dump_skip_default_nested(parser):
@@ -481,11 +488,18 @@ def test_dump_skip_default_nested(parser):
     parser.add_argument("--g1.op2", type=str, default="abc")
     parser.add_argument("--g2.op1", type=int, default=987)
     parser.add_argument("--g2.op2", type=str, default="xyz")
-    assert parser.dump(parser.get_defaults(), skip_default=True) == "{}\n"
-    assert parser.dump(parser.parse_args(["--g1.op1=0"]), skip_default=True) == "g1:\n  op1: 0\n"
-    assert parser.dump(parser.parse_args(["--g2.op2=pqr"]), skip_default=True) == "g2:\n  op2: pqr\n"
-
-
+    dump = parser.dump(parser.get_defaults(), skip_default=True)
+    expected = "{}\n" if pyyaml_available else "{}"
+    assert dump == expected
+    dump = parser.dump(parser.parse_args(["--g1.op1=0"]), skip_default=True)
+    expected = "g1:\n  op1: 0\n" if pyyaml_available else '{"g1":{"op1":0}}'
+    assert dump == expected
+    dump = parser.dump(parser.parse_args(["--g2.op2=pqr"]), skip_default=True)
+    expected = "g2:\n  op2: pqr\n" if pyyaml_available else '{"g2":{"op2":"pqr"}}'
+    assert dump == expected
+
+
+@skip_if_no_pyyaml
 def test_dump_order(parser, subtests):
     args = {}
     for num in range(50):
@@ -532,7 +546,7 @@ def parser_schema_jsonnet(parser, example_parser):
             action=ActionJsonnet(ext_vars=None),
         )
     expected = parser.parse_args(["--subparser.bool=false", "--subparser.nums.val1=3"])
-    subparser_body = yaml.safe_dump(expected.subparser.as_dict())
+    subparser_body = json_or_yaml_dump(expected.subparser.as_dict())
     schema_body = json.dumps(expected.schema) if jsonschema_support else ""
     jsonnet_body = json.dumps(expected.jsonnet) if jsonnet_support else ""
     return parser, expected, subparser_body, schema_body, jsonnet_body
@@ -547,11 +561,12 @@ def test_parse_args_url_config(parser_schema_jsonnet):
     parser, expected, subparser_body, schema_body, jsonnet_body = parser_schema_jsonnet
 
     base_url = "http://jsonargparse.com/"
-    main_body = f"subparser: {base_url}subparser.yaml\n"
+    main_config = {"subparser": f"{base_url}subparser.yaml"}
     if jsonschema_support:
-        main_body += f"schema: {base_url}schema.yaml\n"
+        main_config["schema"] = f"{base_url}schema.yaml"
     if jsonnet_support:
-        main_body += f"jsonnet: {base_url}jsonnet.yaml\n"
+        main_config["jsonnet"] = f"{base_url}jsonnet.yaml"
+    main_body = json_or_yaml_dump(main_config)
 
     for name, body in [
         ("main.yaml", main_body),
@@ -588,12 +603,12 @@ def test_save_multifile(parser_schema_jsonnet, subtests, tmp_cwd):
     schema_file_out = out_dir / "schema.json"
     jsonnet_file_out = out_dir / "jsonnet.json"
 
-    main_body = "subparser: subparser.yaml\n"
+    main_config = {"subparser": "subparser.yaml"}
     if jsonschema_support:
-        main_body += "schema: schema.json\n"
+        main_config["schema"] = "schema.json"
     if jsonnet_support:
-        main_body += "jsonnet: jsonnet.json\n"
-    main_file_in.write_text(main_body)
+        main_config["jsonnet"] = "jsonnet.json"
+    main_file_in.write_text(json_or_yaml_dump(main_config))
     subparser_file_in.write_text(subparser_body)
     if jsonschema_support:
         schema_file_in.write_text(schema_body)
@@ -631,12 +646,12 @@ def test_save_multifile(parser_schema_jsonnet, subtests, tmp_cwd):
         assert expected == cfg3
 
     if jsonschema_support:
-        with subtests.test("save jsonschema yaml output"):
+        with subtests.test("save jsonschema output"):
             rm_out_files()
             schema_yaml_out = out_dir / "schema.yaml"
             cfg1.schema["__path__"] = Path_fc(schema_yaml_out)
             parser.save(cfg1, main_file_out, multifile=True)
-            assert schema_yaml_out.read_text() == "a: 1\nb: 2\n"
+            assert json_or_yaml_load(schema_yaml_out.read_text()) == {"a": 1, "b": 2}
 
 
 def test_save_overwrite(example_parser, tmp_cwd):
@@ -680,7 +695,8 @@ def test_save_path_content(parser, tmp_cwd):
     parser.save_path_content.add("the.path")
     parser.save(cfg, out_yaml)
 
-    assert out_yaml.read_text() == "the:\n  path: file.txt\n"
+    expected = "the:\n  path: file.txt\n" if pyyaml_available else '{"the":{"path":"file.txt"}}'
+    assert out_yaml.read_text() == expected
     assert out_file.read_text() == "file content"
 
 
@@ -710,12 +726,12 @@ def print_parser(parser, subparser):
 
 def test_print_config_normal(print_parser):
     out = get_parse_args_stdout(print_parser, ["--print_config"])
-    assert yaml.safe_load(out) == {"g1": {"v2": "2"}, "g2": {"v3": None}, "v1": 1}
+    assert json_or_yaml_load(out) == {"g1": {"v2": "2"}, "g2": {"v3": None}, "v1": 1}
 
 
 def test_print_config_skip_null(print_parser):
     out = get_parse_args_stdout(print_parser, ["--print_config=skip_null"])
-    assert yaml.safe_load(out) == {"g1": {"v2": "2"}, "g2": {}, "v1": 1}
+    assert json_or_yaml_load(out) == {"g1": {"v2": "2"}, "g2": {}, "v1": 1}
 
 
 @pytest.mark.skipif(not ruyaml_support, reason="ruyaml package is required")
@@ -738,7 +754,7 @@ def test_print_config_empty_default_config_file(print_parser, tmp_cwd):
     default_config_file.touch()
     print_parser.default_config_files = [default_config_file]
     out = get_parse_args_stdout(print_parser, ["--print_config"])
-    assert yaml.safe_load(out) == {"g1": {"v2": "2"}, "g2": {"v3": None}, "v1": 1}
+    assert json_or_yaml_load(out) == {"g1": {"v2": "2"}, "g2": {"v3": None}, "v1": 1}
 
 
 def test_print_config_reuse_name():
@@ -746,12 +762,12 @@ def test_print_config_reuse_name():
     parser.add_argument("--conf", action="config")
     parser.add_argument("--x", default=1)
     out = get_parse_args_stdout(parser, ["--print_conf"])
-    assert yaml.safe_load(out) == {"x": 1}
+    assert json_or_yaml_load(out) == {"x": 1}
 
 
 def test_default_config_files(parser, subtests, tmp_cwd):
     default_config_file = tmp_cwd / "defaults.yaml"
-    default_config_file.write_text("op1: from default config file\n")
+    default_config_file.write_text(json_or_yaml_dump({"op1": "from default config file"}))
 
     parser.default_config_files = [default_config_file]
     parser.add_argument("--op1", default="from parser default")
@@ -787,7 +803,7 @@ def test_default_config_file_help_message_no_existing(parser, tmp_cwd):
 
 def test_default_config_file_invalid_value(parser, tmp_cwd):
     default_config_file = Path("defaults.yaml")
-    default_config_file.write_text("op2: v2\n")
+    default_config_file.write_text(json_or_yaml_dump({"op2": "v2"}))
 
     parser.default_config_files = [default_config_file]
     parser.add_argument("--op1", default="from default")
@@ -804,12 +820,12 @@ def test_default_config_file_invalid_value(parser, tmp_cwd):
 @skip_if_running_as_root
 def test_default_config_file_unreadable(parser, tmp_cwd):
     default_config_file = Path("defaults.yaml")
-    default_config_file.write_text("op1: from yaml\n")
+    default_config_file.write_text(json_or_yaml_dump({"op1": "from config"}))
 
     parser.default_config_files = [default_config_file]
     parser.add_argument("--op1", default="from default")
 
-    assert parser.get_default("op1") == "from yaml"
+    assert parser.get_default("op1") == "from config"
     default_config_file.chmod(0)
     assert parser.get_default("op1") == "from default"
 
@@ -822,29 +838,29 @@ def test_default_config_files_pattern(parser, subtests, tmp_cwd):
 
     with subtests.test("one config"):
         config_1 = tmp_cwd / "defaults_1.yaml"
-        config_1.write_text("op1: from yaml 1\nop2: from yaml 1\n")
+        config_1.write_text(json_or_yaml_dump({"op1": "from config 1", "op2": "from config 1"}))
 
         cfg = parser.get_defaults()
-        assert cfg.op1 == "from yaml 1"
-        assert cfg.op2 == "from yaml 1"
+        assert cfg.op1 == "from config 1"
+        assert cfg.op2 == "from config 1"
         assert str(cfg.__default_config__) == str(config_1)
 
     with subtests.test("two configs"):
         config_2 = tmp_cwd / "defaults_2.yaml"
-        config_2.write_text("op1: from yaml 2\n")
+        config_2.write_text(json_or_yaml_dump({"op1": "from config 2"}))
 
         cfg = parser.get_defaults()
-        assert cfg.op1 == "from yaml 2"
-        assert cfg.op2 == "from yaml 1"
+        assert cfg.op1 == "from config 2"
+        assert cfg.op2 == "from config 1"
         assert list(map(str, cfg.__default_config__)) == list(map(str, [config_1, config_2]))
 
     with subtests.test("three configs"):
         config_0 = tmp_cwd / "defaults_0.yaml"
-        config_0.write_text("op2: from yaml 0\n")
+        config_0.write_text(json_or_yaml_dump({"op2": "from config 0"}))
 
         cfg = parser.get_defaults()
-        assert cfg.op1 == "from yaml 2"
-        assert cfg.op2 == "from yaml 1"
+        assert cfg.op1 == "from config 2"
+        assert cfg.op2 == "from config 1"
         assert list(map(str, cfg.__default_config__)) == list(map(str, [config_0, config_1, config_2]))
 
     with subtests.test("help"):
@@ -915,7 +931,7 @@ def test_set_get_default_suppress_required(parser):
 
 def test_set_get_default_suppress_default_config_file(parser, tmp_cwd):
     default_config_file = tmp_cwd / "defaults.yaml"
-    default_config_file.write_text("p3: 1.2\n")
+    default_config_file.write_text(json_or_yaml_dump({"p3": 1.2}))
 
     parser.default_config_files = [default_config_file]
     parser.add_argument("--p3", type=float, default=SUPPRESS)
diff --git a/jsonargparse_tests/test_dataclass_like.py b/jsonargparse_tests/test_dataclass_like.py
index d023957..51a990a 100644
--- a/jsonargparse_tests/test_dataclass_like.py
+++ b/jsonargparse_tests/test_dataclass_like.py
@@ -6,7 +6,6 @@ from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar, Union
 from unittest.mock import patch
 
 import pytest
-import yaml
 
 from jsonargparse import ArgumentError, ArgumentParser, Namespace, compose_dataclasses, lazy_instance
 from jsonargparse._namespace import NSKeyError
@@ -19,7 +18,11 @@ from jsonargparse._optionals import (
     typing_extensions_import,
 )
 from jsonargparse.typing import PositiveFloat, PositiveInt, final
-from jsonargparse_tests.conftest import get_parser_help, skip_if_docstring_parser_unavailable
+from jsonargparse_tests.conftest import (
+    get_parser_help,
+    json_or_yaml_load,
+    skip_if_docstring_parser_unavailable,
+)
 
 annotated = typing_extensions_import("Annotated")
 type_alias_type = typing_extensions_import("TypeAliasType")
@@ -65,7 +68,7 @@ def test_add_class_arguments(parser, subtests):
         cfg = parser.get_defaults()
         assert dataclasses.asdict(DataClassA()) == cfg["a"].as_dict()
         assert dataclasses.asdict(DataClassB()) == cfg["b"].as_dict()
-        dump = yaml.safe_load(parser.dump(cfg))
+        dump = json_or_yaml_load(parser.dump(cfg))
         assert dataclasses.asdict(DataClassA()) == dump["a"]
         assert dataclasses.asdict(DataClassB()) == dump["b"]
 
@@ -128,7 +131,7 @@ def test_add_class_with_dataclass_attributes(parser):
     cfg = parser.get_defaults()
     assert dataclasses.asdict(DataClassA()) == cfg.g.a1.as_dict()
     assert dataclasses.asdict(DataClassB()) == cfg.g.a2.as_dict()
-    dump = yaml.safe_load(parser.dump(cfg))
+    dump = json_or_yaml_load(parser.dump(cfg))
     assert dataclasses.asdict(DataClassA()) == dump["g"]["a1"]
     assert dataclasses.asdict(DataClassB()) == dump["g"]["a2"]
 
@@ -222,7 +225,7 @@ def test_add_argument_dataclass_unexpected_keys(parser):
         "class_path": f"{__name__}.DataClassB",
     }
     with pytest.raises(ArgumentError) as ctx:
-        parser.parse_args([f"--b={invalid}"])
+        parser.parse_args([f"--b={json.dumps(invalid)}"])
     ctx.match("Group 'b' does not accept nested key 'class_path'")
 
 
@@ -423,7 +426,7 @@ def test_optional_dataclass_type_instantiate():
 
 def test_optional_dataclass_type_dump():
     cfg = parser_optional_data.parse_args(['--data={"p1": "z"}'])
-    assert parser_optional_data.dump(cfg) == "data:\n  p1: z\n  p2: 0\n"
+    assert json_or_yaml_load(parser_optional_data.dump(cfg)) == {"data": {"p1": "z", "p2": 0}}
 
 
 def test_optional_dataclass_type_missing_required_field():
@@ -469,7 +472,7 @@ def test_optional_dataclass_single_param_change(parser):
     parser.add_argument("--config", action="config")
     parser.add_argument("--data", type=Optional[SingleParamChange])
     config = {"data": {"p1": 1}}
-    cfg = parser.parse_args([f"--config={config}", "--data.p2=2"])
+    cfg = parser.parse_args([f"--config={json.dumps(config)}", "--data.p2=2"])
     assert cfg.data == Namespace(p1=1, p2=2)
 
 
@@ -566,14 +569,14 @@ def test_class_path_union_mixture_dataclass_and_class(parser, union_type):
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.union, UnionData)
     assert dataclasses.asdict(init.union) == {"data_a": 2, "data_b": "x"}
-    assert yaml.safe_load(parser.dump(cfg))["union"] == value["init_args"]
+    assert json_or_yaml_load(parser.dump(cfg))["union"] == value["init_args"]
 
     value = {"class_path": f"{__name__}.UnionClass", "init_args": {"prm_1": 1.2, "prm_2": False}}
     cfg = parser.parse_args([f"--union={json.dumps(value)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.union, UnionClass)
     assert init.union.prm_1 == 1.2
-    assert yaml.safe_load(parser.dump(cfg))["union"] == value
+    assert json_or_yaml_load(parser.dump(cfg))["union"] == value
 
 
 # final classes tests
@@ -852,7 +855,7 @@ class TestPydantic:
         parser.add_argument("--model", type=Model)
         cfg = parser.parse_args([f"--model.param={valid_value}"])
         assert cast(cfg.model.param) == valid_value
-        dump = yaml.safe_load(parser.dump(cfg))
+        dump = json_or_yaml_load(parser.dump(cfg))
         assert dump == {"model": {"param": valid_value}}
         with pytest.raises(ArgumentError) as ctx:
             parser.parse_args([f"--model.param={invalid_value}"])
@@ -907,7 +910,7 @@ class TestPydantic:
                 "init_args": {"a": {"p1": "x"}},
             }
         }
-        cfg = parser.parse_args([f"--cfg={config}"])
+        cfg = parser.parse_args([f"--cfg={json.dumps(config)}"])
         assert cfg.b.class_path == f"{__name__}.OptionalPydantic"
         assert cfg.b.init_args == Namespace(a=Namespace(p1="x", p2=3))
 
diff --git a/jsonargparse_tests/test_deprecated.py b/jsonargparse_tests/test_deprecated.py
index da522cd..4f85060 100644
--- a/jsonargparse_tests/test_deprecated.py
+++ b/jsonargparse_tests/test_deprecated.py
@@ -11,7 +11,6 @@ from io import StringIO
 from warnings import catch_warnings
 
 import pytest
-import yaml
 
 from jsonargparse import (
     CLI,
@@ -34,7 +33,7 @@ from jsonargparse._deprecated import (
     shown_deprecation_warnings,
     usage_and_exit_error_handler,
 )
-from jsonargparse._optionals import docstring_parser_support, jsonnet_support, url_support
+from jsonargparse._optionals import docstring_parser_support, jsonnet_support, pyyaml_available, url_support
 from jsonargparse._util import argument_error
 from jsonargparse_tests.conftest import (
     get_parser_help,
@@ -46,6 +45,12 @@ from jsonargparse_tests.test_dataclass_like import DataClassA
 from jsonargparse_tests.test_jsonnet import example_2_jsonnet
 
 
+@pytest.fixture(autouse=True)
+def no_pyyaml_skip():
+    if not pyyaml_available:
+        pytest.skip("pyyaml package is required")
+
+
 @pytest.fixture(autouse=True)
 def clear_shown_deprecation_warnings():
     yield
@@ -592,7 +597,7 @@ def test_add_dataclass_arguments(parser, subtests):
     with subtests.test("get_defaults"):
         cfg = parser.get_defaults()
         assert dataclasses.asdict(DataClassA()) == cfg["a"].as_dict()
-        dump = yaml.safe_load(parser.dump(cfg))
+        dump = __import__("yaml").safe_load(parser.dump(cfg))
         assert dataclasses.asdict(DataClassA()) == dump["a"]
 
     with subtests.test("instantiate_classes"):
diff --git a/jsonargparse_tests/test_formatters.py b/jsonargparse_tests/test_formatters.py
index df72975..aec775f 100644
--- a/jsonargparse_tests/test_formatters.py
+++ b/jsonargparse_tests/test_formatters.py
@@ -8,7 +8,7 @@ from unittest.mock import patch
 import pytest
 
 from jsonargparse import ActionParser, ActionYesNo, ArgumentParser
-from jsonargparse_tests.conftest import get_parser_help
+from jsonargparse_tests.conftest import get_parser_help, json_or_yaml_dump
 
 
 @pytest.fixture
@@ -85,7 +85,7 @@ def test_help_action_yes_no(parser):
 def default_config_files(tmp_cwd) -> Tuple[ArgumentParser, str, Path]:
     not_exist = "does_not_exist.yaml"
     exists = Path("config.yaml")
-    exists.write_text("v1: from yaml v1\nn1.v2: from yaml v2\n")
+    exists.write_text(json_or_yaml_dump({"v1": "from yaml v1", "n1.v2": "from yaml v2"}))
 
     parser = ArgumentParser(default_config_files=[not_exist, exists])
     parser.add_argument("--v1", default="from default v1")
@@ -122,7 +122,7 @@ def test_help_default_config_files_none(default_config_files):
 
 def test_help_default_config_files_with_required(tmp_path, parser):
     config_path = tmp_path / "config.yaml"
-    config_path.write_text("v1: from yaml\n")
+    config_path.write_text(json_or_yaml_dump({"v1": "from config"}))
 
     parser.default_config_files = [config_path]
     parser.add_argument("req", help="req description")
@@ -130,7 +130,7 @@ def test_help_default_config_files_with_required(tmp_path, parser):
 
     help_str = get_parser_help(parser)
     assert "req description" in help_str
-    assert "from yaml" in help_str
+    assert "from config" in help_str
 
 
 def test_format_usage(parser):
diff --git a/jsonargparse_tests/test_jsonnet.py b/jsonargparse_tests/test_jsonnet.py
index add7a4a..a3c1449 100644
--- a/jsonargparse_tests/test_jsonnet.py
+++ b/jsonargparse_tests/test_jsonnet.py
@@ -5,7 +5,6 @@ import re
 from pathlib import Path
 
 import pytest
-import yaml
 
 from jsonargparse import (
     ActionJsonnet,
@@ -14,8 +13,12 @@ from jsonargparse import (
     ArgumentParser,
     strip_meta,
 )
-from jsonargparse._optionals import jsonnet_support
-from jsonargparse_tests.conftest import get_parser_help, skip_if_jsonschema_unavailable
+from jsonargparse._optionals import jsonnet_support, pyyaml_available
+from jsonargparse_tests.conftest import (
+    get_parser_help,
+    json_or_yaml_load,
+    skip_if_jsonschema_unavailable,
+)
 
 
 @pytest.fixture(autouse=True)
@@ -161,36 +164,41 @@ def test_action_jsonnet_save_config_metadata(parser, tmp_path):
 
     jsonnet_file = tmp_path / "example.jsonnet"
     jsonnet_file.write_text(example_2_jsonnet)
-    output_yaml = tmp_path / "output" / "main.yaml"
+    output_config = tmp_path / "output" / "main.yaml"
     output_jsonnet = tmp_path / "output" / "example.jsonnet"
     (tmp_path / "output").mkdir()
 
     # save the config with metadata and verify it is saved as two files
     cfg = parser.parse_args(["--ext_vars", '{"param": 123}', f"--jsonnet={jsonnet_file}"])
     assert str(cfg.jsonnet["__path__"]) == str(jsonnet_file)
-    parser.save(cfg, output_yaml)
-    assert output_yaml.is_file()
+    parser.save(cfg, output_config)
+    assert output_config.is_file()
     assert output_jsonnet.is_file()
 
     # rewrite the config to make sure that ext_vars is after jsonnet
-    main_cfg = yaml.safe_load(output_yaml.read_text())
+    main_cfg = json_or_yaml_load(output_config.read_text())
     main_cfg = {k: main_cfg[k] for k in ["jsonnet", "ext_vars"]}
-    output_yaml.write_text(yaml.safe_dump(main_cfg, sort_keys=False))
+    if pyyaml_available:
+        import yaml
+
+        output_config.write_text(yaml.safe_dump(main_cfg, sort_keys=False))
+    else:
+        output_config.write_text(json.dumps(main_cfg))
 
     # parse using saved config and verify result is the same
-    cfg2 = parser.parse_args([f"--cfg={output_yaml}"])
+    cfg2 = parser.parse_args([f"--cfg={output_config}"])
     cfg2.cfg = None
     assert strip_meta(cfg) == strip_meta(cfg2)
 
     # save the config without metadata and verify it is saved as a single file
-    output_yaml.unlink()
+    output_config.unlink()
     output_jsonnet.unlink()
-    parser.save(strip_meta(cfg), output_yaml)
-    assert output_yaml.is_file()
+    parser.save(strip_meta(cfg), output_config)
+    assert output_config.is_file()
     assert not output_jsonnet.is_file()
 
     # parse using saved config and verify result is the same
-    cfg3 = parser.parse_args([f"--cfg={output_yaml}"])
+    cfg3 = parser.parse_args([f"--cfg={output_config}"])
     cfg3.cfg = None
     assert strip_meta(cfg) == strip_meta(cfg3)
 
@@ -259,7 +267,7 @@ def test_action_jsonnet_schema_dict_or_str():
 @skip_if_jsonschema_unavailable
 def test_action_jsonnet_init_failures():
     pytest.raises(ValueError, lambda: ActionJsonnet(ext_vars=2))
-    pytest.raises(ValueError, lambda: ActionJsonnet(schema="." + json.dumps(example_schema)))
     from jsonschema.exceptions import SchemaError
 
+    pytest.raises((ValueError, SchemaError), lambda: ActionJsonnet(schema="." + json.dumps(example_schema)))
     pytest.raises(SchemaError, lambda: ActionJsonnet(schema="."))
diff --git a/jsonargparse_tests/test_jsonschema.py b/jsonargparse_tests/test_jsonschema.py
index a923d32..2e2862e 100644
--- a/jsonargparse_tests/test_jsonschema.py
+++ b/jsonargparse_tests/test_jsonschema.py
@@ -7,7 +7,7 @@ from importlib.util import find_spec
 import pytest
 
 from jsonargparse import ActionJsonSchema, ArgumentError
-from jsonargparse_tests.conftest import get_parser_help
+from jsonargparse_tests.conftest import get_parser_help, json_or_yaml_dump
 
 
 @pytest.fixture(autouse=True)
@@ -54,7 +54,7 @@ def test_schema_array_parse_args(parser):
 
 @pytest.mark.usefixtures("parser_schema_array")
 def test_schema_array_parse_string(parser):
-    cfg = parser.parse_string("op1: [3, 7]")
+    cfg = parser.parse_string(json_or_yaml_dump({"op1": [3, 7]}))
     assert [3, 7] == cfg["op1"]
 
 
@@ -91,7 +91,7 @@ def parser_schema_object(parser):
 @pytest.mark.usefixtures("parser_schema_object")
 def test_schema_object_parse_args(parser):
     op2_val = {"k1": "one", "k2": 2, "k3": 3.3}
-    assert op2_val == parser.parse_args(["--op2", str(op2_val)]).op2
+    assert op2_val == parser.parse_args(["--op2", json_or_yaml_dump(op2_val)]).op2
     assert 17 == parser.parse_args(["--op2", '{"k2": 2}']).op2["k3"]
     pytest.raises(ArgumentError, lambda: parser.parse_args(["--op2", '{"k1": 1}']))
     pytest.raises(ArgumentError, lambda: parser.parse_args(["--op2", '{"k2": "2"}']))
@@ -101,7 +101,7 @@ def test_schema_object_parse_args(parser):
 @pytest.mark.usefixtures("parser_schema_object")
 def test_schema_object_parse_string(parser):
     op2_val = {"k1": "two", "k2": 7, "k3": 2.4}
-    cfg = parser.parse_string(f"op2:\n  {op2_val}\n")
+    cfg = parser.parse_string(json_or_yaml_dump({"op2": op2_val}))
     assert op2_val == cfg["op2"]
 
 
@@ -109,7 +109,7 @@ def test_schema_object_parse_string(parser):
 def test_schema_object_parse_config(parser, tmp_path):
     op2_val = {"k1": "three", "k2": -3, "k3": 0.4}
     path = tmp_path / "op2.json"
-    path.write_text(f"op2:\n  {op2_val}\n")
+    path.write_text(json_or_yaml_dump({"op2": op2_val}))
     cfg = parser.parse_args([f"--cfg={path}"])
     assert op2_val == cfg["op2"]
 
@@ -156,7 +156,7 @@ def test_action_jsonschema_schema_dict_or_str():
 
 def test_action_jsonschema_init_failures():
     pytest.raises(ValueError, ActionJsonSchema)
-    pytest.raises(ValueError, lambda: ActionJsonSchema(schema=":"))
     from jsonschema.exceptions import SchemaError
 
+    pytest.raises((ValueError, SchemaError), lambda: ActionJsonSchema(schema=":"))
     pytest.raises(SchemaError, lambda: ActionJsonSchema(schema="."))
diff --git a/jsonargparse_tests/test_link_arguments.py b/jsonargparse_tests/test_link_arguments.py
index 71e8539..2b34154 100644
--- a/jsonargparse_tests/test_link_arguments.py
+++ b/jsonargparse_tests/test_link_arguments.py
@@ -1,12 +1,12 @@
 from __future__ import annotations
 
+import json
 from calendar import Calendar, TextCalendar
 from dataclasses import dataclass
 from importlib.util import find_spec
 from typing import Any, Callable, List, Mapping, Optional, Union
 
 import pytest
-import yaml
 
 from jsonargparse import (
     ArgumentError,
@@ -15,7 +15,7 @@ from jsonargparse import (
     lazy_instance,
 )
 from jsonargparse._optionals import docstring_parser_support
-from jsonargparse_tests.conftest import get_parse_args_stdout, get_parser_help
+from jsonargparse_tests.conftest import get_parse_args_stdout, get_parser_help, json_or_yaml_dump, json_or_yaml_load
 
 # tests for links applied on parse
 
@@ -34,7 +34,7 @@ def test_on_parse_shallow_print_config(parser):
     parser.add_argument("--b", type=str)
     parser.link_arguments("a", "b")
     out = get_parse_args_stdout(parser, ["--print_config"])
-    assert yaml.safe_load(out) == {"a": 0}
+    assert json_or_yaml_load(out) == {"a": 0}
 
 
 def test_on_parse_subcommand_failing_compute_fn(parser, subparser, subtests):
@@ -57,7 +57,7 @@ def test_on_parse_subcommand_failing_compute_fn(parser, subparser, subtests):
 
     with subtests.test("print_config"):
         out = get_parse_args_stdout(parser, ["sub", "--print_config"])
-        assert yaml.safe_load(out) == {"a": 0}
+        assert json_or_yaml_load(out) == {"a": 0}
 
 
 def test_on_parse_compute_fn_single_arguments(parser, subtests):
@@ -74,11 +74,11 @@ def test_on_parse_compute_fn_single_arguments(parser, subtests):
         assert cfg.b.v2 == cfg.a.v1 * cfg.a.v2
 
     with subtests.test("dump removal of target"):
-        dump = yaml.safe_load(parser.dump(cfg))
+        dump = json_or_yaml_load(parser.dump(cfg))
         assert dump == {"a": {"v1": 2, "v2": -5}}
 
     with subtests.test("dump keep target"):
-        dump = yaml.safe_load(parser.dump(cfg, skip_link_targets=False))
+        dump = json_or_yaml_load(parser.dump(cfg, skip_link_targets=False))
         assert dump == {"a": {"v1": 2, "v2": -5}, "b": {"v2": -10}}
 
     with subtests.test("invalid compute_fn result type"):
@@ -156,12 +156,12 @@ def test_on_parse_add_class_arguments(subtests):
         cfg = parser.parse_args(["--a.v1=11", "--a.v2=7"])
         assert 7 == cfg.b.v1
         assert 11 + 7 == cfg.b.v2
-        dump = yaml.safe_load(parser.dump(cfg))
+        dump = json_or_yaml_load(parser.dump(cfg))
         assert dump == {"a": {"v1": 11, "v2": 7}, "b": {"v3": 2}}
 
     with subtests.test("dump keep targets"):
         cfg = parser.parse_args(["--a.v1=11", "--a.v2=7"])
-        dump = yaml.safe_load(parser.dump(cfg, skip_link_targets=False))
+        dump = json_or_yaml_load(parser.dump(cfg, skip_link_targets=False))
         assert dump == {"a": {"v1": 11, "v2": 7}, "b": {"v3": 2, "v1": 7, "v2": 18}}
 
     with subtests.test("argument error"):
@@ -196,17 +196,17 @@ def test_on_parse_add_subclass_arguments(parser, subtests):
     }
 
     with subtests.test("compute_fn result"):
-        cfg = parser.parse_args([f"--s1={s1_value}", f"--s2={__name__}.ClassS2"])
+        cfg = parser.parse_args([f"--s1={json.dumps(s1_value)}", f"--s2={__name__}.ClassS2"])
         assert cfg.s2.init_args.v3 == 4
         assert cfg.s2.init_args.v3 == cfg.s1.init_args.v1 + cfg.s1.init_args.v2
 
     with subtests.test("dump removal of target"):
-        cfg = parser.parse_args([f"--s1={s1_value}", f"--s2={__name__}.ClassS2"])
-        dump = yaml.safe_load(parser.dump(cfg))
+        cfg = parser.parse_args([f"--s1={json.dumps(s1_value)}", f"--s2={__name__}.ClassS2"])
+        dump = json_or_yaml_load(parser.dump(cfg))
         assert dump["s2"] == {"class_path": f"{__name__}.ClassS2"}
 
     with subtests.test("dump keep target"):
-        dump = yaml.safe_load(parser.dump(cfg, skip_link_targets=False))
+        dump = json_or_yaml_load(parser.dump(cfg, skip_link_targets=False))
         assert dump["s2"] == {"class_path": f"{__name__}.ClassS2", "init_args": {"v3": 4}}
 
     with subtests.test("compute_fn invalid result type"):
@@ -253,11 +253,11 @@ def test_on_parse_subclass_target_in_list(parser):
     parser.link_arguments("trainer.save_dir", "trainer.logger.init_args.save_dir")
     cfg = parser.parse_args([])
     assert cfg.trainer == Namespace(logger=[], save_dir=None)
-    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger]"])
+    cfg = parser.parse_args(["--trainer.save_dir=logs", '--trainer.logger=["Logger"]'])
     assert cfg.trainer.save_dir == "logs"
     assert len(cfg.trainer.logger) == 1
     assert cfg.trainer.logger[0].init_args == Namespace(save_dir="logs")
-    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger, Logger]"])
+    cfg = parser.parse_args(["--trainer.save_dir=logs", '--trainer.logger=["Logger", "Logger"]'])
     assert len(cfg.trainer.logger) == 2
     assert all(x.init_args == Namespace(save_dir="logs") for x in cfg.trainer.logger)
 
@@ -279,7 +279,7 @@ def test_on_parse_subclass_target_in_union_list(parser):
     cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=Logger"])
     assert cfg.trainer.save_dir == "logs"
     assert cfg.trainer.logger.init_args == Namespace(save_dir="logs")
-    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger, Logger]"])
+    cfg = parser.parse_args(["--trainer.save_dir=logs", '--trainer.logger=["Logger", "Logger"]'])
     assert len(cfg.trainer.logger) == 2
     assert all(x.init_args == Namespace(save_dir="logs") for x in cfg.trainer.logger)
 
@@ -301,7 +301,7 @@ def test_on_parse_subclass_target_in_optional_list(parser):
     cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger+=Logger"])
     assert cfg.trainer.save_dir == "logs"
     assert cfg.trainer.logger[0].init_args == Namespace(save_dir="logs")
-    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger, Logger]"])
+    cfg = parser.parse_args(["--trainer.save_dir=logs", '--trainer.logger=["Logger", "Logger"]'])
     assert len(cfg.trainer.logger) == 2
     assert all(x.init_args == Namespace(save_dir="logs") for x in cfg.trainer.logger)
 
@@ -329,7 +329,7 @@ def test_on_parse_add_subclass_arguments_with_instantiate_false(parser, subtests
     }
 
     with subtests.test("parse_args"):
-        cfg = parser.parse_args([f"--f={f_value}", f"--c={c_value}"])
+        cfg = parser.parse_args([f"--f={json.dumps(f_value)}", f"--c={json.dumps(c_value)}"])
         assert cfg.c.as_dict() == {
             "class_path": "calendar.Calendar",
             "init_args": {"firstweekday": 3},
@@ -344,11 +344,11 @@ def test_on_parse_add_subclass_arguments_with_instantiate_false(parser, subtests
         assert init.f.c.firstweekday == 3
 
     with subtests.test("dump removal of target"):
-        dump = yaml.safe_load(parser.dump(cfg))
+        dump = json_or_yaml_load(parser.dump(cfg))
         assert "c" not in dump["f"]["init_args"]
 
     with subtests.test("dump keep target"):
-        dump = yaml.safe_load(parser.dump(cfg, skip_link_targets=False))
+        dump = json_or_yaml_load(parser.dump(cfg, skip_link_targets=False))
         assert dump["f"]["init_args"]["c"] == {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 3}}
 
 
@@ -380,7 +380,7 @@ def test_on_parse_add_subclass_arguments_compute_fn_return_dict(parser):
         },
     }
 
-    cfg = parser.parse_args([f"--d={d_value}", f"--c={c_value}"])
+    cfg = parser.parse_args([f"--d={json.dumps(d_value)}", f"--c={json.dumps(c_value)}"])
     assert cfg.d.init_args.a1 == c_value
     assert cfg.d.init_args.a2 == c_value
 
@@ -438,7 +438,7 @@ def test_on_parse_save_required_target_subclass_param(parser, tmp_cwd):
     parser.link_arguments("a.a", "c.init_args.b.a")
     cfg = parser.parse_args(["--a.a=1", f"--c={__name__}.RequiredTargetC"])
     parser.save(cfg, "config.yaml")
-    saved = yaml.safe_load((tmp_cwd / "config.yaml").read_text())
+    saved = json_or_yaml_load((tmp_cwd / "config.yaml").read_text())
     assert saved == {"a": {"a": 1}, "c": {"class_path": f"{__name__}.RequiredTargetC", "init_args": {}}}
 
 
@@ -473,7 +473,7 @@ def test_on_parse_type_skip_link_targets_dump(parser):
     parser.link_arguments("t1", "t2")
 
     cfg = parser.parse_args([f"--t1={__name__}.Model"])
-    dump = yaml.safe_load(parser.dump(cfg, skip_link_targets=False))
+    dump = json_or_yaml_load(parser.dump(cfg, skip_link_targets=False))
     assert dump["t2"] == f"{__name__}.Model"
 
 
@@ -653,7 +653,7 @@ def test_on_parse_link_entire_subclass(parser):
     parser.link_arguments("n.calendar", "q.calendar", apply_on="parse")
 
     cal = {"class_path": "Calendar", "init_args": {"firstweekday": 4}}
-    cfg = parser.parse_args([f"--n.calendar={cal}", "--q.q2=7"])
+    cfg = parser.parse_args([f"--n.calendar={json.dumps(cal)}", "--q.q2=7"])
     assert cfg.n.calendar == cfg.q.calendar
     assert cfg.q.q2 == 7
 
@@ -782,7 +782,7 @@ def test_on_instantiate_within_deep_subclass(parser, caplog):
         apply_on="instantiate",
     )
 
-    cfg = parser.parse_args([f"--cfg={within_deep_config}"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(within_deep_config)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.model, WithinDeepModel)
     assert isinstance(init.model.encoder, WithinDeepSource)
@@ -816,7 +816,7 @@ def test_on_instantiate_within_deeper_subclass(parser, caplog):
         apply_on="instantiate",
     )
 
-    cfg = parser.parse_args([f"--cfg={within_deeper_config}"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(within_deeper_config)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.system, WithinDeeperSystem)
     assert isinstance(init.system.model, WithinDeepModel)
@@ -1013,7 +1013,7 @@ def test_on_instantiate_linking_deep_targets(parser, tmp_path):
         "c": {},
     }
     config_path = tmp_path / "config.yaml"
-    config_path.write_text(yaml.safe_dump(config))
+    config_path.write_text(json_or_yaml_dump(config))
 
     parser.add_argument("--config", action="config")
     parser.add_subclass_arguments(DeepBSuper, nested_key="b", required=True)
@@ -1045,7 +1045,7 @@ def test_on_instantiate_linking_deep_targets_mapping(parser, tmp_path):
         "c": {},
     }
     config_path = tmp_path / "config.yaml"
-    config_path.write_text(yaml.safe_dump(config))
+    config_path.write_text(json_or_yaml_dump(config))
 
     parser.add_argument("--config", action="config")
     parser.add_subclass_arguments(DeepBSuper, nested_key="b", required=True)
diff --git a/jsonargparse_tests/test_loaders_dumpers.py b/jsonargparse_tests/test_loaders_dumpers.py
index 32d1dbd..e207500 100644
--- a/jsonargparse_tests/test_loaders_dumpers.py
+++ b/jsonargparse_tests/test_loaders_dumpers.py
@@ -1,19 +1,24 @@
 from __future__ import annotations
 
+import json
 import os
 from dataclasses import dataclass
 from typing import List
 from unittest.mock import patch
 
 import pytest
-import yaml
 
 from jsonargparse import ArgumentParser, get_loader, set_dumper, set_loader
 from jsonargparse._common import parser_context
 from jsonargparse._loaders_dumpers import load_value, loaders, yaml_dump
-from jsonargparse._optionals import omegaconf_support
+from jsonargparse._optionals import omegaconf_support, pyyaml_available
+from jsonargparse_tests.conftest import json_or_yaml_dump, json_or_yaml_load, skip_if_no_pyyaml
 
+if pyyaml_available:
+    import yaml
 
+
+@skip_if_no_pyyaml
 def test_set_dumper_custom_yaml(parser):
     parser.add_argument("--list", type=List[int])
 
@@ -99,12 +104,13 @@ def test_set_loader_parser_mode_subparsers(parser, subparser):
     subcommands.add_subcommand("sub", subparser)
 
     with patch.dict("jsonargparse._loaders_dumpers.loaders"):
-        set_loader("custom", yaml.safe_load)
+        set_loader("custom", yaml.safe_load if pyyaml_available else json.loads)
         parser.parser_mode = "custom"
         assert "custom" == parser.parser_mode
         assert "custom" == subparser.parser_mode
 
 
+@skip_if_no_pyyaml
 def test_dump_header_yaml(parser):
     parser.add_argument("--int", type=int, default=1)
     parser.dump_header = ["line 1", "line 2"]
@@ -141,7 +147,10 @@ class CustomContainer:
 
 
 def custom_loader(data):
-    data = yaml.safe_load(data)
+    if pyyaml_available:
+        data = yaml.safe_load(data)
+    else:
+        data = json.loads(data)
     if isinstance(data, dict) and "fn" in data:
         data["fn"] = {k: custom_loader for k in data["fn"]}
     return data
@@ -150,7 +159,7 @@ def custom_loader(data):
 def custom_dumper(data):
     if "data" in data and "fn" in data["data"]:
         data["data"]["fn"] = {k: "dumped" for k in data["data"]["fn"]}
-    return yaml_dump(data)
+    return json_or_yaml_dump(data)
 
 
 def test_nested_parser_mode(parser):
@@ -160,7 +169,7 @@ def test_nested_parser_mode(parser):
     parser.add_argument("--custom", type=CustomContainer)
     cfg = parser.parse_args(['--custom.data={"fn": {"key": "value"}}'])
     assert cfg.custom.init_args.data["fn"]["key"] is custom_loader
-    dump = yaml.safe_load(parser.dump(cfg))
+    dump = json_or_yaml_load(parser.dump(cfg))
     assert dump["custom"]["init_args"]["data"] == {"fn": {"key": "dumped"}}
 
 
diff --git a/jsonargparse_tests/test_paths.py b/jsonargparse_tests/test_paths.py
index 614267f..e191552 100644
--- a/jsonargparse_tests/test_paths.py
+++ b/jsonargparse_tests/test_paths.py
@@ -10,7 +10,7 @@ import pytest
 
 from jsonargparse import ArgumentError, Namespace
 from jsonargparse.typing import Path_drw, Path_fc, Path_fr, path_type
-from jsonargparse_tests.conftest import get_parser_help
+from jsonargparse_tests.conftest import get_parser_help, json_or_yaml_dump, json_or_yaml_load
 
 # stdlib path types tests
 
@@ -20,7 +20,7 @@ def test_pathlib_path(parser, file_r):
     cfg = parser.parse_args([f"--path={file_r}"])
     assert isinstance(cfg.path, Path)
     assert str(cfg.path) == file_r
-    assert parser.dump(cfg) == "path: file_r\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"path": "file_r"}
 
 
 def test_os_pathlike(parser, file_r):
@@ -55,7 +55,7 @@ def test_paths_config_relative_absolute(parser, tmp_cwd):
     (tmp_cwd / "example").mkdir()
     rel_yaml_file = Path("..", "example", "example.yaml")
     abs_yaml_file = (tmp_cwd / "example" / rel_yaml_file).resolve()
-    abs_yaml_file.write_text(f"file: {rel_yaml_file}\ndir: {tmp_cwd}\n")
+    abs_yaml_file.write_text(json_or_yaml_dump({"file": str(rel_yaml_file), "dir": str(tmp_cwd)}))
 
     cfg = parser.parse_args([f"--cfg={abs_yaml_file}"])
     assert os.path.realpath(tmp_cwd) == os.path.realpath(cfg.dir)
@@ -100,21 +100,21 @@ def test_register_path_dcc_default_path(parser, tmp_cwd):
     path_dcc = path_type("dcc")
     parser.add_argument("--path", type=path_dcc, default=path_dcc("test"))
     cfg = parser.parse_args([])
-    assert "path: test\n" == parser.dump(cfg)
+    assert {"path": "test"} == json_or_yaml_load(parser.dump(cfg))
     help_str = get_parser_help(parser)
     assert "(type: Path_dcc, default: test)" in help_str
 
 
 def test_path_dump(parser, tmp_cwd):
     parser.add_argument("--path", type=Path_fc)
-    cfg = parser.parse_string("path: path")
-    assert parser.dump(cfg) == "path: path\n"
+    cfg = parser.parse_string(json_or_yaml_dump({"path": "path"}))
+    assert json_or_yaml_load(parser.dump(cfg)) == {"path": "path"}
 
 
 def test_paths_dump(parser, tmp_cwd):
     parser.add_argument("--paths", nargs="+", type=Path_fc)
     cfg = parser.parse_args(["--paths", "path1", "path2"])
-    assert parser.dump(cfg) == "paths:\n- path1\n- path2\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"paths": ["path1", "path2"]}
 
 
 # enable_path tests
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index fbb2a3d..47fe856 100644
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -1,11 +1,11 @@
 from __future__ import annotations
 
+import json
 from pathlib import Path
 from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar, Union
 from unittest.mock import patch
 
 import pytest
-import yaml
 
 from jsonargparse import (
     ArgumentError,
@@ -19,6 +19,8 @@ from jsonargparse_tests.conftest import (
     capture_logs,
     get_parse_args_stdout,
     get_parser_help,
+    json_or_yaml_dump,
+    json_or_yaml_load,
     skip_if_docstring_parser_unavailable,
 )
 
@@ -191,7 +193,7 @@ def test_add_class_without_parameters(parser):
 
     config = {"no_params": {"class_path": f"{__name__}.NoParams"}}
     with pytest.raises(ArgumentError) as ctx:
-        parser.parse_args([f"--cfg={config}"])
+        parser.parse_args([f"--cfg={json.dumps(config)}"])
     ctx.match("Group 'no_params' does not accept nested key 'class_path'")
 
 
@@ -257,7 +259,7 @@ def test_add_class_with_required_parameters(parser):
         pytest.raises(ArgumentError, lambda: parser.parse_args(args))
 
     out = get_parse_args_stdout(parser, ["--model.m=0.1", "--print_config"])
-    assert "  n: null" in out
+    assert json_or_yaml_load(out) == {"model": {"n": None, "m": 0.1}}
 
     cfg = parser.parse_args([f"--config={out}", "--model.n=3"])
     assert cfg.model == Namespace(m=0.1, n=3)
@@ -340,7 +342,7 @@ def test_add_class_in_subcommand(parser, subparser):
 def test_add_class_group_config(parser, tmp_cwd):
     parser.add_class_arguments(Class0, "a")
     path = Path("a.yaml")
-    path.write_text("c0_a0: x")
+    path.write_text(json_or_yaml_dump({"c0_a0": "x"}))
     cfg = parser.parse_args(["--a=a.yaml"])
     assert str(cfg.a.pop("__path__")) == str(path)
     assert cfg.a == Namespace(c0_a0="x")
@@ -418,7 +420,7 @@ def test_add_class_unmatched_default_type(parser):
     init = parser.instantiate_classes(cfg)
     assert init.cls.p2 == "deprecated"
     dump = parser.dump(cfg)
-    assert dump == "cls:\n  p1: x\n  p2: deprecated\n"
+    assert json_or_yaml_load(dump) == {"cls": {"p1": "x", "p2": "deprecated"}}
 
 
 # add_method_arguments tests
@@ -601,7 +603,7 @@ def test_add_function_group_config(parser, tmp_cwd):
     parser.add_function_arguments(func, "func")
 
     cfg_path = Path("config.yaml")
-    cfg_path.write_text(yaml.dump({"a1": "one", "a3": True}))
+    cfg_path.write_text(json_or_yaml_dump({"a1": "one", "a3": True}))
 
     cfg = parser.parse_args([f"--func={cfg_path}"])
     assert cfg.func == Namespace(a1="one", a2=2.0, a3=True)
@@ -621,8 +623,8 @@ def test_add_function_group_config_within_config(parser, tmp_cwd):
     cfg_path = Path("subdir", "config.yaml")
     subcfg_path = Path("subsubdir", "func_config.yaml")
     Path("subdir", "subsubdir").mkdir(parents=True)
-    cfg_path.write_text(f"func: {subcfg_path}\n")
-    (cfg_path.parent / subcfg_path).write_text(yaml.dump({"a1": "one", "a3": True}))
+    cfg_path.write_text(json_or_yaml_dump({"func": str(subcfg_path)}))
+    (cfg_path.parent / subcfg_path).write_text(json_or_yaml_dump({"a1": "one", "a3": True}))
 
     cfg = parser.parse_args([f"--cfg={cfg_path}"])
     assert str(cfg.func.__path__) == str(subcfg_path)
diff --git a/jsonargparse_tests/test_stubs_resolver.py b/jsonargparse_tests/test_stubs_resolver.py
index b9a81d2..9cd1bbb 100644
--- a/jsonargparse_tests/test_stubs_resolver.py
+++ b/jsonargparse_tests/test_stubs_resolver.py
@@ -14,13 +14,13 @@ from unittest.mock import patch
 from uuid import UUID, uuid5
 
 import pytest
-import yaml
 
 from jsonargparse._parameter_resolvers import get_signature_parameters as get_params
 from jsonargparse._stubs_resolver import get_mro_method_parent, get_stubs_resolver
 from jsonargparse_tests.conftest import (
     capture_logs,
     get_parser_help,
+    skip_if_no_pyyaml,
     skip_if_requests_unavailable,
 )
 
@@ -176,7 +176,10 @@ def test_get_params_function():
     assert [("address", inspect._empty), ("strict", inspect._empty)] == get_param_types(params)
 
 
+@skip_if_no_pyyaml
 def test_get_params_relative_import_from_init():
+    import yaml
+
     params = get_params(yaml.safe_load)
     assert ["stream"] == get_param_names(params)
     assert params[0].annotation is not inspect._empty
diff --git a/jsonargparse_tests/test_subclasses.py b/jsonargparse_tests/test_subclasses.py
index e5a8c87..771d5de 100644
--- a/jsonargparse_tests/test_subclasses.py
+++ b/jsonargparse_tests/test_subclasses.py
@@ -15,7 +15,6 @@ from unittest.mock import patch
 from uuid import NAMESPACE_OID
 
 import pytest
-import yaml
 
 from jsonargparse import (
     ArgumentError,
@@ -30,6 +29,8 @@ from jsonargparse_tests.conftest import (
     get_parse_args_stderr,
     get_parse_args_stdout,
     get_parser_help,
+    json_or_yaml_dump,
+    json_or_yaml_load,
     source_unavailable,
 )
 
@@ -41,7 +42,7 @@ def test_subclass_basics(parser, type):
         "init_args": {"firstweekday": 3},
     }
     parser.add_argument("--op", type=type)
-    cfg = parser.parse_args([f"--op={value}"])
+    cfg = parser.parse_args([f"--op={json.dumps(value)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init["op"], Calendar)
     assert 3 == init["op"].firstweekday
@@ -235,7 +236,7 @@ def test_subclass_merge_init_args_global_config(parser):
     expected = deepcopy(config1["cal"])
     expected["init_args"].update(config2["cal"]["init_args"])
 
-    cfg = parser.parse_args([f"--cfg={config1}", f"--cfg={config2}"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(config1)}", f"--cfg={json.dumps(config2)}"])
     assert cfg.cal.as_dict() == expected
 
 
@@ -252,10 +253,10 @@ def test_subclass_init_args_without_class_path_dict(parser):
     parser.add_argument("--cal", type=Calendar)
     config = {"cal": {"class_path": "TextCalendar", "init_args": {"firstweekday": 2}}}
 
-    cfg = parser.parse_args([f"--cfg={config}", "--cal={'init_args': {'firstweekday': 3}}"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(config)}", '--cal={"init_args": {"firstweekday": 3}}'])
     assert cfg.cal.init_args == Namespace(firstweekday=3)
 
-    cfg = parser.parse_args([f"--cfg={config}", "--cal={'firstweekday': 4}"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(config)}", '--cal={"firstweekday": 4}'])
     assert cfg.cal.init_args == Namespace(firstweekday=4)
 
 
@@ -282,9 +283,8 @@ def test_subclass_with_default_config_files(parser, tmp_cwd, logger, subtests):
         assert cfg.data.cal.as_dict() == config
 
     with subtests.test("dump"):
-        dump = parser.dump(cfg)
-        assert "class_path: calendar.Calendar\n" in dump
-        assert "firstweekday: 3\n" in dump
+        dump = json_or_yaml_load(parser.dump(cfg))
+        assert dump["data"]["cal"] == {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 3}}
 
     with subtests.test("disable defaults"):
         cfg = parser.parse_args(["--data.cal.class_path=calendar.Calendar"], defaults=False)
@@ -305,7 +305,7 @@ class DefaultConfigSubcommands:
 
 def test_subclass_in_subcommand_with_global_default_config_file(parser, subparser, tmp_cwd):
     default_path = Path("default.yaml")
-    default_path.write_text("fit:\n  model:\n    foo: 123")
+    default_path.write_text(json_or_yaml_dump({"fit": {"model": {"foo": 123}}}))
 
     parser.default_config_files = [default_path]
     parser.add_argument("--config", action="config")
@@ -407,8 +407,8 @@ def test_importable_instances(parser):
     parser.add_argument("--dtype", type=dtype)
     cfg = parser.parse_args([f"--dtype={__name__}.float32"])
     assert cfg.dtype is float32
-    dump = parser.dump(cfg)
-    assert dump.strip() == f"dtype: {__name__}.float32"
+    dump = json_or_yaml_load(parser.dump(cfg))
+    assert dump == {"dtype": f"{__name__}.float32"}
 
 
 # custom instantiation tests
@@ -512,7 +512,7 @@ def test_subclass_env_config(parser):
     parser.env_prefix = "APP"
     parser.default_env = True
     parser.add_argument("--cal", type=Calendar)
-    env = {"APP_CAL": "{'class_path': 'TextCalendar', 'init_args': {'firstweekday': 4}}"}
+    env = {"APP_CAL": '{"class_path": "TextCalendar", "init_args": {"firstweekday": 4}}'}
     with patch.dict(os.environ, env):
         cfg = parser.parse_env()
     assert cfg.cal == Namespace(class_path="calendar.TextCalendar", init_args=Namespace(firstweekday=4))
@@ -568,22 +568,27 @@ class RequiredParamModel:
 
 
 def test_subclass_required_parameter_with_default_config_files(parser, tmp_cwd):
-    defaults = f"""model:
-      sub_module:
-        class_path: {__name__}.RequiredParamSubModule
-        init_args:
-          p1: 4
-          p2: 5
-    """
+    defaults = {
+        "model": {
+            "sub_module": {
+                "class_path": f"{__name__}.RequiredParamSubModule",
+                "init_args": {
+                    "p1": 4,
+                    "p2": 5,
+                },
+            },
+        },
+    }
     defaults_path = Path("defaults.yaml")
-    defaults_path.write_text(defaults)
+    defaults_path.write_text(json_or_yaml_dump(defaults))
 
     parser.default_config_files = [defaults_path]
     parser.add_class_arguments(RequiredParamModel, "model")
 
     cfg = parser.parse_args(["--model.sub_module.init_args.p2=7"])
 
-    expected = yaml.safe_load(defaults.replace("p2: 5", "p2: 7"))["model"]
+    expected = defaults["model"]
+    expected["sub_module"]["init_args"]["p2"] = 7
     expected["sub_module"]["init_args"]["p3"] = 3
     assert cfg.model.as_dict() == expected
 
@@ -728,25 +733,28 @@ def test_subclass_dict_parameter_deep(parser):
     parser.add_argument("--cfg", action="config")
     parser.add_class_arguments(Model, "model")
 
-    config = f"""model:
-      encoder:
-        class_path: {__name__}.Network
-        init_args:
-          some_dict:
-            a: 1
-          sub_network:
-            class_path: {__name__}.Network
-            init_args:
-              some_dict:
-                b: 2
-              sub_network:
-                class_path: {__name__}.Module
-    """
+    config = {
+        "model": {
+            "encoder": {
+                "class_path": f"{__name__}.Network",
+                "init_args": {
+                    "some_dict": {"a": 1},
+                    "sub_network": {
+                        "class_path": f"{__name__}.Network",
+                        "init_args": {
+                            "some_dict": {"b": 2},
+                            "sub_network": {"class_path": f"{__name__}.Module"},
+                        },
+                    },
+                },
+            },
+        },
+    }
 
-    cfg = parser.parse_args([f"--cfg={config}"])
+    cfg = parser.parse_args([f"--cfg={json_or_yaml_dump(config)}"])
     assert cfg.model.encoder.init_args.some_dict == {"a": 1}
     assert cfg.model.encoder.init_args.sub_network.init_args.some_dict == {"b": 2}
-    assert cfg.model.as_dict() == yaml.safe_load(config)["model"]
+    assert cfg.model.as_dict() == config["model"]
 
 
 # list append tests
@@ -803,7 +811,7 @@ def test_subclass_list_append_multiple(parser):
     )
     assert ["calendar.Calendar", "calendar.TextCalendar"] == [x.class_path for x in cfg.a.cals]
     assert [3, 1] == [x.init_args.firstweekday for x in cfg.a.cals]
-    cfg = parser.parse_args([f"--a={cfg.a.as_dict()}", "--a.cals.firstweekday=4"])
+    cfg = parser.parse_args([f"--a={json.dumps(cfg.a.as_dict())}", "--a.cals.firstweekday=4"])
     assert Namespace(firstweekday=4) == cfg.a.cals[-1].init_args
     args = ["--a.cals+=Invalid", "--a.cals+=TextCalendar"]
     pytest.raises(ArgumentError, lambda: parser.parse_args(args))
@@ -825,7 +833,7 @@ def test_subcommand_subclass_list_append_multiple(parser, subparser):
     )
     assert ["calendar.Calendar", "calendar.TextCalendar"] == [x.class_path for x in cfg.cmd.a.cals]
     assert [3, 1] == [x.init_args.firstweekday for x in cfg.cmd.a.cals]
-    cfg = parser.parse_args(["cmd", f"--a={cfg.cmd.a.as_dict()}", "--a.cals.firstweekday=4"])
+    cfg = parser.parse_args(["cmd", f"--a={json.dumps(cfg.cmd.a.as_dict())}", "--a.cals.firstweekday=4"])
     assert Namespace(firstweekday=4) == cfg.cmd.a.cals[-1].init_args
     args = ["cmd", "--a.cals+=Invalid", "--a.cals+=TextCalendar"]
     pytest.raises(ArgumentError, lambda: parser.parse_args(args))
@@ -857,7 +865,7 @@ def test_type_any_subclasses(parser):
         },
     }
 
-    cfg = parser.parse_args([f"--any={value}"])
+    cfg = parser.parse_args([f"--any={json.dumps(value)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.any, AnySubclasses)
     assert isinstance(init.any.cal1, TextCalendar)
@@ -866,7 +874,7 @@ def test_type_any_subclasses(parser):
     assert 2 == init.any.cal2.firstweekday
 
     value["init_args"]["cal2"]["class_path"] = "does.not.exist"
-    cfg = parser.parse_args([f"--any={value}"])
+    cfg = parser.parse_args([f"--any={json.dumps(value)}"])
     assert isinstance(cfg.any.init_args.cal1, Namespace)
     assert isinstance(cfg.any.init_args.cal2, dict)
     init = parser.instantiate_classes(cfg)
@@ -877,7 +885,7 @@ def test_type_any_subclasses(parser):
     assert 2 == init.any.cal2["init_args"]["firstweekday"]
 
     value["init_args"]["cal1"]["class_path"] = "does.not.exist"
-    cfg = parser.parse_args([f"--any={value}"])
+    cfg = parser.parse_args([f"--any={json.dumps(value)}"])
     assert isinstance(cfg.any, dict)
 
 
@@ -894,7 +902,7 @@ def test_type_any_list_of_subclasses(parser):
         },
     ]
 
-    cfg = parser.parse_args([f"--any={value}"])
+    cfg = parser.parse_args([f"--any={json.dumps(value)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.any, list)
     assert 2 == len(init.any)
@@ -917,7 +925,7 @@ def test_type_any_dict_of_subclasses(parser):
         },
     }
 
-    cfg = parser.parse_args([f"--any={value}"])
+    cfg = parser.parse_args([f"--any={json.dumps(value)}"])
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.any, dict)
     assert 2 == len(init.any)
@@ -1041,7 +1049,7 @@ def test_subclass_discard_init_args_config_with_default(parser, logger):
 
     config = {"s": {"class_path": "OverrideSub2", "init_args": {"s2": "v2"}}}
     with capture_logs(logger) as logs:
-        cfg = parser.parse_args([f"--cfg={config}"])
+        cfg = parser.parse_args([f"--cfg={json.dumps(config)}"])
 
     assert "discarding init_args: {'s1': 'v1'}" in logs.getvalue()
     assert cfg.s.class_path == f"{__name__}.OverrideSub2"
@@ -1102,7 +1110,7 @@ def test_subclass_subcommand_set_defaults_discard_init_args(parser, subparser, l
     subcommands.add_subcommand("fit", subparser)
 
     with capture_logs(logger) as logs:
-        cfg = parser.parse_args(["fit", f"--arch={value}"])
+        cfg = parser.parse_args(["fit", f"--arch={json.dumps(value)}"])
     assert "discarding init_args: {'b': 3}" in logs.getvalue()
     assert cfg.fit.arch.as_dict() == value
 
@@ -1152,7 +1160,7 @@ def test_discard_init_args_config_nested(parser, logger, tmp_cwd, method):
         parser.set_defaults(main=lazy_instance(ConfigDiscardMain))
 
     config_path = Path("config.yaml")
-    config_path.write_text(yaml.safe_dump(config))
+    config_path.write_text(json_or_yaml_dump(config))
 
     with capture_logs(logger) as logs:
         cfg = parser.parse_args([f"--cfg={config_path}"])
@@ -1203,7 +1211,7 @@ def test_subclass_discard_init_args_dict_looks_like_subclass(parser, logger, tmp
             }
         }
         config_paths[c] = Path(f"config{c}.yaml")
-        config_paths[c].write_text(yaml.safe_dump(configs[c]))
+        config_paths[c].write_text(json_or_yaml_dump(configs[c]))
 
     with capture_logs(logger) as logs:
         cfg = parser.parse_args([f"--cfg={config_paths[1]}", f"--cfg={config_paths[2]}"])
@@ -1250,15 +1258,15 @@ def test_subclass_unresolved_parameters(parser, subtests):
         assert cfg.cls.dict_kwargs == expected.dict_kwargs
 
     with subtests.test("config"):
-        cfg = parser.parse_args([f"--cfg={config}"])
+        cfg = parser.parse_args([f"--cfg={json.dumps(config)}"])
         assert cfg.cls == expected
         init = parser.instantiate_classes(cfg)
         assert isinstance(init.cls, UnresolvedParams)
         assert init.cls.kwargs == expected.dict_kwargs
 
     with subtests.test("print_config"):
-        out = get_parse_args_stdout(parser, [f"--cfg={config}", "--print_config"])
-        data = yaml.safe_load(out)["cls"]
+        out = get_parse_args_stdout(parser, [f"--cfg={json.dumps(config)}", "--print_config"])
+        data = json_or_yaml_load(out)["cls"]
         assert data == expected.as_dict()
 
     with subtests.test("invalid dict_kwargs"):
@@ -1308,8 +1316,7 @@ def test_add_subclass_lazy_default(parser):
     parser.add_argument("--config", action="config")
     parser.set_defaults({"cal": lazy_instance(Calendar, firstweekday=5)})
     out = get_parse_args_stdout(parser, ["--print_config"])
-    assert "class_path: calendar.Calendar" in out
-    assert "firstweekday: 5" in out
+    assert json_or_yaml_load(out)["cal"] == {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 5}}
 
     help_str = get_parser_help(parser)
     assert "'init_args': {'firstweekday': 5}" in help_str
@@ -1388,7 +1395,7 @@ def test_add_subclass_list_of_union(parser):
             }
         ]
     }
-    cfg = parser.parse_args([f"--config={config}"])
+    cfg = parser.parse_args([f"--config={json.dumps(config)}"])
     assert cfg.as_dict()["subclass"] == config["subclass"]
     help_str = get_parser_help(parser)
     assert "Show the help for the given subclass of {ListUnionA,ListUnionB}" in help_str
@@ -1425,7 +1432,7 @@ def test_subclass_signature_instance_default(parser):
     with warnings.catch_warnings(record=True) as w:
         dump = parser.dump(cfg)
     assert "Unable to serialize instance" in str(w[0].message)
-    assert "cal: Unable to serialize instance <calendar.Calendar " in dump
+    assert "Unable to serialize instance <calendar.Calendar " in dump
 
 
 # protocol tests
@@ -1643,7 +1650,7 @@ def test_subclass_print_config(parser):
     parser.add_class_arguments(PrintConfig, "g")
 
     out = get_parse_args_stdout(parser, ["--g.a1=calendar.Calendar", "--print_config"])
-    obtained = yaml.safe_load(out)["g"]
+    obtained = json_or_yaml_load(out)["g"]
     assert obtained == {"a1": {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 0}}, "a2": 7}
 
     err = get_parse_args_stderr(parser, ["--g.a1=calendar.Calendar", "--g.a1.invalid=1", "--print_config"])
@@ -1676,22 +1683,22 @@ def test_subclass_print_config_required_parameters_as_null(parser):
         "subclass": {"class_path": f"{__name__}.PrintConfigRequiredSub", "init_args": {"arg1": None, "arg2": 1}},
     }
 
-    assert yaml.safe_load(out) == expected
+    assert json_or_yaml_load(out) == expected
 
 
 def test_subclass_multifile_save(parser, tmp_cwd):
     parser.add_subclass_arguments(Calendar, "cal")
 
     cal_cfg_path = Path("cal.yaml")
-    cal_cfg_path.write_text(yaml.dump({"class_path": "calendar.Calendar"}))
+    cal_cfg_path.write_text(json_or_yaml_dump({"class_path": "calendar.Calendar"}))
     out_main_cfg = Path("out", "config.yaml")
     out_main_cfg.parent.mkdir()
 
     cfg = parser.parse_args([f"--cal={cal_cfg_path}"])
     parser.save(cfg, out_main_cfg, multifile=True)
 
-    assert "cal: cal.yaml" == out_main_cfg.read_text().strip()
-    cal = yaml.safe_load(Path("out", "cal.yaml").read_text())
+    assert {"cal": "cal.yaml"} == json_or_yaml_load(out_main_cfg.read_text())
+    cal = json_or_yaml_load(Path("out", "cal.yaml").read_text())
     assert cal == {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 0}}
 
 
@@ -1722,7 +1729,7 @@ def test_subclass_error_undefined_module(parser):
 def test_subclass_error_unexpected_init_arg(parser):
     parser.add_argument("--op", type=Calendar)
     class_path = '"class_path": "calendar.Calendar"'
-    init_args = '"init_args": {"unexpected_arg": True}'
+    init_args = '"init_args": {"unexpected_arg": true}'
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(["--op={" + class_path + ", " + init_args + "}"])
     ctx.match("Key 'unexpected_arg' is not expected")
@@ -1834,7 +1841,7 @@ def test_subclass_error_indentation_in_union_invalid_value(parser):
     parser.add_argument("--union", type=Union[str, ErrorIndentation2])
     parser.add_argument("--cfg", action="config")
     config = {"union": [{"class_path": "ErrorIndentation2", "init_args": {"val": "x"}}]}
-    err = get_parse_args_stderr(parser, [f"--cfg={config}"])
+    err = get_parse_args_stderr(parser, [f"--cfg={json.dumps(config)}"])
     expected = textwrap.dedent(
         """
     Errors:
diff --git a/jsonargparse_tests/test_subcommands.py b/jsonargparse_tests/test_subcommands.py
index a181bbb..5892402 100644
--- a/jsonargparse_tests/test_subcommands.py
+++ b/jsonargparse_tests/test_subcommands.py
@@ -7,7 +7,6 @@ from pathlib import Path
 from unittest.mock import patch
 
 import pytest
-import yaml
 
 from jsonargparse import (
     ArgumentError,
@@ -15,7 +14,13 @@ from jsonargparse import (
     Namespace,
     strip_meta,
 )
-from jsonargparse_tests.conftest import get_parse_args_stderr, get_parse_args_stdout, get_parser_help
+from jsonargparse_tests.conftest import (
+    get_parse_args_stderr,
+    get_parse_args_stdout,
+    get_parser_help,
+    json_or_yaml_dump,
+    json_or_yaml_load,
+)
 from jsonargparse_tests.test_subclasses import CustomInstantiationBase, instantiator
 
 
@@ -209,7 +214,7 @@ def test_subcommand_print_config_default_env(subparser):
     subcommands.add_subcommand("a", subparser)
 
     out = get_parse_args_stdout(parser, ["a", "--print_config"])
-    assert yaml.safe_load(out) == {"o": 1}
+    assert json_or_yaml_load(out) == {"o": 1}
 
 
 def test_subcommand_default_config_repeated_keys(parser, subparser, tmp_cwd):
@@ -244,7 +249,8 @@ def test_subsubcommand_default_config_repeated_keys(parser, subparser, tmp_cwd):
 
 
 def test_subcommand_required_arg_in_default_config(parser, subparser, tmp_cwd):
-    Path("config.yaml").write_text("output: test\nprepare:\n  media: test\n")
+    config = {"output": "test", "prepare": {"media": "test"}}
+    Path("config.yaml").write_text(json_or_yaml_dump(config))
     parser.default_config_files = ["config.yaml"]
     parser.add_argument("--output", required=True)
     subcommands = parser.add_subcommands()
diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py
index 51354d1..1be16ec 100644
--- a/jsonargparse_tests/test_typehints.py
+++ b/jsonargparse_tests/test_typehints.py
@@ -32,9 +32,9 @@ from unittest import mock
 from warnings import catch_warnings
 
 import pytest
-import yaml
 
 from jsonargparse import ArgumentError, Namespace, lazy_instance
+from jsonargparse._optionals import pyyaml_available
 from jsonargparse._typehints import (
     ActionTypeHint,
     NotRequired,
@@ -56,6 +56,8 @@ from jsonargparse_tests.conftest import (
     capture_logs,
     get_parse_args_stdout,
     get_parser_help,
+    json_or_yaml_dump,
+    json_or_yaml_load,
 )
 
 
@@ -100,9 +102,11 @@ def test_bool_parse(parser):
     parser.add_argument("--val", type=bool)
     assert None is parser.get_defaults().val
     assert True is parser.parse_args(["--val", "true"]).val
-    assert True is parser.parse_args(["--val", "TRUE"]).val
+    if pyyaml_available:
+        assert True is parser.parse_args(["--val", "TRUE"]).val
     assert False is parser.parse_args(["--val", "false"]).val
-    assert False is parser.parse_args(["--val", "FALSE"]).val
+    if pyyaml_available:
+        assert False is parser.parse_args(["--val", "FALSE"]).val
     pytest.raises(ArgumentError, lambda: parser.parse_args(["--val", "1"]))
 
 
@@ -123,7 +127,7 @@ def test_complex_number(parser):
     parser.add_argument("--complex", type=complex)
     cfg = parser.parse_args(["--complex=(2+3j)"])
     assert cfg.complex == 2 + 3j
-    assert parser.dump(cfg) == "complex: (2+3j)\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"complex": "(2+3j)"}
 
 
 def test_literal(parser):
@@ -159,8 +163,8 @@ def test_type_any(parser):
     assert 5.6 == parser.parse_args(["--any=5.6"]).any
     assert [7, 8] == parser.parse_args(["--any=[7, 8]"]).any
     assert {"a": 0, "b": 1} == parser.parse_args(['--any={"a":0, "b":1}']).any
-    assert True is parser.parse_args(["--any=True"]).any
-    assert False is parser.parse_args(["--any=False"]).any
+    assert True is parser.parse_args(["--any=true"]).any
+    assert False is parser.parse_args(["--any=false"]).any
     assert None is parser.parse_args(["--any=null"]).any
     assert " " == parser.parse_args(["--any= "]).any
     assert " xyz " == parser.parse_args(["--any= xyz "]).any
@@ -170,7 +174,7 @@ def test_type_any(parser):
 def test_type_any_dump(parser):
     parser.add_argument("--any", type=Any, default=EnumABC.B)
     cfg = parser.parse_args([])
-    assert "any: B\n" == parser.dump(cfg)
+    assert {"any": "B"} == json_or_yaml_load(parser.dump(cfg))
 
 
 def test_type_typehint_without_arg(parser):
@@ -178,7 +182,7 @@ def test_type_typehint_without_arg(parser):
     parser.add_argument("--type", type=type_class)
     cfg = parser.parse_args(["--type=uuid.UUID"])
     assert cfg.type is uuid.UUID
-    assert parser.dump(cfg) == "type: uuid.UUID\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"type": "uuid.UUID"}
 
 
 def test_type_typehint_with_arg(parser):
@@ -186,7 +190,7 @@ def test_type_typehint_with_arg(parser):
     parser.add_argument("--cal", type=type_class[Calendar])
     cfg = parser.parse_args(["--cal=calendar.Calendar"])
     assert cfg.cal is Calendar
-    assert parser.dump(cfg) == "cal: calendar.Calendar\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"cal": "calendar.Calendar"}
     pytest.raises(ArgumentError, lambda: parser.parse_args(["--cal=uuid.UUID"]))
 
 
@@ -216,7 +220,7 @@ def test_enum_parse(parser):
 def test_enum_dump(parser):
     parser.add_argument("--enum", type=EnumABC)
     cfg = parser.parse_args(["--enum=C"])
-    assert "enum: C\n" == parser.dump(cfg)
+    assert {"enum": "C"} == json_or_yaml_load(parser.dump(cfg))
     with pytest.raises(TypeError):
         parser.dump(Namespace(enum="x"))
 
@@ -273,7 +277,7 @@ def test_set(parser):
 
 def test_tuple_without_arg(parser):
     parser.add_argument("--tuple", type=tuple)
-    cfg = parser.parse_args(['--tuple=[1, "a", True]'])
+    cfg = parser.parse_args(['--tuple=[1, "a", true]'])
     assert (1, "a", True) == cfg.tuple
     help_str = get_parser_help(parser, strip=True)
     assert "--tuple [ITEM,...] (type: tuple, default: null)" in help_str
@@ -324,7 +328,7 @@ def test_list_variants(parser, list_type):
 
 def test_list_dump(parser):
     parser.add_argument("--list", type=Union[PositiveInt, List[PositiveInt]])
-    dump = yaml.safe_load(parser.dump(Namespace(list=[1, 2])))
+    dump = json_or_yaml_load(parser.dump(Namespace(list=[1, 2])))
     assert [1, 2] == dump["list"]
     with pytest.raises(TypeError):
         parser.dump(Namespace(list=[1, -2]))
@@ -378,20 +382,20 @@ def test_list_append(parser):
 def test_list_append_default_empty(parser):
     parser.add_argument("--list", type=List[str], default=[])
     assert [] == parser.get_defaults().list
-    assert ["a"] == parser.parse_args(["--list=[a]"]).list
+    assert ["a"] == parser.parse_args(['--list=["a"]']).list
     assert [] == parser.get_defaults().list
-    assert ["b", "c"] == parser.parse_args(["--list+=[b, c]"]).list
+    assert ["b", "c"] == parser.parse_args(['--list+=["b", "c"]']).list
     assert [] == parser.get_defaults().list
 
 
 def test_list_append_config(parser):
     parser.add_argument("--cfg", action="config")
     parser.add_argument("--val", type=List[int], default=[1, 2])
-    assert [3, 4] == parser.parse_args(["--cfg", "val: [3, 4]"]).val
-    assert [1, 2, 3] == parser.parse_args(["--cfg", "val+: 3"]).val
-    assert [1, 2, 3, 4] == parser.parse_args(["--cfg", "val+: [3, 4]"]).val
-    pytest.raises(ArgumentError, lambda: parser.parse_args(["--cfg", "val+: a"]))
-    pytest.raises(ArgumentError, lambda: parser.parse_args(["--val=2", "--cfg", "val+: 3"]))
+    assert [3, 4] == parser.parse_args(["--cfg", '{"val": [3, 4]}']).val
+    assert [1, 2, 3] == parser.parse_args(["--cfg", '{"val+": 3}']).val
+    assert [1, 2, 3, 4] == parser.parse_args(["--cfg", '{"val+": [3, 4]}']).val
+    pytest.raises(ArgumentError, lambda: parser.parse_args(["--cfg", '{"val+": "a"}']))
+    pytest.raises(ArgumentError, lambda: parser.parse_args(["--val=2", "--cfg", '{"val+": 3}']))
 
 
 def test_list_append_default_config_files(parser, tmp_cwd, subtests):
@@ -400,14 +404,14 @@ def test_list_append_default_config_files(parser, tmp_cwd, subtests):
     parser.add_argument("--nums", type=List[int], default=[0])
 
     with subtests.test("replace"):
-        config_path.write_text("nums: [1]\n")
+        config_path.write_text(json_or_yaml_dump({"nums": [1]}))
         cfg = parser.parse_args(["--nums+=2"])
         assert cfg.nums == [1, 2]
         cfg = parser.parse_args(["--nums+=[2, 3]"])
         assert cfg.nums == [1, 2, 3]
 
     with subtests.test("append"):
-        config_path.write_text("nums+: [1]\n")
+        config_path.write_text(json_or_yaml_dump({"nums+": [1]}))
         cfg = parser.get_defaults()
         assert cfg.nums == [0, 1]
         cfg = parser.parse_args(["--nums+=2"])
@@ -418,7 +422,7 @@ def test_list_append_default_config_files(parser, tmp_cwd, subtests):
 
     with subtests.test("append in second default config"):
         config_path2 = tmp_cwd / "config2.yaml"
-        config_path2.write_text("nums+: [2]\n")
+        config_path2.write_text(json_or_yaml_dump({"nums+": [2]}))
         parser.default_config_files += [str(config_path2)]
         cfg = parser.get_defaults()
         assert cfg.nums == [0, 1, 2]
@@ -431,7 +435,7 @@ def test_list_append_subcommand_global_default_config_files(parser, subparser, t
     subcommands = parser.add_subcommands()
     subparser.add_argument("--nums", type=List[int], default=[0])
     subcommands.add_subcommand("sub", subparser)
-    config_path.write_text("sub:\n  nums: [1]\n")
+    config_path.write_text(json_or_yaml_dump({"sub": {"nums": [1]}}))
 
     cfg = parser.parse_args(["sub", "--nums+=2"])
     assert cfg.sub.nums == [1, 2]
@@ -446,7 +450,7 @@ def test_list_append_subcommand_subparser_default_config_files(parser, subparser
     subparser.default_config_files = [config_path]
     subparser.add_argument("--nums", type=List[int], default=[0])
     subcommands.add_subcommand("sub", subparser)
-    config_path.write_text("nums: [1]\n")
+    config_path.write_text(json_or_yaml_dump({"nums": [1]}))
 
     cfg = parser.parse_args(["sub", "--nums+=2"])
     assert cfg.sub.nums == [1, 2]
@@ -469,7 +473,7 @@ def test_dict_int_keys(parser):
     parser.add_argument("--d", type=Dict[int, str])
     parser.add_argument("--cfg", action="config")
     cfg = {"d": {1: "val1", 2: "val2"}}
-    assert cfg["d"] == parser.parse_args(["--cfg", str(cfg)]).d
+    assert cfg["d"] == parser.parse_args(["--cfg", json.dumps(cfg)]).d
     pytest.raises(ArgumentError, lambda: parser.parse_args(['--cfg={"d": {"a": "b"}}']))
 
 
@@ -482,7 +486,7 @@ def test_dict_union(parser, tmp_cwd):
     assert isinstance(cfg.dict2["b"], Path_fc)
     assert {5: None} == parser.parse_args(['--dict1={"5":null}']).dict1
     pytest.raises(ArgumentError, lambda: parser.parse_args(['--dict1=["a", "b"]']))
-    cfg = yaml.safe_load(parser.dump(cfg))
+    cfg = json_or_yaml_load(parser.dump(cfg))
     assert {"dict1": {"2": 4.5, "6": "C"}, "dict2": {"a": True, "b": "f"}} == cfg
 
 
@@ -523,7 +527,7 @@ def test_typeddict_without_arg(parser):
 
 def test_typeddict_with_args(parser):
     parser.add_argument("--typeddict", type=TypedDict("MyDict", {"a": int}))
-    assert {"a": 1} == parser.parse_args(["--typeddict={'a': 1}"])["typeddict"]
+    assert {"a": 1} == parser.parse_args(['--typeddict={"a": 1}'])["typeddict"]
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(['--typeddict={"a":1, "b":2}'])
     ctx.match("Unexpected keys")
@@ -541,7 +545,7 @@ def test_typeddict_with_args(parser):
 
 def test_typeddict_with_args_ntotal(parser):
     parser.add_argument("--typeddict", type=TypedDict("MyDict", {"a": int}, total=False))
-    assert {"a": 1} == parser.parse_args(["--typeddict={'a': 1}"])["typeddict"]
+    assert {"a": 1} == parser.parse_args(['--typeddict={"a": 1}'])["typeddict"]
     assert {} == parser.parse_args(["--typeddict={}"])["typeddict"]
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(['--typeddict={"a":1, "b":2}'])
@@ -562,8 +566,8 @@ def test_not_required_support(parser):
 @pytest.mark.skipif(not NotRequired, reason="NotRequired introduced in python 3.11 or backported in typing_extensions")
 def test_typeddict_with_not_required_arg(parser):
     parser.add_argument("--typeddict", type=TypedDict("MyDict", {"a": int, "b": NotRequired[int]}))
-    assert {"a": 1} == parser.parse_args(["--typeddict={'a': 1}"])["typeddict"]
-    assert {"a": 1, "b": 2} == parser.parse_args(["--typeddict={'a': 1, 'b': 2}"])["typeddict"]
+    assert {"a": 1} == parser.parse_args(['--typeddict={"a": 1}'])["typeddict"]
+    assert {"a": 1, "b": 2} == parser.parse_args(['--typeddict={"a": 1, "b": 2}'])["typeddict"]
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(['--typeddict={"a":1, "b":2, "c": 3}'])
     ctx.match("Unexpected keys")
@@ -589,8 +593,8 @@ def test_required_support(parser):
 @pytest.mark.skipif(not Required, reason="Required introduced in python 3.11 or backported in typing_extensions")
 def test_typeddict_with_required_arg(parser):
     parser.add_argument("--typeddict", type=TypedDict("MyDict", {"a": Required[int], "b": int}, total=False))
-    assert {"a": 1} == parser.parse_args(["--typeddict={'a': 1}"])["typeddict"]
-    assert {"a": 1, "b": 2} == parser.parse_args(["--typeddict={'a': 1, 'b': 2}"])["typeddict"]
+    assert {"a": 1} == parser.parse_args(['--typeddict={"a": 1}'])["typeddict"]
+    assert {"a": 1, "b": 2} == parser.parse_args(['--typeddict={"a": 1, "b": 2}'])["typeddict"]
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(['--typeddict={"a":1, "b":2, "c": 3}'])
     ctx.match("Unexpected keys")
@@ -693,16 +697,16 @@ def test_typeddict_totality_inheritance(parser):
 
     parser.add_argument("--middledict", type=MiddleDict, required=False)
     parser.add_argument("--topdict", type=TopDict, required=False)
-    assert {"a": 1} == parser.parse_args(["--middledict={'a': 1}"])["middledict"]
-    assert {"a": 1, "b": 2} == parser.parse_args(["--middledict={'a': 1, 'b': 2}"])["middledict"]
+    assert {"a": 1} == parser.parse_args(['--middledict={"a": 1}'])["middledict"]
+    assert {"a": 1, "b": 2} == parser.parse_args(['--middledict={"a": 1, "b": 2}'])["middledict"]
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(["--middledict={}"])
     ctx.match("Missing required keys")
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(['--middledict={"b": 2}'])
     ctx.match("Missing required keys")
-    assert {"a": 1, "c": 2} == parser.parse_args(["--topdict={'a': 1, 'c': 2}"])["topdict"]
-    assert {"a": 1, "b": 2, "c": 3} == parser.parse_args(["--topdict={'a': 1, 'b': 2, 'c': 3}"])["topdict"]
+    assert {"a": 1, "c": 2} == parser.parse_args(['--topdict={"a": 1, "c": 2}'])["topdict"]
+    assert {"a": 1, "b": 2, "c": 3} == parser.parse_args(['--topdict={"a": 1, "b": 2, "c": 3}'])["topdict"]
     with pytest.raises(ArgumentError) as ctx:
         parser.parse_args(['--topdict={"a": 1, "b": 2}'])
     ctx.match("Missing required keys")
@@ -738,7 +742,8 @@ def test_ordered_dict(parser):
         parser.parse_args(['--odict={"x":"-"}'])
     ctx.match("Expected a <class 'int'>")
     assert parser.dump(cfg, format="json") == '{"odict":{"a":1,"b":2}}'
-    assert parser.dump(cfg, format="yaml") == "odict:\n  a: 1\n  b: 2\n"
+    if pyyaml_available:
+        assert parser.dump(cfg, format="yaml") == "odict:\n  a: 1\n  b: 2\n"
 
 
 def test_dict_default_ordered_dict(parser):
@@ -804,11 +809,11 @@ def test_callable_function_path(parser):
 
     cfg = parser.get_defaults()
     assert cfg.callable is time.time
-    assert parser.dump(cfg) == "callable: time.time\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"callable": "time.time"}
 
     cfg = parser.parse_args(["--callable=random.randint"])
     assert cfg.callable is random.randint
-    assert parser.dump(cfg) == "callable: random.randint\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"callable": "random.randint"}
 
     help_str = get_parser_help(parser)
     assert "(type: Callable, default: time.time)" in help_str
@@ -821,11 +826,11 @@ def test_callable_function_path(parser):
 def test_callable_list_of_function_paths(parser):
     parser.add_argument("--callables", type=List[Callable])
 
-    cfg = parser.parse_args(["--callables=[random.randint, time.time]"])
+    cfg = parser.parse_args(['--callables=["random.randint", "time.time"]'])
     assert [random.randint, time.time] == cfg.callables
 
     with pytest.raises(ArgumentError) as ctx:
-        parser.parse_args(["--callables=[jsonargparse.not_exist]"])
+        parser.parse_args(['--callables=["jsonargparse.not_exist"]'])
     ctx.match("Callable expects a function or a callable class")
 
 
@@ -841,9 +846,9 @@ def test_callable_class_path_simple(parser):
     parser.add_argument("--callable", type=Callable)
 
     value = {"class_path": f"{__name__}.CallableClassPath", "init_args": {"p1": 2}}
-    cfg = parser.parse_args([f"--callable={value}"])
+    cfg = parser.parse_args([f"--callable={json.dumps(value)}"])
     assert value == cfg.callable.as_dict()
-    assert value == yaml.safe_load(parser.dump(cfg))["callable"]
+    assert value == json_or_yaml_load(parser.dump(cfg))["callable"]
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.callable, CallableClassPath)
     assert 2 == init.callable()
@@ -852,7 +857,7 @@ def test_callable_class_path_simple(parser):
     pytest.raises(ArgumentError, lambda: parser.parse_args(["--callable=jsonargparse.SUPPRESS"]))
     pytest.raises(ArgumentError, lambda: parser.parse_args(["--callable=calendar.Calendar"]))
     value = {"class_path": f"{__name__}.CallableClassPath", "key": "val"}
-    pytest.raises(ArgumentError, lambda: parser.parse_args([f"--callable={value}"]))
+    pytest.raises(ArgumentError, lambda: parser.parse_args([f"--callable={json.dumps(value)}"]))
 
 
 class CallableParent(CallableClassPath):
@@ -955,7 +960,7 @@ def test_callable_args_return_type_class(parser, subtests):
                 "lr": 0.01,
             },
         }
-        cfg = parser.parse_args([f"--optimizer={value}"])
+        cfg = parser.parse_args([f"--optimizer={json.dumps(value)}"])
         assert f"{__name__}.Adam" == cfg.optimizer.class_path
         assert Namespace(lr=0.01, momentum=0.0) == cfg.optimizer.init_args
         init = parser.instantiate_classes(cfg)
@@ -965,7 +970,7 @@ def test_callable_args_return_type_class(parser, subtests):
         assert 0.01 == optimizer.lr
         assert 0.0 == optimizer.momentum
         dump = parser.dump(cfg)
-        assert yaml.safe_load(dump) == cfg.as_dict()
+        assert json_or_yaml_load(dump) == cfg.as_dict()
 
     with subtests.test("short notation"):
         assert cfg == parser.parse_args(["--optimizer=Adam", "--optimizer.lr=0.01"])
@@ -1019,7 +1024,7 @@ def test_callable_multiple_args_return_type_class(parser, subtests):
             "class_path": "Adam",
             "init_args": {"momentum": 0.9},
         }
-        cfg = parser.parse_args([f"--optimizer={value}"])
+        cfg = parser.parse_args([f"--optimizer={json.dumps(value)}"])
         assert f"{__name__}.Adam" == cfg.optimizer.class_path
         assert Namespace(momentum=0.9) == cfg.optimizer.init_args
         init = parser.instantiate_classes(cfg)
@@ -1029,7 +1034,7 @@ def test_callable_multiple_args_return_type_class(parser, subtests):
         assert 0.01 == optimizer.lr
         assert 0.9 == optimizer.momentum
         dump = parser.dump(cfg)
-        assert yaml.safe_load(dump) == cfg.as_dict()
+        assert json_or_yaml_load(dump) == cfg.as_dict()
 
     with subtests.test("short notation"):
         assert cfg == parser.parse_args(["--optimizer=Adam", "--optimizer.momentum=0.9"])
@@ -1084,7 +1089,7 @@ def test_callable_args_return_type_union_of_classes(parser, subtests):
                 "monitor": "loss",
             },
         }
-        cfg = parser.parse_args([f"--scheduler={value}"])
+        cfg = parser.parse_args([f"--scheduler={json.dumps(value)}"])
         assert f"{__name__}.ReduceLROnPlateau" == cfg.scheduler.class_path
         assert Namespace(monitor="loss", factor=0.1) == cfg.scheduler.init_args
         init = parser.instantiate_classes(cfg)
@@ -1125,7 +1130,7 @@ def test_optional_callable_args_return_type_class(parser, subtests):
                 "last_epoch": 2,
             },
         }
-        cfg = parser.parse_args([f"--scheduler={value}"])
+        cfg = parser.parse_args([f"--scheduler={json.dumps(value)}"])
         init = parser.instantiate_classes(cfg)
         scheduler = init.scheduler(optimizer)
         assert isinstance(scheduler, StepLR)
@@ -1151,7 +1156,7 @@ def test_callable_args_return_type_class_subconfig(parser, tmp_cwd):
         "class_path": "Adam",
         "init_args": {"momentum": 0.8},
     }
-    Path("optimizer.yaml").write_text(yaml.safe_dump(config))
+    Path("optimizer.yaml").write_text(json_or_yaml_dump(config))
 
     parser.add_class_arguments(CallableSubconfig, "m", sub_configs=True)
     cfg = parser.parse_args(["--m.o=optimizer.yaml"])
@@ -1222,7 +1227,7 @@ def test_callable_return_class_required_arg_from_default(parser):
             },
         }
     }
-    cfg = parser.parse_args([f"--cfg={config}"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(config)}"])
     assert cfg.model.init_args.scheduler.class_path == f"{__name__}.ReduceLROnPlateau"
     assert cfg.model.init_args.scheduler.init_args == Namespace(monitor="acc", factor=0.5)
 
@@ -1258,7 +1263,7 @@ def test_list_callable_return_class(parser):
         },
     }
 
-    cfg = parser.parse_args([f"--cfg={config}", "--model.schedulers.monitor=val/mAP50"])
+    cfg = parser.parse_args([f"--cfg={json.dumps(config)}", "--model.schedulers.monitor=val/mAP50"])
     assert cfg.model.init_args.schedulers[1].class_path == f"{__name__}.ReduceLROnPlateau"
     assert cfg.model.init_args.schedulers[1].init_args == Namespace(monitor="val/mAP50", factor=0.5)
 
@@ -1376,21 +1381,21 @@ def test_dump_skip_default(parser):
 
     cfg = parser.get_defaults()
     dump = parser.dump(cfg, skip_default=True)
-    assert dump == "{}\n"
+    assert dump.strip() == "{}"
 
     cfg.g2.op2.class_path = f"{__name__}.SkipDefault"
-    dump = parser.dump(cfg, skip_default=True)
-    assert dump == f"g2:\n  op2:\n    class_path: {__name__}.SkipDefault\n    init_args:\n      firstweekday: 2\n"
+    dump = json_or_yaml_load(parser.dump(cfg, skip_default=True))
+    assert dump == {"g2": {"op2": {"class_path": f"{__name__}.SkipDefault", "init_args": {"firstweekday": 2}}}}
 
     cfg.g2.op2.init_args.firstweekday = 0
-    dump = parser.dump(cfg, skip_default=True)
-    assert dump == f"g2:\n  op2:\n    class_path: {__name__}.SkipDefault\n"
+    dump = json_or_yaml_load(parser.dump(cfg, skip_default=True))
+    assert dump == {"g2": {"op2": {"class_path": f"{__name__}.SkipDefault"}}}
 
     parser.link_arguments("g1.op1", "g2.op2.init_args.firstweekday")
     parser.link_arguments("g1.op2", "g2.op2.init_args.param")
     del cfg["g2.op2.init_args"]
-    dump = parser.dump(cfg, skip_default=True)
-    assert dump == f"g2:\n  op2:\n    class_path: {__name__}.SkipDefault\n"
+    dump = json_or_yaml_load(parser.dump(cfg, skip_default=True))
+    assert dump == {"g2": {"op2": {"class_path": f"{__name__}.SkipDefault"}}}
 
 
 class ImportClass:
diff --git a/jsonargparse_tests/test_typing.py b/jsonargparse_tests/test_typing.py
index 6be2a93..aff1fb6 100644
--- a/jsonargparse_tests/test_typing.py
+++ b/jsonargparse_tests/test_typing.py
@@ -31,7 +31,7 @@ from jsonargparse.typing import (
     restricted_number_type,
     restricted_string_type,
 )
-from jsonargparse_tests.conftest import get_parser_help
+from jsonargparse_tests.conftest import get_parser_help, json_or_yaml_load
 
 
 def test_public_api():
@@ -177,7 +177,7 @@ def test_restricted_number_optional_union(parser):
 
 def test_restricted_number_dump(parser):
     parser.add_argument("--float", type=PositiveFloat)
-    assert "float: 1.1\n" == parser.dump(parser.parse_args(["--float", "1.1"]))
+    assert {"float": 1.1} == json_or_yaml_load(parser.dump(parser.parse_args(["--float", "1.1"])))
 
 
 def test_type_function_parse(parser):
@@ -252,7 +252,7 @@ def test_restricted_string_parse(parser):
 def test_restricted_string_dump(parser):
     ThreeChars = restricted_string_type("ThreeChars", "^[A-Z]{3}$")
     parser.add_argument("--op", type=ThreeChars)
-    assert "op: ABC\n" == parser.dump(parser.parse_args(["--op", "ABC"]))
+    assert {"op": "ABC"} == json_or_yaml_load(parser.dump(parser.parse_args(["--op", "ABC"])))
 
 
 # other types
@@ -371,7 +371,7 @@ def test_register_type_datetime(parser):
     parser.add_argument("--datetime", type=datetime)
     cfg = parser.parse_args(["--datetime=2008-09-03T20:56:35"])
     assert cfg.datetime == datetime(2008, 9, 3, 20, 56, 35)
-    assert parser.dump(cfg) == "datetime: '2008-09-03T20:56:35'\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"datetime": "2008-09-03T20:56:35"}
 
     register_type(datetime, serializer, deserializer)  # identical re-registering is okay
     pytest.raises(ValueError, lambda: register_type(datetime))  # different registration not okay
@@ -394,7 +394,7 @@ def test_decimal(parser):
     cfg = parser.parse_args(["--decimal=0.1"])
     assert isinstance(cfg.decimal, Decimal)
     assert cfg.decimal == Decimal("0.1")
-    assert parser.dump(cfg) == "decimal: 0.1\n"
+    assert json_or_yaml_load(parser.dump(cfg)) == {"decimal": 0.1}
 
 
 def test_uuid(parser):
@@ -405,7 +405,7 @@ def test_uuid(parser):
     cfg = parser.parse_args([f"--uuid={id1}", f'--uuids=["{id1}", "{id2}"]'])
     assert cfg.uuid == id1
     assert cfg.uuids == [id1, id2]
-    assert f"uuid: {id1}\nuuids:\n- {id1}\n- {id2}\n" == parser.dump(cfg)
+    assert {"uuid": str(id1), "uuids": [str(id1), str(id2)]} == json_or_yaml_load(parser.dump(cfg))
 
 
 def test_secret_str_methods():