diff --git a/python_json_config/config_node.py b/python_json_config/config_node.py
index 1195954..4cf2627 100644
--- a/python_json_config/config_node.py
+++ b/python_json_config/config_node.py
@@ -46,20 +46,9 @@ class ConfigNode(object):
 
         self.__node_dict = node_dict
 
-    def __getattr__(self, item: str):
-        """
-        Enables access of config elements via dots (i.e. config.field1 instead of config["field1"]). This method wraps
-        the get method.
-        If strict access is defined or the field is a required field, an AttributeError is thrown if the referenced
-        field does not exist. Otherwise, i.e. non-strict access is defined or the field is an optional field, None is
-        returned in the field does not exist.
-        :raises AttributeError: Raised when a non-existing field is accessed when either strict access is defined or the
-                                field is a required field.
-        :param item: the field that is accessed.
-        :return: The value of the referenced field.
-        """
-        return self.get(item)
-
+    """
+    CRUD methods to access and modify the config contents.
+    """
     def get(self, path: Union[str, List[str]]):
         """
         Retrieve a value in the config.
@@ -135,6 +124,59 @@ class ConfigNode(object):
         else:
             self.get(key).update(path=path[1:], value=value, upsert=upsert)
 
+    """
+    Iteration functions
+    """
+    def keys(self):
+        for key, value in self.__node_dict.items():
+            if isinstance(value, ConfigNode):
+                yield from value.keys()
+            else:
+                yield self.__path_for_key(key)
+
+    def values(self):
+        for key, value in self.__node_dict.items():
+            if isinstance(value, ConfigNode):
+                yield from value.values()
+            else:
+                yield value
+
+    def items(self):
+        for key, value in self.__node_dict.items():
+            if isinstance(value, ConfigNode):
+                yield from value.items()
+            else:
+                yield self.__path_for_key(key), value
+
+    def to_dict(self):
+        config_dict = {}
+        for key, value in self.__node_dict.items():
+            if isinstance(value, ConfigNode):
+                config_dict[key] = value.to_dict()
+            else:
+                config_dict[key] = value
+        return config_dict
+
+    """
+    Built-in python functions
+    """
+    def __iter__(self):
+        yield from self.keys()
+
+    def __getattr__(self, item: str):
+        """
+        Enables access of config elements via dots (i.e. config.field1 instead of config["field1"]). This method wraps
+        the get method.
+        If strict access is defined or the field is a required field, an AttributeError is thrown if the referenced
+        field does not exist. Otherwise, i.e. non-strict access is defined or the field is an optional field, None is
+        returned in the field does not exist.
+        :raises AttributeError: Raised when a non-existing field is accessed when either strict access is defined or the
+                                field is a required field.
+        :param item: the field that is accessed.
+        :return: The value of the referenced field.
+        """
+        return self.get(item)
+
     def __contains__(self, item: Union[str, List[str]]) -> bool:
         """
         Test if a field exists in the config and is not None (result in the case of a non-existing optional field).
@@ -156,14 +198,6 @@ class ConfigNode(object):
 
     __repr__ = __str__
 
-    @property
-    def __path_str(self):
-        return ".".join(self.__path)
-
-    def __path_for_key(self, key: str):
-        print_path = self.__path_str + '.' * bool(self.__path)
-        return print_path + key
-
     def __getstate__(self):
         """
         This method is needed to enable pickling since this class overwrites __getattr__.
@@ -176,6 +210,17 @@ class ConfigNode(object):
         """
         vars(self).update(state)
 
+    """
+    Private functions used in this class (e.g., for utility).
+    """
+    @property
+    def __path_str(self):
+        return ".".join(self.__path)
+
+    def __path_for_key(self, key: str):
+        print_path = self.__path_str + '.' * bool(self.__path)
+        return print_path + key
+
     def __parse_field_settings(self, field_names: List[Union[str, List[str]]]) -> Tuple[List[str], List[List[str]]]:
         """
         Parses settings (required or optional) for fields and subfields of this node.

Enable config iterations
It would be great if it were possible to iterate over the configuration.

For example:
```python
for key, value in config.items():
    print(f'{key} --> {value}')
```
diff --git a/tests/__init__.py b/tests/__init__.py
index 787665f..3f566de 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,10 +1,13 @@
-from .config_node_test import ConfigNodeTest
+from .config_node import ConfigNodeSerializationTest, ConfigNodeIteratorsTest, ConfigNodeCrudTest, ConfigNodeUtilsTest
 from .config_builder_test import ConfigBuilderTest
 from .validators import NetworkValidatorsTest, GenericValidatorsTest
 from .transformers import GenericTransformersTest
 
 __all__ = [
-    'ConfigNodeTest',
+    'ConfigNodeIteratorsTest',
+    'ConfigNodeCrudTest',
+    'ConfigNodeSerializationTest',
+    'ConfigNodeUtilsTest',
     'ConfigBuilderTest',
     'NetworkValidatorsTest',
     'GenericValidatorsTest',
diff --git a/tests/config_node/__init__.py b/tests/config_node/__init__.py
new file mode 100644
index 0000000..63b60f0
--- /dev/null
+++ b/tests/config_node/__init__.py
@@ -0,0 +1,11 @@
+from .test_crud import ConfigNodeCrudTest
+from .test_iterators import ConfigNodeIteratorsTest
+from .test_serialization import ConfigNodeSerializationTest
+from .test_utils import ConfigNodeUtilsTest
+
+__all__ = [
+    'ConfigNodeCrudTest',
+    'ConfigNodeSerializationTest',
+    'ConfigNodeIteratorsTest',
+    'ConfigNodeUtilsTest'
+]
diff --git a/tests/config_node_test.py b/tests/config_node/test_crud.py
similarity index 81%
rename from tests/config_node_test.py
rename to tests/config_node/test_crud.py
index e415a7e..7dc8f02 100644
--- a/tests/config_node_test.py
+++ b/tests/config_node/test_crud.py
@@ -1,11 +1,10 @@
-import pickle
 import warnings
 from unittest import TestCase
 
 from python_json_config.config_node import ConfigNode
 
 
-class ConfigNodeTest(TestCase):
+class ConfigNodeCrudTest(TestCase):
 
     def setUp(self):
         self.config_dict = {
@@ -98,27 +97,6 @@ class ConfigNodeTest(TestCase):
         node.update('key2', 'asd', upsert=True)
         self.assertEqual(node.key2, 'asd')
 
-    def test_string(self):
-        node = ConfigNode({1: 2, 3: 4})
-        node_str = 'ConfigNode(path=[], values={1: 2, 3: 4}, strict_access=True, required_fields=[], optional_fields' \
-                   '=[])'
-        self.assertEqual(str(node), node_str)
-
-    def test_pickle(self):
-        config = ConfigNode(self.config_dict)
-        pickle_conf = pickle.loads(pickle.dumps(config))
-        self.assertEqual(pickle_conf.key1, 1)
-        self.assertEqual(pickle_conf.key2.key3, 3)
-        self.assertEqual(pickle_conf.key2.key4.key5, 5)
-
-    def test_contains(self):
-        config = ConfigNode(self.config_dict)
-        self.assertTrue('key1' in config)
-        self.assertFalse('key1.key2' in config)
-        self.assertTrue('key2.key3' in config)
-        self.assertTrue('key2.key4.key5' in config)
-        self.assertFalse('key2.key4.key6' in config)
-
     def test_strict_access(self):
         config = ConfigNode(self.config_dict,
                             strict_access=True,
diff --git a/tests/config_node/test_iterators.py b/tests/config_node/test_iterators.py
new file mode 100644
index 0000000..0528ed1
--- /dev/null
+++ b/tests/config_node/test_iterators.py
@@ -0,0 +1,63 @@
+from unittest import TestCase
+
+from python_json_config.config_node import ConfigNode
+
+
+class ConfigNodeIteratorsTest(TestCase):
+
+    def setUp(self):
+        self.config_dict = {
+            'key1': 1,
+            'key2': {
+                'key3': 3,
+                'key4': {'key5': 5},
+                'key6': 6
+            },
+            'key7': 7
+        }
+
+    def test_contains(self):
+        config = ConfigNode(self.config_dict)
+        self.assertTrue('key1' in config)
+        self.assertFalse('key1.key2' in config)
+        self.assertTrue('key2.key3' in config)
+        self.assertTrue('key2.key4.key5' in config)
+        self.assertFalse('key2.key4.key6' in config)
+
+    def test_keys(self):
+        config = ConfigNode(self.config_dict)
+        keys = list(config.keys())
+        expected_keys = ['key1', 'key2.key3', 'key2.key4.key5', 'key2.key6', 'key7']
+        self.assertEqual(keys, expected_keys)
+
+    def test_values(self):
+        config = ConfigNode(self.config_dict)
+        values = list(config.values())
+        self.assertEqual(values, [1, 3, 5, 6, 7])
+
+    def test_items(self):
+        config = ConfigNode(self.config_dict)
+        items = list(config.items())
+        expected_items = [('key1', 1), ('key2.key3', 3), ('key2.key4.key5', 5), ('key2.key6', 6), ('key7', 7)]
+        self.assertEqual(items, expected_items)
+
+    def test_iter(self):
+        config = ConfigNode(self.config_dict)
+        expected_keys = ['key1', 'key2.key3', 'key2.key4.key5', 'key2.key6', 'key7']
+        self.assertEqual(list(config), expected_keys)
+
+    def test_iteration(self):
+        config = ConfigNode(self.config_dict)
+        for iter_key, key, value, item in zip(config, config.keys(), config.values(), config.items()):
+            item_key, item_value = item
+            self.assertEqual(iter_key, key)
+            self.assertEqual(item_key, key)
+            self.assertEqual(item_value, value)
+            self.assertEqual(config.get(key), value)
+
+    def test_to_dict(self):
+        config = ConfigNode(self.config_dict)
+        self.assertEqual(config.to_dict(), self.config_dict)
+        self.assertEqual(config.key2.to_dict(), self.config_dict["key2"])
+        self.assertEqual(config.key2.key4.to_dict(), self.config_dict["key2"]["key4"])
+
diff --git a/tests/config_node/test_serialization.py b/tests/config_node/test_serialization.py
new file mode 100644
index 0000000..963afba
--- /dev/null
+++ b/tests/config_node/test_serialization.py
@@ -0,0 +1,23 @@
+import pickle
+from unittest import TestCase
+
+from python_json_config.config_node import ConfigNode
+
+
+class ConfigNodeSerializationTest(TestCase):
+
+    def setUp(self):
+        self.config_dict = {
+            'key1': 1,
+            'key2': {
+                'key3': 3,
+                'key4': { 'key5': 5 }
+            }
+        }
+
+    def test_pickle(self):
+        config = ConfigNode(self.config_dict)
+        pickle_conf = pickle.loads(pickle.dumps(config))
+        self.assertEqual(pickle_conf.key1, 1)
+        self.assertEqual(pickle_conf.key2.key3, 3)
+        self.assertEqual(pickle_conf.key2.key4.key5, 5)
diff --git a/tests/config_node/test_utils.py b/tests/config_node/test_utils.py
new file mode 100644
index 0000000..edbd480
--- /dev/null
+++ b/tests/config_node/test_utils.py
@@ -0,0 +1,11 @@
+from unittest import TestCase
+
+from python_json_config.config_node import ConfigNode
+
+
+class ConfigNodeUtilsTest(TestCase):
+    def test_string(self):
+        node = ConfigNode({1: 2, 3: 4})
+        node_str = 'ConfigNode(path=[], values={1: 2, 3: 4}, strict_access=True, required_fields=[], optional_fields' \
+                   '=[])'
+        self.assertEqual(str(node), node_str)

pip install -e .
parse_log_pytest
requirements.txt
pytest
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
jsonschema==2.6.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
-e git+https://github.com/janehmueller/python-json-config.git@8525c2694a6d1958b92d121015b1add5298a686c#egg=python_json_config
tomli==2.2.1

name: python-json-config
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - jsonschema==2.6.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - tomli==2.2.1
prefix: /opt/conda/envs/python-json-config

tests/__init__.py::ConfigNodeIteratorsTest::test_items tests/__init__.py::ConfigNodeIteratorsTest::test_iter tests/__init__.py::ConfigNodeIteratorsTest::test_iteration tests/__init__.py::ConfigNodeIteratorsTest::test_keys tests/__init__.py::ConfigNodeIteratorsTest::test_to_dict tests/__init__.py::ConfigNodeIteratorsTest::test_values tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_items tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_iter tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_iteration tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_keys tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_to_dict tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_values tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_items tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_iter tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_iteration tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_keys tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_to_dict tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_values
tests/__init__.py::ConfigNodeSerializationTest::test_pickle tests/__init__.py::ConfigNodeIteratorsTest::test_contains tests/__init__.py::ConfigNodeCrudTest::test_add tests/__init__.py::ConfigNodeCrudTest::test_add_overwrite tests/__init__.py::ConfigNodeCrudTest::test_creation tests/__init__.py::ConfigNodeCrudTest::test_get tests/__init__.py::ConfigNodeCrudTest::test_strict_access tests/__init__.py::ConfigNodeCrudTest::test_update tests/__init__.py::ConfigNodeCrudTest::test_update_upsert tests/__init__.py::ConfigNodeUtilsTest::test_string tests/__init__.py::ConfigBuilderTest::test_json_schema_validation tests/__init__.py::ConfigBuilderTest::test_merge_env_variable tests/__init__.py::ConfigBuilderTest::test_optional_access tests/__init__.py::ConfigBuilderTest::test_optional_validation tests/__init__.py::ConfigBuilderTest::test_parse tests/__init__.py::ConfigBuilderTest::test_required_access tests/__init__.py::ConfigBuilderTest::test_type_validation tests/__init__.py::ConfigBuilderTest::test_type_validation_builder tests/__init__.py::ConfigBuilderTest::test_value_transformation tests/__init__.py::ConfigBuilderTest::test_value_transformation_builder tests/__init__.py::ConfigBuilderTest::test_value_validation tests/__init__.py::ConfigBuilderTest::test_value_validation_builder tests/__init__.py::ConfigBuilderTest::test_value_validation_error_messages tests/__init__.py::NetworkValidatorsTest::test_is_ipv4_address tests/__init__.py::NetworkValidatorsTest::test_is_unreserved_port tests/__init__.py::GenericValidatorsTest::test_is_timedelta tests/__init__.py::GenericValidatorsTest::test_is_valid_choice tests/__init__.py::GenericTransformersTest::test_to_timedelta tests/config_node/__init__.py::ConfigNodeCrudTest::test_add tests/config_node/__init__.py::ConfigNodeCrudTest::test_add_overwrite tests/config_node/__init__.py::ConfigNodeCrudTest::test_creation tests/config_node/__init__.py::ConfigNodeCrudTest::test_get tests/config_node/__init__.py::ConfigNodeCrudTest::test_strict_access tests/config_node/__init__.py::ConfigNodeCrudTest::test_update tests/config_node/__init__.py::ConfigNodeCrudTest::test_update_upsert tests/config_node/__init__.py::ConfigNodeIteratorsTest::test_contains tests/config_node/__init__.py::ConfigNodeSerializationTest::test_pickle tests/config_node/__init__.py::ConfigNodeUtilsTest::test_string tests/config_node/test_crud.py::ConfigNodeCrudTest::test_add tests/config_node/test_crud.py::ConfigNodeCrudTest::test_add_overwrite tests/config_node/test_crud.py::ConfigNodeCrudTest::test_creation tests/config_node/test_crud.py::ConfigNodeCrudTest::test_get tests/config_node/test_crud.py::ConfigNodeCrudTest::test_strict_access tests/config_node/test_crud.py::ConfigNodeCrudTest::test_update tests/config_node/test_crud.py::ConfigNodeCrudTest::test_update_upsert tests/config_node/test_iterators.py::ConfigNodeIteratorsTest::test_contains tests/config_node/test_serialization.py::ConfigNodeSerializationTest::test_pickle tests/config_node/test_utils.py::ConfigNodeUtilsTest::test_string
python_json_config/config_node.py
python_json_config/config_node.py