[19/36] libcamera: Rename YamlObject to ValueNode
diff mbox series

Message ID 20260113000808.15395-20-laurent.pinchart@ideasonboard.com
State New
Headers show
Series
  • libcamera: Global configuration file improvements
Related show

Commit Message

Laurent Pinchart Jan. 13, 2026, 12:07 a.m. UTC
The YamlObject class is now a generic data container to model trees of
values. Rename it to ValueNode and expand the class documentation.

While at it, drop the unneeded libcamera:: namespace prefix when using
the ValueNode class.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 .../internal/converter/converter_dw100.h      |   2 +-
 .../libcamera/internal/global_configuration.h |  10 +-
 include/libcamera/internal/matrix.h           |  10 +-
 include/libcamera/internal/meson.build        |   2 +-
 .../internal/{yaml_object.h => value_node.h}  |  34 +-
 include/libcamera/internal/vector.h           |  10 +-
 include/libcamera/internal/yaml_parser.h      |   4 +-
 src/android/camera_hal_config.cpp             |  16 +-
 src/ipa/ipu3/algorithms/agc.cpp               |   4 +-
 src/ipa/ipu3/algorithms/agc.h                 |   2 +-
 src/ipa/ipu3/ipu3.cpp                         |   2 +-
 src/ipa/libipa/agc_mean_luminance.cpp         |  16 +-
 src/ipa/libipa/agc_mean_luminance.h           |  12 +-
 src/ipa/libipa/algorithm.cpp                  |   2 +-
 src/ipa/libipa/algorithm.h                    |   4 +-
 src/ipa/libipa/awb.cpp                        |   6 +-
 src/ipa/libipa/awb.h                          |   6 +-
 src/ipa/libipa/awb_bayes.cpp                  |   4 +-
 src/ipa/libipa/awb_bayes.h                    |   6 +-
 src/ipa/libipa/awb_grey.cpp                   |   2 +-
 src/ipa/libipa/awb_grey.h                     |   2 +-
 src/ipa/libipa/interpolator.cpp               |   2 +-
 src/ipa/libipa/interpolator.h                 |   4 +-
 src/ipa/libipa/lsc_polynomial.h               |   6 +-
 src/ipa/libipa/lux.cpp                        |   6 +-
 src/ipa/libipa/lux.h                          |   4 +-
 src/ipa/libipa/module.cpp                     |   2 +-
 src/ipa/libipa/module.h                       |   6 +-
 src/ipa/libipa/pwl.cpp                        |   2 +-
 src/ipa/mali-c55/algorithms/agc.cpp           |   2 +-
 src/ipa/mali-c55/algorithms/agc.h             |   2 +-
 src/ipa/mali-c55/algorithms/blc.cpp           |   4 +-
 src/ipa/mali-c55/algorithms/blc.h             |   2 +-
 src/ipa/mali-c55/algorithms/lsc.cpp           |   6 +-
 src/ipa/mali-c55/algorithms/lsc.h             |   2 +-
 src/ipa/mali-c55/mali-c55.cpp                 |   2 +-
 src/ipa/rkisp1/algorithms/agc.cpp             |  10 +-
 src/ipa/rkisp1/algorithms/agc.h               |   4 +-
 src/ipa/rkisp1/algorithms/awb.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/awb.h               |   2 +-
 src/ipa/rkisp1/algorithms/blc.cpp             |   4 +-
 src/ipa/rkisp1/algorithms/blc.h               |   2 +-
 src/ipa/rkisp1/algorithms/ccm.cpp             |   4 +-
 src/ipa/rkisp1/algorithms/ccm.h               |   4 +-
 src/ipa/rkisp1/algorithms/cproc.cpp           |   2 +-
 src/ipa/rkisp1/algorithms/cproc.h             |   2 +-
 src/ipa/rkisp1/algorithms/dpcc.cpp            |  22 +-
 src/ipa/rkisp1/algorithms/dpcc.h              |   2 +-
 src/ipa/rkisp1/algorithms/dpf.cpp             |   8 +-
 src/ipa/rkisp1/algorithms/dpf.h               |   2 +-
 src/ipa/rkisp1/algorithms/filter.cpp          |   2 +-
 src/ipa/rkisp1/algorithms/filter.h            |   2 +-
 src/ipa/rkisp1/algorithms/goc.cpp             |   4 +-
 src/ipa/rkisp1/algorithms/goc.h               |   2 +-
 src/ipa/rkisp1/algorithms/gsl.cpp             |   6 +-
 src/ipa/rkisp1/algorithms/gsl.h               |   2 +-
 src/ipa/rkisp1/algorithms/lsc.cpp             |  14 +-
 src/ipa/rkisp1/algorithms/lsc.h               |   2 +-
 src/ipa/rkisp1/algorithms/lux.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/lux.h               |   2 +-
 src/ipa/rkisp1/algorithms/wdr.cpp             |   4 +-
 src/ipa/rkisp1/algorithms/wdr.h               |   2 +-
 src/ipa/rkisp1/rkisp1.cpp                     |   2 +-
 src/ipa/rpi/controller/algorithm.cpp          |   2 +-
 src/ipa/rpi/controller/algorithm.h            |   4 +-
 src/ipa/rpi/controller/controller.cpp         |   4 +-
 src/ipa/rpi/controller/controller.h           |   4 +-
 src/ipa/rpi/controller/rpi/af.cpp             |  10 +-
 src/ipa/rpi/controller/rpi/af.h               |   8 +-
 src/ipa/rpi/controller/rpi/agc.cpp            |   2 +-
 src/ipa/rpi/controller/rpi/agc.h              |   2 +-
 src/ipa/rpi/controller/rpi/agc_channel.cpp    |  24 +-
 src/ipa/rpi/controller/rpi/agc_channel.h      |  12 +-
 src/ipa/rpi/controller/rpi/alsc.cpp           |  10 +-
 src/ipa/rpi/controller/rpi/alsc.h             |   2 +-
 src/ipa/rpi/controller/rpi/awb.cpp            |  10 +-
 src/ipa/rpi/controller/rpi/awb.h              |   8 +-
 src/ipa/rpi/controller/rpi/black_level.cpp    |   2 +-
 src/ipa/rpi/controller/rpi/black_level.h      |   2 +-
 src/ipa/rpi/controller/rpi/cac.cpp            |   4 +-
 src/ipa/rpi/controller/rpi/cac.h              |   2 +-
 src/ipa/rpi/controller/rpi/ccm.cpp            |   2 +-
 src/ipa/rpi/controller/rpi/ccm.h              |   2 +-
 src/ipa/rpi/controller/rpi/contrast.cpp       |   2 +-
 src/ipa/rpi/controller/rpi/contrast.h         |   2 +-
 src/ipa/rpi/controller/rpi/decompand.cpp      |   2 +-
 src/ipa/rpi/controller/rpi/decompand.h        |   2 +-
 src/ipa/rpi/controller/rpi/denoise.cpp        |   4 +-
 src/ipa/rpi/controller/rpi/denoise.h          |   4 +-
 src/ipa/rpi/controller/rpi/dpc.cpp            |   2 +-
 src/ipa/rpi/controller/rpi/dpc.h              |   2 +-
 src/ipa/rpi/controller/rpi/geq.cpp            |   2 +-
 src/ipa/rpi/controller/rpi/geq.h              |   2 +-
 src/ipa/rpi/controller/rpi/hdr.cpp            |   4 +-
 src/ipa/rpi/controller/rpi/hdr.h              |   4 +-
 src/ipa/rpi/controller/rpi/lux.cpp            |   2 +-
 src/ipa/rpi/controller/rpi/lux.h              |   2 +-
 src/ipa/rpi/controller/rpi/noise.cpp          |   2 +-
 src/ipa/rpi/controller/rpi/noise.h            |   2 +-
 src/ipa/rpi/controller/rpi/saturation.cpp     |   2 +-
 src/ipa/rpi/controller/rpi/saturation.h       |   2 +-
 src/ipa/rpi/controller/rpi/sdn.cpp            |   2 +-
 src/ipa/rpi/controller/rpi/sdn.h              |   2 +-
 src/ipa/rpi/controller/rpi/sharpen.cpp        |   2 +-
 src/ipa/rpi/controller/rpi/sharpen.h          |   2 +-
 src/ipa/rpi/controller/rpi/tonemap.cpp        |   2 +-
 src/ipa/rpi/controller/rpi/tonemap.h          |   2 +-
 src/ipa/simple/algorithms/blc.cpp             |   2 +-
 src/ipa/simple/algorithms/blc.h               |   2 +-
 src/ipa/simple/algorithms/ccm.cpp             |   2 +-
 src/ipa/simple/algorithms/ccm.h               |   2 +-
 src/ipa/simple/algorithms/lut.cpp             |   2 +-
 src/ipa/simple/algorithms/lut.h               |   2 +-
 src/ipa/simple/soft_simple.cpp                |   2 +-
 src/libcamera/converter/converter_dw100.cpp   |   2 +-
 src/libcamera/geometry.cpp                    |   4 +-
 src/libcamera/global_configuration.cpp        |   8 +-
 src/libcamera/matrix.cpp                      |   2 +-
 src/libcamera/meson.build                     |   2 +-
 src/libcamera/pipeline/rkisp1/rkisp1.cpp      |   2 +-
 .../pipeline/rpi/common/pipeline_base.cpp     |   4 +-
 .../pipeline/rpi/common/pipeline_base.h       |   4 +-
 src/libcamera/pipeline/rpi/pisp/pisp.cpp      |   6 +-
 src/libcamera/pipeline/rpi/vc4/vc4.cpp        |   6 +-
 src/libcamera/pipeline/virtual/README.md      |   2 +-
 .../pipeline/virtual/config_parser.cpp        |  18 +-
 .../pipeline/virtual/config_parser.h          |  12 +-
 src/libcamera/pipeline/virtual/virtual.cpp    |   2 +-
 src/libcamera/value_node.cpp                  | 484 ++++++++++++++++++
 src/libcamera/vector.cpp                      |   2 +-
 src/libcamera/yaml_object.cpp                 | 480 -----------------
 src/libcamera/yaml_parser.cpp                 |  54 +-
 test/yaml-parser.cpp                          |   6 +-
 133 files changed, 807 insertions(+), 803 deletions(-)
 rename include/libcamera/internal/{yaml_object.h => value_node.h} (81%)
 create mode 100644 src/libcamera/value_node.cpp
 delete mode 100644 src/libcamera/yaml_object.cpp

Comments

Barnabás Pőcze Jan. 14, 2026, 11:28 a.m. UTC | #1
2026. 01. 13. 1:07 keltezéssel, Laurent Pinchart írta:
> The YamlObject class is now a generic data container to model trees of
> values. Rename it to ValueNode and expand the class documentation.
> 
> While at it, drop the unneeded libcamera:: namespace prefix when using
> the ValueNode class.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---

Seems ok to me. There are no "YamlObject" left in the source, so it must be correct, right?

Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>


>   .../internal/converter/converter_dw100.h      |   2 +-
>   .../libcamera/internal/global_configuration.h |  10 +-
>   include/libcamera/internal/matrix.h           |  10 +-
>   include/libcamera/internal/meson.build        |   2 +-
>   .../internal/{yaml_object.h => value_node.h}  |  34 +-
>   include/libcamera/internal/vector.h           |  10 +-
>   include/libcamera/internal/yaml_parser.h      |   4 +-
>   src/android/camera_hal_config.cpp             |  16 +-
>   src/ipa/ipu3/algorithms/agc.cpp               |   4 +-
>   src/ipa/ipu3/algorithms/agc.h                 |   2 +-
>   src/ipa/ipu3/ipu3.cpp                         |   2 +-
>   src/ipa/libipa/agc_mean_luminance.cpp         |  16 +-
>   src/ipa/libipa/agc_mean_luminance.h           |  12 +-
>   src/ipa/libipa/algorithm.cpp                  |   2 +-
>   src/ipa/libipa/algorithm.h                    |   4 +-
>   src/ipa/libipa/awb.cpp                        |   6 +-
>   src/ipa/libipa/awb.h                          |   6 +-
>   src/ipa/libipa/awb_bayes.cpp                  |   4 +-
>   src/ipa/libipa/awb_bayes.h                    |   6 +-
>   src/ipa/libipa/awb_grey.cpp                   |   2 +-
>   src/ipa/libipa/awb_grey.h                     |   2 +-
>   src/ipa/libipa/interpolator.cpp               |   2 +-
>   src/ipa/libipa/interpolator.h                 |   4 +-
>   src/ipa/libipa/lsc_polynomial.h               |   6 +-
>   src/ipa/libipa/lux.cpp                        |   6 +-
>   src/ipa/libipa/lux.h                          |   4 +-
>   src/ipa/libipa/module.cpp                     |   2 +-
>   src/ipa/libipa/module.h                       |   6 +-
>   src/ipa/libipa/pwl.cpp                        |   2 +-
>   src/ipa/mali-c55/algorithms/agc.cpp           |   2 +-
>   src/ipa/mali-c55/algorithms/agc.h             |   2 +-
>   src/ipa/mali-c55/algorithms/blc.cpp           |   4 +-
>   src/ipa/mali-c55/algorithms/blc.h             |   2 +-
>   src/ipa/mali-c55/algorithms/lsc.cpp           |   6 +-
>   src/ipa/mali-c55/algorithms/lsc.h             |   2 +-
>   src/ipa/mali-c55/mali-c55.cpp                 |   2 +-
>   src/ipa/rkisp1/algorithms/agc.cpp             |  10 +-
>   src/ipa/rkisp1/algorithms/agc.h               |   4 +-
>   src/ipa/rkisp1/algorithms/awb.cpp             |   2 +-
>   src/ipa/rkisp1/algorithms/awb.h               |   2 +-
>   src/ipa/rkisp1/algorithms/blc.cpp             |   4 +-
>   src/ipa/rkisp1/algorithms/blc.h               |   2 +-
>   src/ipa/rkisp1/algorithms/ccm.cpp             |   4 +-
>   src/ipa/rkisp1/algorithms/ccm.h               |   4 +-
>   src/ipa/rkisp1/algorithms/cproc.cpp           |   2 +-
>   src/ipa/rkisp1/algorithms/cproc.h             |   2 +-
>   src/ipa/rkisp1/algorithms/dpcc.cpp            |  22 +-
>   src/ipa/rkisp1/algorithms/dpcc.h              |   2 +-
>   src/ipa/rkisp1/algorithms/dpf.cpp             |   8 +-
>   src/ipa/rkisp1/algorithms/dpf.h               |   2 +-
>   src/ipa/rkisp1/algorithms/filter.cpp          |   2 +-
>   src/ipa/rkisp1/algorithms/filter.h            |   2 +-
>   src/ipa/rkisp1/algorithms/goc.cpp             |   4 +-
>   src/ipa/rkisp1/algorithms/goc.h               |   2 +-
>   src/ipa/rkisp1/algorithms/gsl.cpp             |   6 +-
>   src/ipa/rkisp1/algorithms/gsl.h               |   2 +-
>   src/ipa/rkisp1/algorithms/lsc.cpp             |  14 +-
>   src/ipa/rkisp1/algorithms/lsc.h               |   2 +-
>   src/ipa/rkisp1/algorithms/lux.cpp             |   2 +-
>   src/ipa/rkisp1/algorithms/lux.h               |   2 +-
>   src/ipa/rkisp1/algorithms/wdr.cpp             |   4 +-
>   src/ipa/rkisp1/algorithms/wdr.h               |   2 +-
>   src/ipa/rkisp1/rkisp1.cpp                     |   2 +-
>   src/ipa/rpi/controller/algorithm.cpp          |   2 +-
>   src/ipa/rpi/controller/algorithm.h            |   4 +-
>   src/ipa/rpi/controller/controller.cpp         |   4 +-
>   src/ipa/rpi/controller/controller.h           |   4 +-
>   src/ipa/rpi/controller/rpi/af.cpp             |  10 +-
>   src/ipa/rpi/controller/rpi/af.h               |   8 +-
>   src/ipa/rpi/controller/rpi/agc.cpp            |   2 +-
>   src/ipa/rpi/controller/rpi/agc.h              |   2 +-
>   src/ipa/rpi/controller/rpi/agc_channel.cpp    |  24 +-
>   src/ipa/rpi/controller/rpi/agc_channel.h      |  12 +-
>   src/ipa/rpi/controller/rpi/alsc.cpp           |  10 +-
>   src/ipa/rpi/controller/rpi/alsc.h             |   2 +-
>   src/ipa/rpi/controller/rpi/awb.cpp            |  10 +-
>   src/ipa/rpi/controller/rpi/awb.h              |   8 +-
>   src/ipa/rpi/controller/rpi/black_level.cpp    |   2 +-
>   src/ipa/rpi/controller/rpi/black_level.h      |   2 +-
>   src/ipa/rpi/controller/rpi/cac.cpp            |   4 +-
>   src/ipa/rpi/controller/rpi/cac.h              |   2 +-
>   src/ipa/rpi/controller/rpi/ccm.cpp            |   2 +-
>   src/ipa/rpi/controller/rpi/ccm.h              |   2 +-
>   src/ipa/rpi/controller/rpi/contrast.cpp       |   2 +-
>   src/ipa/rpi/controller/rpi/contrast.h         |   2 +-
>   src/ipa/rpi/controller/rpi/decompand.cpp      |   2 +-
>   src/ipa/rpi/controller/rpi/decompand.h        |   2 +-
>   src/ipa/rpi/controller/rpi/denoise.cpp        |   4 +-
>   src/ipa/rpi/controller/rpi/denoise.h          |   4 +-
>   src/ipa/rpi/controller/rpi/dpc.cpp            |   2 +-
>   src/ipa/rpi/controller/rpi/dpc.h              |   2 +-
>   src/ipa/rpi/controller/rpi/geq.cpp            |   2 +-
>   src/ipa/rpi/controller/rpi/geq.h              |   2 +-
>   src/ipa/rpi/controller/rpi/hdr.cpp            |   4 +-
>   src/ipa/rpi/controller/rpi/hdr.h              |   4 +-
>   src/ipa/rpi/controller/rpi/lux.cpp            |   2 +-
>   src/ipa/rpi/controller/rpi/lux.h              |   2 +-
>   src/ipa/rpi/controller/rpi/noise.cpp          |   2 +-
>   src/ipa/rpi/controller/rpi/noise.h            |   2 +-
>   src/ipa/rpi/controller/rpi/saturation.cpp     |   2 +-
>   src/ipa/rpi/controller/rpi/saturation.h       |   2 +-
>   src/ipa/rpi/controller/rpi/sdn.cpp            |   2 +-
>   src/ipa/rpi/controller/rpi/sdn.h              |   2 +-
>   src/ipa/rpi/controller/rpi/sharpen.cpp        |   2 +-
>   src/ipa/rpi/controller/rpi/sharpen.h          |   2 +-
>   src/ipa/rpi/controller/rpi/tonemap.cpp        |   2 +-
>   src/ipa/rpi/controller/rpi/tonemap.h          |   2 +-
>   src/ipa/simple/algorithms/blc.cpp             |   2 +-
>   src/ipa/simple/algorithms/blc.h               |   2 +-
>   src/ipa/simple/algorithms/ccm.cpp             |   2 +-
>   src/ipa/simple/algorithms/ccm.h               |   2 +-
>   src/ipa/simple/algorithms/lut.cpp             |   2 +-
>   src/ipa/simple/algorithms/lut.h               |   2 +-
>   src/ipa/simple/soft_simple.cpp                |   2 +-
>   src/libcamera/converter/converter_dw100.cpp   |   2 +-
>   src/libcamera/geometry.cpp                    |   4 +-
>   src/libcamera/global_configuration.cpp        |   8 +-
>   src/libcamera/matrix.cpp                      |   2 +-
>   src/libcamera/meson.build                     |   2 +-
>   src/libcamera/pipeline/rkisp1/rkisp1.cpp      |   2 +-
>   .../pipeline/rpi/common/pipeline_base.cpp     |   4 +-
>   .../pipeline/rpi/common/pipeline_base.h       |   4 +-
>   src/libcamera/pipeline/rpi/pisp/pisp.cpp      |   6 +-
>   src/libcamera/pipeline/rpi/vc4/vc4.cpp        |   6 +-
>   src/libcamera/pipeline/virtual/README.md      |   2 +-
>   .../pipeline/virtual/config_parser.cpp        |  18 +-
>   .../pipeline/virtual/config_parser.h          |  12 +-
>   src/libcamera/pipeline/virtual/virtual.cpp    |   2 +-
>   src/libcamera/value_node.cpp                  | 484 ++++++++++++++++++
>   src/libcamera/vector.cpp                      |   2 +-
>   src/libcamera/yaml_object.cpp                 | 480 -----------------
>   src/libcamera/yaml_parser.cpp                 |  54 +-
>   test/yaml-parser.cpp                          |   6 +-
>   133 files changed, 807 insertions(+), 803 deletions(-)
>   rename include/libcamera/internal/{yaml_object.h => value_node.h} (81%)
>   create mode 100644 src/libcamera/value_node.cpp
>   delete mode 100644 src/libcamera/yaml_object.cpp
> 
> diff --git a/include/libcamera/internal/converter/converter_dw100.h b/include/libcamera/internal/converter/converter_dw100.h
> index d70c8fe76185..054750ef0866 100644
> --- a/include/libcamera/internal/converter/converter_dw100.h
> +++ b/include/libcamera/internal/converter/converter_dw100.h
> @@ -31,7 +31,7 @@ public:
>   
>   	static std::unique_ptr<ConverterDW100Module> createModule(DeviceEnumerator *enumerator);
>   
> -	int init(const YamlObject &params);
> +	int init(const ValueNode &params);
>   
>   	int configure(const StreamConfiguration &inputCfg,
>   		      const std::vector<std::reference_wrapper<StreamConfiguration>>
> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
> index 16c6a21f2a8a..7ae923977aa6 100644
> --- a/include/libcamera/internal/global_configuration.h
> +++ b/include/libcamera/internal/global_configuration.h
> @@ -14,14 +14,14 @@
>   
>   #include <libcamera/base/utils.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
>   class GlobalConfiguration
>   {
>   public:
> -	using Configuration = const YamlObject &;
> +	using Configuration = const ValueNode &;
>   
>   	GlobalConfiguration();
>   
> @@ -32,7 +32,7 @@ public:
>   	std::optional<T> option(
>   		const std::initializer_list<std::string_view> confPath) const
>   	{
> -		const YamlObject *c = &configuration();
> +		const ValueNode *c = &configuration();
>   		for (auto part : confPath) {
>   			c = &(*c)[part];
>   			if (!*c)
> @@ -55,8 +55,8 @@ private:
>   	bool loadFile(const std::filesystem::path &fileName);
>   	void load();
>   
> -	std::unique_ptr<YamlObject> yamlConfiguration_ =
> -		std::make_unique<YamlObject>();
> +	std::unique_ptr<ValueNode> yamlConfiguration_ =
> +		std::make_unique<ValueNode>();
>   };
>   
>   } /* namespace libcamera */
> diff --git a/include/libcamera/internal/matrix.h b/include/libcamera/internal/matrix.h
> index 11fccd27547e..0f72263f2536 100644
> --- a/include/libcamera/internal/matrix.h
> +++ b/include/libcamera/internal/matrix.h
> @@ -14,7 +14,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/span.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
> @@ -188,7 +188,7 @@ constexpr Matrix<T, Rows, Cols> operator+(const Matrix<T, Rows, Cols> &m1, const
>   }
>   
>   #ifndef __DOXYGEN__
> -bool matrixValidateYaml(const YamlObject &obj, unsigned int size);
> +bool matrixValidateYaml(const ValueNode &obj, unsigned int size);
>   #endif /* __DOXYGEN__ */
>   
>   #ifndef __DOXYGEN__
> @@ -200,8 +200,8 @@ std::ostream &operator<<(std::ostream &out, const Matrix<T, Rows, Cols> &m)
>   }
>   
>   template<typename T, unsigned int Rows, unsigned int Cols>
> -struct YamlObject::Accessor<Matrix<T, Rows, Cols>> {
> -	std::optional<Matrix<T, Rows, Cols>> get(const YamlObject &obj) const
> +struct ValueNode::Accessor<Matrix<T, Rows, Cols>> {
> +	std::optional<Matrix<T, Rows, Cols>> get(const ValueNode &obj) const
>   	{
>   		if (!matrixValidateYaml(obj, Rows * Cols))
>   			return std::nullopt;
> @@ -210,7 +210,7 @@ struct YamlObject::Accessor<Matrix<T, Rows, Cols>> {
>   		T *data = &matrix[0][0];
>   
>   		unsigned int i = 0;
> -		for (const YamlObject &entry : obj.asList()) {
> +		for (const ValueNode &entry : obj.asList()) {
>   			const auto value = entry.get<T>();
>   			if (!value)
>   				return std::nullopt;
> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> index c24c9f062f29..76ab43115768 100644
> --- a/include/libcamera/internal/meson.build
> +++ b/include/libcamera/internal/meson.build
> @@ -49,8 +49,8 @@ libcamera_internal_headers = files([
>       'v4l2_request.h',
>       'v4l2_subdevice.h',
>       'v4l2_videodevice.h',
> +    'value_node.h',
>       'vector.h',
> -    'yaml_object.h',
>       'yaml_parser.h',
>   ])
>   
> diff --git a/include/libcamera/internal/yaml_object.h b/include/libcamera/internal/value_node.h
> similarity index 81%
> rename from include/libcamera/internal/yaml_object.h
> rename to include/libcamera/internal/value_node.h
> index 1771f8821e2e..a336c1d08e1c 100644
> --- a/include/libcamera/internal/yaml_object.h
> +++ b/include/libcamera/internal/value_node.h
> @@ -3,7 +3,7 @@
>    * Copyright (C) 2022, Google Inc.
>    * Copyright (C) 2025, Ideas on Board
>    *
> - * libcamera YAML object
> + * Data structure to manage tree of values
>    */
>   
>   #pragma once
> @@ -22,16 +22,16 @@
>   
>   namespace libcamera {
>   
> -class YamlObject
> +class ValueNode
>   {
>   private:
>   	struct Value {
> -		Value(std::string &&k, std::unique_ptr<YamlObject> &&v)
> +		Value(std::string &&k, std::unique_ptr<ValueNode> &&v)
>   			: key(std::move(k)), value(std::move(v))
>   		{
>   		}
>   		std::string key;
> -		std::unique_ptr<YamlObject> value;
> +		std::unique_ptr<ValueNode> value;
>   	};
>   
>   	using ValueContainer = std::vector<Value>;
> @@ -103,8 +103,8 @@ public:
>   	class ListIterator : public Iterator<ListIterator>
>   	{
>   	public:
> -		using value_type = const YamlObject &;
> -		using pointer = const YamlObject *;
> +		using value_type = const ValueNode &;
> +		using pointer = const ValueNode *;
>   		using reference = value_type;
>   
>   		value_type operator*() const
> @@ -121,7 +121,7 @@ public:
>   	class DictIterator : public Iterator<DictIterator>
>   	{
>   	public:
> -		using value_type = std::pair<const std::string &, const YamlObject &>;
> +		using value_type = std::pair<const std::string &, const ValueNode &>;
>   		using pointer = value_type *;
>   		using reference = value_type &;
>   
> @@ -142,8 +142,8 @@ public:
>   	};
>   #endif /* __DOXYGEN__ */
>   
> -	YamlObject();
> -	~YamlObject();
> +	ValueNode();
> +	~ValueNode();
>   
>   	bool isValue() const
>   	{
> @@ -189,16 +189,16 @@ public:
>   	DictAdapter asDict() const { return DictAdapter{ list_ }; }
>   	ListAdapter asList() const { return ListAdapter{ list_ }; }
>   
> -	const YamlObject &operator[](std::size_t index) const;
> +	const ValueNode &operator[](std::size_t index) const;
>   
>   	bool contains(std::string_view key) const;
> -	const YamlObject &operator[](std::string_view key) const;
> +	const ValueNode &operator[](std::string_view key) const;
>   
> -	YamlObject *add(std::unique_ptr<YamlObject> child);
> -	YamlObject *add(std::string key, std::unique_ptr<YamlObject> child);
> +	ValueNode *add(std::unique_ptr<ValueNode> child);
> +	ValueNode *add(std::string key, std::unique_ptr<ValueNode> child);
>   
>   private:
> -	LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject)
> +	LIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)
>   
>   	template<typename T>
>   	friend struct Accessor;
> @@ -212,15 +212,15 @@ private:
>   
>   	template<typename T, typename Enable = void>
>   	struct Accessor {
> -		std::optional<T> get(const YamlObject &obj) const;
> -		void set(YamlObject &obj, T value);
> +		std::optional<T> get(const ValueNode &obj) const;
> +		void set(ValueNode &obj, T value);
>   	};
>   
>   	Type type_;
>   
>   	std::string value_;
>   	ValueContainer list_;
> -	std::map<std::string, YamlObject *, std::less<>> dictionary_;
> +	std::map<std::string, ValueNode *, std::less<>> dictionary_;
>   };
>   
>   } /* namespace libcamera */
> diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
> index af24485f3bb1..ed7490e16e9e 100644
> --- a/include/libcamera/internal/vector.h
> +++ b/include/libcamera/internal/vector.h
> @@ -19,7 +19,7 @@
>   #include <libcamera/base/span.h>
>   
>   #include "libcamera/internal/matrix.h"
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
> @@ -329,7 +329,7 @@ bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
>   }
>   
>   #ifndef __DOXYGEN__
> -bool vectorValidateYaml(const YamlObject &obj, unsigned int size);
> +bool vectorValidateYaml(const ValueNode &obj, unsigned int size);
>   #endif /* __DOXYGEN__ */
>   
>   #ifndef __DOXYGEN__
> @@ -347,8 +347,8 @@ std::ostream &operator<<(std::ostream &out, const Vector<T, Rows> &v)
>   }
>   
>   template<typename T, unsigned int Rows>
> -struct YamlObject::Accessor<Vector<T, Rows>> {
> -	std::optional<Vector<T, Rows>> get(const YamlObject &obj) const
> +struct ValueNode::Accessor<Vector<T, Rows>> {
> +	std::optional<Vector<T, Rows>> get(const ValueNode &obj) const
>   	{
>   		if (!vectorValidateYaml(obj, Rows))
>   			return std::nullopt;
> @@ -356,7 +356,7 @@ struct YamlObject::Accessor<Vector<T, Rows>> {
>   		Vector<T, Rows> vector;
>   
>   		unsigned int i = 0;
> -		for (const YamlObject &entry : obj.asList()) {
> +		for (const ValueNode &entry : obj.asList()) {
>   			const auto value = entry.get<T>();
>   			if (!value)
>   				return std::nullopt;
> diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
> index e503e83a80da..9d4ba0a1d0e1 100644
> --- a/include/libcamera/internal/yaml_parser.h
> +++ b/include/libcamera/internal/yaml_parser.h
> @@ -9,7 +9,7 @@
>   
>   #include <memory>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
> @@ -18,7 +18,7 @@ class File;
>   class YamlParser final
>   {
>   public:
> -	static std::unique_ptr<YamlObject> parse(File &file);
> +	static std::unique_ptr<ValueNode> parse(File &file);
>   };
>   
>   } /* namespace libcamera */
> diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> index 7ef451ef8ab9..4a9c0577a0be 100644
> --- a/src/android/camera_hal_config.cpp
> +++ b/src/android/camera_hal_config.cpp
> @@ -30,9 +30,9 @@ public:
>   	int parseConfigFile(File &file, std::map<std::string, CameraConfigData> *cameras);
>   
>   private:
> -	int parseCameraConfigData(const std::string &cameraId, const YamlObject &);
> -	int parseLocation(const YamlObject &, CameraConfigData &cameraConfigData);
> -	int parseRotation(const YamlObject &, CameraConfigData &cameraConfigData);
> +	int parseCameraConfigData(const std::string &cameraId, const ValueNode &);
> +	int parseLocation(const ValueNode &, CameraConfigData &cameraConfigData);
> +	int parseRotation(const ValueNode &, CameraConfigData &cameraConfigData);
>   
>   	std::map<std::string, CameraConfigData> *cameras_;
>   };
> @@ -65,7 +65,7 @@ int CameraHalConfig::Private::parseConfigFile(File &file,
>   
>   	cameras_ = cameras;
>   
> -	std::unique_ptr<YamlObject> root = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> root = YamlParser::parse(file);
>   	if (!root)
>   		return -EINVAL;
>   
> @@ -76,7 +76,7 @@ int CameraHalConfig::Private::parseConfigFile(File &file,
>   	if (!root->contains("cameras"))
>   		return -EINVAL;
>   
> -	const YamlObject &yamlObjectCameras = (*root)["cameras"];
> +	const ValueNode &yamlObjectCameras = (*root)["cameras"];
>   
>   	if (!yamlObjectCameras.isDictionary())
>   		return -EINVAL;
> @@ -90,7 +90,7 @@ int CameraHalConfig::Private::parseConfigFile(File &file,
>   }
>   
>   int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId,
> -						    const YamlObject &cameraObject)
> +						    const ValueNode &cameraObject)
>   
>   {
>   	if (!cameraObject.isDictionary())
> @@ -109,7 +109,7 @@ int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId,
>   	return 0;
>   }
>   
> -int CameraHalConfig::Private::parseLocation(const YamlObject &cameraObject,
> +int CameraHalConfig::Private::parseLocation(const ValueNode &cameraObject,
>   					    CameraConfigData &cameraConfigData)
>   {
>   	if (!cameraObject.contains("location"))
> @@ -127,7 +127,7 @@ int CameraHalConfig::Private::parseLocation(const YamlObject &cameraObject,
>   	return 0;
>   }
>   
> -int CameraHalConfig::Private::parseRotation(const YamlObject &cameraObject,
> +int CameraHalConfig::Private::parseRotation(const ValueNode &cameraObject,
>   					    CameraConfigData &cameraConfigData)
>   {
>   	if (!cameraObject.contains("rotation"))
> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
> index b0d89541da85..d6a7036c6504 100644
> --- a/src/ipa/ipu3/algorithms/agc.cpp
> +++ b/src/ipa/ipu3/algorithms/agc.cpp
> @@ -65,14 +65,14 @@ Agc::Agc()
>   /**
>    * \brief Initialise the AGC algorithm from tuning files
>    * \param[in] context The shared IPA context
> - * \param[in] tuningData The YamlObject containing Agc tuning data
> + * \param[in] tuningData The ValueNode containing Agc tuning data
>    *
>    * This function calls the base class' tuningData parsers to discover which
>    * control values are supported.
>    *
>    * \return 0 on success or errors from the base class
>    */
> -int Agc::init(IPAContext &context, const YamlObject &tuningData)
> +int Agc::init(IPAContext &context, const ValueNode &tuningData)
>   {
>   	int ret;
>   
> diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h
> index 890c271b4462..d274a2350485 100644
> --- a/src/ipa/ipu3/algorithms/agc.h
> +++ b/src/ipa/ipu3/algorithms/agc.h
> @@ -30,7 +30,7 @@ public:
>   	Agc();
>   	~Agc() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
>   	void process(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index b926f579a9a3..09f6aeb9400d 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -324,7 +324,7 @@ int IPAIPU3::init(const IPASettings &settings,
>   		return ret;
>   	}
>   
> -	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
>   	if (!data)
>   		return -EINVAL;
>   
> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
> index 72988096d384..1d385551adc6 100644
> --- a/src/ipa/libipa/agc_mean_luminance.cpp
> +++ b/src/ipa/libipa/agc_mean_luminance.cpp
> @@ -159,7 +159,7 @@ AgcMeanLuminance::AgcMeanLuminance()
>   
>   AgcMeanLuminance::~AgcMeanLuminance() = default;
>   
> -int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
> +int AgcMeanLuminance::parseRelativeLuminanceTarget(const ValueNode &tuningData)
>   {
>   	auto &target = tuningData["relativeLuminanceTarget"];
>   	if (!target) {
> @@ -178,7 +178,7 @@ int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
>   	return 0;
>   }
>   
> -int AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id)
> +int AgcMeanLuminance::parseConstraint(const ValueNode &modeDict, int32_t id)
>   {
>   	for (const auto &[boundName, content] : modeDict.asDict()) {
>   		if (boundName != "upper" && boundName != "lower") {
> @@ -212,11 +212,11 @@ int AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id)
>   	return 0;
>   }
>   
> -int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData)
> +int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData)
>   {
>   	std::vector<ControlValue> availableConstraintModes;
>   
> -	const YamlObject &yamlConstraintModes = tuningData[controls::AeConstraintMode.name()];
> +	const ValueNode &yamlConstraintModes = tuningData[controls::AeConstraintMode.name()];
>   	if (yamlConstraintModes.isDictionary()) {
>   		for (const auto &[modeName, modeDict] : yamlConstraintModes.asDict()) {
>   			if (AeConstraintModeNameValueMap.find(modeName) ==
> @@ -267,11 +267,11 @@ int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData)
>   	return 0;
>   }
>   
> -int AgcMeanLuminance::parseExposureModes(const YamlObject &tuningData)
> +int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData)
>   {
>   	std::vector<ControlValue> availableExposureModes;
>   
> -	const YamlObject &yamlExposureModes = tuningData[controls::AeExposureMode.name()];
> +	const ValueNode &yamlExposureModes = tuningData[controls::AeExposureMode.name()];
>   	if (yamlExposureModes.isDictionary()) {
>   		for (const auto &[modeName, modeValues] : yamlExposureModes.asDict()) {
>   			if (AeExposureModeNameValueMap.find(modeName) ==
> @@ -361,7 +361,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration,
>   
>   /**
>    * \brief Parse tuning data for AeConstraintMode and AeExposureMode controls
> - * \param[in] tuningData the YamlObject representing the tuning data
> + * \param[in] tuningData the ValueNode representing the tuning data
>    *
>    * This function parses tuning data to build the list of allowed values for the
>    * AeConstraintMode and AeExposureMode controls. Those tuning data must provide
> @@ -414,7 +414,7 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration,
>    *
>    * \return 0 on success or a negative error code
>    */
> -int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
> +int AgcMeanLuminance::parseTuningData(const ValueNode &tuningData)
>   {
>   	int ret;
>   
> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
> index dfe1ccbe948b..27e92b6fce7a 100644
> --- a/src/ipa/libipa/agc_mean_luminance.h
> +++ b/src/ipa/libipa/agc_mean_luminance.h
> @@ -16,7 +16,7 @@
>   
>   #include <libcamera/controls.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "exposure_mode_helper.h"
>   #include "histogram.h"
> @@ -44,7 +44,7 @@ public:
>   	};
>   
>   	void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper);
> -	int parseTuningData(const YamlObject &tuningData);
> +	int parseTuningData(const ValueNode &tuningData);
>   
>   	void setExposureCompensation(double gain)
>   	{
> @@ -88,10 +88,10 @@ public:
>   private:
>   	virtual double estimateLuminance(const double gain) const = 0;
>   
> -	int parseRelativeLuminanceTarget(const YamlObject &tuningData);
> -	int parseConstraint(const YamlObject &modeDict, int32_t id);
> -	int parseConstraintModes(const YamlObject &tuningData);
> -	int parseExposureModes(const YamlObject &tuningData);
> +	int parseRelativeLuminanceTarget(const ValueNode &tuningData);
> +	int parseConstraint(const ValueNode &modeDict, int32_t id);
> +	int parseConstraintModes(const ValueNode &tuningData);
> +	int parseExposureModes(const ValueNode &tuningData);
>   	double estimateInitialGain() const;
>   	double constraintClampGain(uint32_t constraintModeIndex,
>   				   const Histogram &hist,
> diff --git a/src/ipa/libipa/algorithm.cpp b/src/ipa/libipa/algorithm.cpp
> index 201efdfdba25..757ce3519652 100644
> --- a/src/ipa/libipa/algorithm.cpp
> +++ b/src/ipa/libipa/algorithm.cpp
> @@ -44,7 +44,7 @@ namespace ipa {
>    * \param[in] tuningData The tuning data for the algorithm
>    *
>    * This function is called once, when the IPA module is initialized, to
> - * initialize the algorithm. The \a tuningData YamlObject contains the tuning
> + * initialize the algorithm. The \a tuningData ValueNode contains the tuning
>    * data for algorithm.
>    *
>    * \return 0 if successful, an error code otherwise
> diff --git a/src/ipa/libipa/algorithm.h b/src/ipa/libipa/algorithm.h
> index 9a19dbd61b31..4ddb16ef3920 100644
> --- a/src/ipa/libipa/algorithm.h
> +++ b/src/ipa/libipa/algorithm.h
> @@ -14,7 +14,7 @@
>   
>   namespace libcamera {
>   
> -class YamlObject;
> +class ValueNode;
>   
>   namespace ipa {
>   
> @@ -27,7 +27,7 @@ public:
>   	virtual ~Algorithm() {}
>   
>   	virtual int init([[maybe_unused]] typename Module::Context &context,
> -			 [[maybe_unused]] const YamlObject &tuningData)
> +			 [[maybe_unused]] const ValueNode &tuningData)
>   	{
>   		return 0;
>   	}
> diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
> index 214bac8b56a6..af966f1e007c 100644
> --- a/src/ipa/libipa/awb.cpp
> +++ b/src/ipa/libipa/awb.cpp
> @@ -132,7 +132,7 @@ namespace ipa {
>   
>   /**
>    * \brief Parse the mode configurations from the tuning data
> - * \param[in] tuningData the YamlObject representing the tuning data
> + * \param[in] tuningData the ValueNode representing the tuning data
>    * \param[in] def The default value for the AwbMode control
>    *
>    * Utility function to parse the tuning data for an AwbMode entry and read all
> @@ -162,12 +162,12 @@ namespace ipa {
>    * \sa controls::AwbModeEnum
>    * \return Zero on success, negative error code otherwise
>    */
> -int AwbAlgorithm::parseModeConfigs(const YamlObject &tuningData,
> +int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
>   				   const ControlValue &def)
>   {
>   	std::vector<ControlValue> availableModes;
>   
> -	const YamlObject &yamlModes = tuningData[controls::AwbMode.name()];
> +	const ValueNode &yamlModes = tuningData[controls::AwbMode.name()];
>   	if (!yamlModes.isDictionary()) {
>   		LOG(Awb, Error)
>   			<< "AwbModes must be a dictionary.";
> diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
> index 3f25d13feaa8..09c00e47d604 100644
> --- a/src/ipa/libipa/awb.h
> +++ b/src/ipa/libipa/awb.h
> @@ -13,8 +13,8 @@
>   #include <libcamera/control_ids.h>
>   #include <libcamera/controls.h>
>   
> +#include "libcamera/internal/value_node.h"
>   #include "libcamera/internal/vector.h"
> -#include "libcamera/internal/yaml_object.h"
>   
>   namespace libcamera {
>   
> @@ -38,7 +38,7 @@ class AwbAlgorithm
>   public:
>   	virtual ~AwbAlgorithm() = default;
>   
> -	virtual int init(const YamlObject &tuningData) = 0;
> +	virtual int init(const ValueNode &tuningData) = 0;
>   	virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
>   	virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
>   
> @@ -50,7 +50,7 @@ public:
>   	virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
>   
>   protected:
> -	int parseModeConfigs(const YamlObject &tuningData,
> +	int parseModeConfigs(const ValueNode &tuningData,
>   			     const ControlValue &def = {});
>   
>   	struct ModeConfig {
> diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
> index 595bd0705732..a1412c8bd2b5 100644
> --- a/src/ipa/libipa/awb_bayes.cpp
> +++ b/src/ipa/libipa/awb_bayes.cpp
> @@ -143,7 +143,7 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
>    * \brief The currently selected mode
>    */
>   
> -int AwbBayes::init(const YamlObject &tuningData)
> +int AwbBayes::init(const ValueNode &tuningData)
>   {
>   	int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
>   	if (ret) {
> @@ -188,7 +188,7 @@ int AwbBayes::init(const YamlObject &tuningData)
>   	return 0;
>   }
>   
> -int AwbBayes::readPriors(const YamlObject &tuningData)
> +int AwbBayes::readPriors(const ValueNode &tuningData)
>   {
>   	const auto &priorsList = tuningData["priors"];
>   	std::map<uint32_t, Pwl> priors;
> diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
> index 79334ad3e7a3..1e3373676bc0 100644
> --- a/src/ipa/libipa/awb_bayes.h
> +++ b/src/ipa/libipa/awb_bayes.h
> @@ -9,8 +9,8 @@
>   
>   #include <libcamera/controls.h>
>   
> +#include "libcamera/internal/value_node.h"
>   #include "libcamera/internal/vector.h"
> -#include "libcamera/internal/yaml_object.h"
>   
>   #include "awb.h"
>   #include "interpolator.h"
> @@ -25,13 +25,13 @@ class AwbBayes : public AwbAlgorithm
>   public:
>   	AwbBayes() = default;
>   
> -	int init(const YamlObject &tuningData) override;
> +	int init(const ValueNode &tuningData) override;
>   	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
>   	std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
>   	void handleControls(const ControlList &controls) override;
>   
>   private:
> -	int readPriors(const YamlObject &tuningData);
> +	int readPriors(const ValueNode &tuningData);
>   
>   	void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
>   			const AwbStats &stats) const;
> diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
> index d252edb2b6c6..b14b096810ae 100644
> --- a/src/ipa/libipa/awb_grey.cpp
> +++ b/src/ipa/libipa/awb_grey.cpp
> @@ -41,7 +41,7 @@ namespace ipa {
>    *
>    * \return 0 on success, a negative error code otherwise
>    */
> -int AwbGrey::init(const YamlObject &tuningData)
> +int AwbGrey::init(const ValueNode &tuningData)
>   {
>   	Interpolator<Vector<double, 2>> gains;
>   	int ret = gains.readYaml(tuningData["colourGains"], "ct", "gains");
> diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
> index f82a368d11cc..154a2af97f15 100644
> --- a/src/ipa/libipa/awb_grey.h
> +++ b/src/ipa/libipa/awb_grey.h
> @@ -23,7 +23,7 @@ class AwbGrey : public AwbAlgorithm
>   public:
>   	AwbGrey() = default;
>   
> -	int init(const YamlObject &tuningData) override;
> +	int init(const ValueNode &tuningData) override;
>   	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
>   	std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
>   
> diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp
> index 9355802f796a..6bd83849262e 100644
> --- a/src/ipa/libipa/interpolator.cpp
> +++ b/src/ipa/libipa/interpolator.cpp
> @@ -53,7 +53,7 @@ namespace ipa {
>    */
>   
>   /**
> - * \fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml,
> + * \fn int Interpolator<T>::readYaml(const ValueNode &yaml,
>   		                     const std::string &key_name,
>   		                     const std::string &value_name)
>    * \brief Initialize an Interpolator instance from yaml
> diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h
> index 08003a06dbba..c67091e21ca6 100644
> --- a/src/ipa/libipa/interpolator.h
> +++ b/src/ipa/libipa/interpolator.h
> @@ -15,7 +15,7 @@
>   
>   #include <libcamera/base/log.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
> @@ -39,7 +39,7 @@ public:
>   
>   	~Interpolator() = default;
>   
> -	int readYaml(const libcamera::YamlObject &yaml,
> +	int readYaml(const ValueNode &yaml,
>   		     const std::string &key_name,
>   		     const std::string &value_name)
>   	{
> diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
> index 19da46860849..d7d9ae42e360 100644
> --- a/src/ipa/libipa/lsc_polynomial.h
> +++ b/src/ipa/libipa/lsc_polynomial.h
> @@ -16,7 +16,7 @@
>   
>   #include <libcamera/geometry.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
> @@ -83,8 +83,8 @@ private:
>   #ifndef __DOXYGEN__
>   
>   template<>
> -struct YamlObject::Accessor<ipa::LscPolynomial> {
> -	std::optional<ipa::LscPolynomial> get(const YamlObject &obj) const
> +struct ValueNode::Accessor<ipa::LscPolynomial> {
> +	std::optional<ipa::LscPolynomial> get(const ValueNode &obj) const
>   	{
>   		std::optional<double> cx = obj["cx"].get<double>();
>   		std::optional<double> cy = obj["cy"].get<double>();
> diff --git a/src/ipa/libipa/lux.cpp b/src/ipa/libipa/lux.cpp
> index d79b116a3339..46cc63a115b5 100644
> --- a/src/ipa/libipa/lux.cpp
> +++ b/src/ipa/libipa/lux.cpp
> @@ -12,7 +12,7 @@
>   
>   #include <libcamera/base/log.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "histogram.h"
>   
> @@ -78,7 +78,7 @@ Lux::Lux()
>   
>   /**
>    * \brief Parse tuning data
> - * \param[in] tuningData The YamlObject representing the tuning data
> + * \param[in] tuningData The ValueNode representing the tuning data
>    *
>    * This function parses yaml tuning data for the common Lux module. It requires
>    * reference exposure time, analogue gain, digital gain, and lux values.
> @@ -95,7 +95,7 @@ Lux::Lux()
>    *
>    * \return 0 on success or a negative error code
>    */
> -int Lux::parseTuningData(const YamlObject &tuningData)
> +int Lux::parseTuningData(const ValueNode &tuningData)
>   {
>   	auto value = tuningData["referenceExposureTime"].get<double>();
>   	if (!value) {
> diff --git a/src/ipa/libipa/lux.h b/src/ipa/libipa/lux.h
> index d95bcdafcfcd..b6837ab729de 100644
> --- a/src/ipa/libipa/lux.h
> +++ b/src/ipa/libipa/lux.h
> @@ -12,7 +12,7 @@
>   
>   namespace libcamera {
>   
> -class YamlObject;
> +class ValueNode;
>   
>   namespace ipa {
>   
> @@ -23,7 +23,7 @@ class Lux
>   public:
>   	Lux();
>   
> -	int parseTuningData(const YamlObject &tuningData);
> +	int parseTuningData(const ValueNode &tuningData);
>   	double estimateLux(utils::Duration exposureTime,
>   			   double aGain, double dGain,
>   			   const Histogram &yHist) const;
> diff --git a/src/ipa/libipa/module.cpp b/src/ipa/libipa/module.cpp
> index a95dca696adf..76e10e250b73 100644
> --- a/src/ipa/libipa/module.cpp
> +++ b/src/ipa/libipa/module.cpp
> @@ -87,7 +87,7 @@ namespace ipa {
>    * \fn Module::createAlgorithms()
>    * \brief Create algorithms from YAML configuration data
>    * \param[in] context The IPA context
> - * \param[in] algorithms Algorithms configuration data as a parsed YamlObject
> + * \param[in] algorithms Algorithms configuration data as a parsed ValueNode
>    *
>    * This function iterates over the list of \a algorithms parsed from the YAML
>    * configuration file, and instantiates and initializes the corresponding
> diff --git a/src/ipa/libipa/module.h b/src/ipa/libipa/module.h
> index 8e6c658a6b63..3e2408ca30b6 100644
> --- a/src/ipa/libipa/module.h
> +++ b/src/ipa/libipa/module.h
> @@ -15,7 +15,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/utils.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "algorithm.h"
>   
> @@ -43,7 +43,7 @@ public:
>   		return algorithms_;
>   	}
>   
> -	int createAlgorithms(Context &context, const YamlObject &algorithms)
> +	int createAlgorithms(Context &context, const ValueNode &algorithms)
>   	{
>   		const auto &list = algorithms.asList();
>   
> @@ -71,7 +71,7 @@ public:
>   	}
>   
>   private:
> -	int createAlgorithm(Context &context, const YamlObject &data)
> +	int createAlgorithm(Context &context, const ValueNode &data)
>   	{
>   		const auto &[name, algoData] = *data.asDict().begin();
>   
> diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp
> index f38573e69c13..9fddd5bf70e2 100644
> --- a/src/ipa/libipa/pwl.cpp
> +++ b/src/ipa/libipa/pwl.cpp
> @@ -435,7 +435,7 @@ std::string Pwl::toString() const
>    */
>   template<>
>   std::optional<ipa::Pwl>
> -YamlObject::Accessor<ipa::Pwl>::get(const YamlObject &obj) const
> +ValueNode::Accessor<ipa::Pwl>::get(const ValueNode &obj) const
>   {
>   	/* Treat a single value as single point PWL. */
>   	if (obj.isValue()) {
> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp
> index 014fd12452ac..e4ac735ccabc 100644
> --- a/src/ipa/mali-c55/algorithms/agc.cpp
> +++ b/src/ipa/mali-c55/algorithms/agc.cpp
> @@ -131,7 +131,7 @@ Agc::Agc()
>   {
>   }
>   
> -int Agc::init(IPAContext &context, const YamlObject &tuningData)
> +int Agc::init(IPAContext &context, const ValueNode &tuningData)
>   {
>   	int ret = parseTuningData(tuningData);
>   	if (ret)
> diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h
> index 9684fff664bc..ee913de2b2e2 100644
> --- a/src/ipa/mali-c55/algorithms/agc.h
> +++ b/src/ipa/mali-c55/algorithms/agc.h
> @@ -49,7 +49,7 @@ public:
>   	Agc();
>   	~Agc() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPACameraSensorInfo &configInfo) override;
>   	void queueRequest(IPAContext &context, const uint32_t frame,
> diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp
> index 3bdf19141e1d..591a5eaf2dc8 100644
> --- a/src/ipa/mali-c55/algorithms/blc.cpp
> +++ b/src/ipa/mali-c55/algorithms/blc.cpp
> @@ -10,7 +10,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/control_ids.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   /**
>    * \file blc.h
> @@ -36,7 +36,7 @@ BlackLevelCorrection::BlackLevelCorrection()
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> -			       const YamlObject &tuningData)
> +			       const ValueNode &tuningData)
>   {
>   	offset00 = tuningData["offset00"].get<uint32_t>(0);
>   	offset01 = tuningData["offset01"].get<uint32_t>(0);
> diff --git a/src/ipa/mali-c55/algorithms/blc.h b/src/ipa/mali-c55/algorithms/blc.h
> index fc5a7ea310cb..bce343e20c55 100644
> --- a/src/ipa/mali-c55/algorithms/blc.h
> +++ b/src/ipa/mali-c55/algorithms/blc.h
> @@ -18,7 +18,7 @@ public:
>   	BlackLevelCorrection();
>   	~BlackLevelCorrection() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPACameraSensorInfo &configInfo) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
> diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
> index e32f5a83485e..fe230fdff418 100644
> --- a/src/ipa/mali-c55/algorithms/lsc.cpp
> +++ b/src/ipa/mali-c55/algorithms/lsc.cpp
> @@ -7,7 +7,7 @@
>   
>   #include "lsc.h"
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   namespace libcamera {
>   
> @@ -15,7 +15,7 @@ namespace ipa::mali_c55::algorithms {
>   
>   LOG_DEFINE_CATEGORY(MaliC55Lsc)
>   
> -int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
> +int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>   {
>   	if (!tuningData.contains("meshScale")) {
>   		LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
> @@ -24,7 +24,7 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
>   
>   	meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
>   
> -	const YamlObject &yamlSets = tuningData["sets"];
> +	const ValueNode &yamlSets = tuningData["sets"];
>   	if (!yamlSets.isList()) {
>   		LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
>   		return -EINVAL;
> diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
> index e7092bc74a0b..3d35fd72bfa8 100644
> --- a/src/ipa/mali-c55/algorithms/lsc.h
> +++ b/src/ipa/mali-c55/algorithms/lsc.h
> @@ -20,7 +20,7 @@ public:
>   	Lsc() = default;
>   	~Lsc() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
>   		     MaliC55Params *params) override;
> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> index 1d2a4f75cf8c..e4637548ac4d 100644
> --- a/src/ipa/mali-c55/mali-c55.cpp
> +++ b/src/ipa/mali-c55/mali-c55.cpp
> @@ -118,7 +118,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
>   		return ret;
>   	}
>   
> -	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
>   	if (!data)
>   		return -EINVAL;
>   
> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> index ef16a3775056..523930488a3b 100644
> --- a/src/ipa/rkisp1/algorithms/agc.cpp
> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> @@ -19,7 +19,7 @@
>   #include <libcamera/control_ids.h>
>   #include <libcamera/ipa/core_ipa_interface.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "libipa/histogram.h"
>   
> @@ -40,7 +40,7 @@ namespace ipa::rkisp1::algorithms {
>   
>   LOG_DEFINE_CATEGORY(RkISP1Agc)
>   
> -int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData)
> +int Agc::parseMeteringModes(IPAContext &context, const ValueNode &tuningData)
>   {
>   	if (!tuningData.isDictionary())
>   		LOG(RkISP1Agc, Warning)
> @@ -127,14 +127,14 @@ Agc::Agc()
>   /**
>    * \brief Initialise the AGC algorithm from tuning files
>    * \param[in] context The shared IPA context
> - * \param[in] tuningData The YamlObject containing Agc tuning data
> + * \param[in] tuningData The ValueNode containing Agc tuning data
>    *
>    * This function calls the base class' tuningData parsers to discover which
>    * control values are supported.
>    *
>    * \return 0 on success or errors from the base class
>    */
> -int Agc::init(IPAContext &context, const YamlObject &tuningData)
> +int Agc::init(IPAContext &context, const ValueNode &tuningData)
>   {
>   	int ret;
>   
> @@ -142,7 +142,7 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
>   	if (ret)
>   		return ret;
>   
> -	const YamlObject &yamlMeteringModes = tuningData["AeMeteringMode"];
> +	const ValueNode &yamlMeteringModes = tuningData["AeMeteringMode"];
>   	ret = parseMeteringModes(context, yamlMeteringModes);
>   	if (ret)
>   		return ret;
> diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
> index 7867eed9c4e3..dec79f2f399c 100644
> --- a/src/ipa/rkisp1/algorithms/agc.h
> +++ b/src/ipa/rkisp1/algorithms/agc.h
> @@ -28,7 +28,7 @@ public:
>   	Agc();
>   	~Agc() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
>   	void queueRequest(IPAContext &context,
>   			  const uint32_t frame,
> @@ -43,7 +43,7 @@ public:
>   		     ControlList &metadata) override;
>   
>   private:
> -	int parseMeteringModes(IPAContext &context, const YamlObject &tuningData);
> +	int parseMeteringModes(IPAContext &context, const ValueNode &tuningData);
>   	uint8_t computeHistogramPredivider(const Size &size,
>   					   enum rkisp1_cif_isp_histogram_mode mode);
>   
> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> index e8da7974a1d6..1d248beba61f 100644
> --- a/src/ipa/rkisp1/algorithms/awb.cpp
> +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> @@ -84,7 +84,7 @@ Awb::Awb()
>   /**
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
> -int Awb::init(IPAContext &context, const YamlObject &tuningData)
> +int Awb::init(IPAContext &context, const ValueNode &tuningData)
>   {
>   	auto &cmap = context.ctrlMap;
>   	cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
> diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
> index 02651cc732bf..60d9ef111495 100644
> --- a/src/ipa/rkisp1/algorithms/awb.h
> +++ b/src/ipa/rkisp1/algorithms/awb.h
> @@ -24,7 +24,7 @@ public:
>   	Awb();
>   	~Awb() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
>   	void queueRequest(IPAContext &context, const uint32_t frame,
>   			  IPAFrameContext &frameContext,
> diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> index 19c262fffa73..1ed700a205c8 100644
> --- a/src/ipa/rkisp1/algorithms/blc.cpp
> +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> @@ -13,7 +13,7 @@
>   
>   #include <libcamera/control_ids.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   /**
>    * \file blc.h
> @@ -53,7 +53,7 @@ BlackLevelCorrection::BlackLevelCorrection()
>   /**
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
> -int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData)
> +int BlackLevelCorrection::init(IPAContext &context, const ValueNode &tuningData)
>   {
>   	std::optional<int16_t> levelRed = tuningData["R"].get<int16_t>();
>   	std::optional<int16_t> levelGreenR = tuningData["Gr"].get<int16_t>();
> diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h
> index f797ae44d639..3b2b0ce6e2a8 100644
> --- a/src/ipa/rkisp1/algorithms/blc.h
> +++ b/src/ipa/rkisp1/algorithms/blc.h
> @@ -19,7 +19,7 @@ public:
>   	BlackLevelCorrection();
>   	~BlackLevelCorrection() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPACameraSensorInfo &configInfo) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
> diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
> index 3ed307280677..710989de1d74 100644
> --- a/src/ipa/rkisp1/algorithms/ccm.cpp
> +++ b/src/ipa/rkisp1/algorithms/ccm.cpp
> @@ -16,7 +16,7 @@
>   
>   #include <libcamera/ipa/core_ipa_interface.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "libipa/fixedpoint.h"
>   #include "libipa/interpolator.h"
> @@ -41,7 +41,7 @@ constexpr Matrix<float, 3, 3> kIdentity3x3 = Matrix<float, 3, 3>::identity();
>   /**
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
> -int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
> +int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>   {
>   	auto &cmap = context.ctrlMap;
>   	cmap[&controls::ColourCorrectionMatrix] = ControlInfo(
> diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h
> index c301e6e531c8..9ac537426d16 100644
> --- a/src/ipa/rkisp1/algorithms/ccm.h
> +++ b/src/ipa/rkisp1/algorithms/ccm.h
> @@ -25,7 +25,7 @@ public:
>   	Ccm() {}
>   	~Ccm() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPACameraSensorInfo &configInfo) override;
>   	void queueRequest(IPAContext &context,
> @@ -41,7 +41,7 @@ public:
>   		     ControlList &metadata) override;
>   
>   private:
> -	void parseYaml(const YamlObject &tuningData);
> +	void parseYaml(const ValueNode &tuningData);
>   	void setParameters(struct rkisp1_cif_isp_ctk_config &config,
>   			   const Matrix<float, 3, 3> &matrix,
>   			   const Matrix<int16_t, 3, 1> &offsets);
> diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp
> index d1fff6990d37..ae3effacd359 100644
> --- a/src/ipa/rkisp1/algorithms/cproc.cpp
> +++ b/src/ipa/rkisp1/algorithms/cproc.cpp
> @@ -55,7 +55,7 @@ int convertContrastOrSaturation(const float v)
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int ColorProcessing::init(IPAContext &context,
> -			  [[maybe_unused]] const YamlObject &tuningData)
> +			  [[maybe_unused]] const ValueNode &tuningData)
>   {
>   	auto &cmap = context.ctrlMap;
>   
> diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h
> index fd38fd17e8bb..a3863b94fb6e 100644
> --- a/src/ipa/rkisp1/algorithms/cproc.h
> +++ b/src/ipa/rkisp1/algorithms/cproc.h
> @@ -21,7 +21,7 @@ public:
>   	ColorProcessing() = default;
>   	~ColorProcessing() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPACameraSensorInfo &configInfo) override;
>   	void queueRequest(IPAContext &context, const uint32_t frame,
> diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp
> index c195334750e1..eb8cbf2049c5 100644
> --- a/src/ipa/rkisp1/algorithms/dpcc.cpp
> +++ b/src/ipa/rkisp1/algorithms/dpcc.cpp
> @@ -9,7 +9,7 @@
>   
>   #include <libcamera/base/log.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "linux/rkisp1-config.h"
>   
> @@ -45,7 +45,7 @@ DefectPixelClusterCorrection::DefectPixelClusterCorrection()
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
> -				       const YamlObject &tuningData)
> +				       const ValueNode &tuningData)
>   {
>   	config_.mode = RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE;
>   	config_.output_mode = RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER
> @@ -55,7 +55,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   			? RKISP1_CIF_ISP_DPCC_SET_USE_STAGE1_USE_FIX_SET : 0;
>   
>   	/* Get all defined sets to apply (up to 3). */
> -	const YamlObject &setsObject = tuningData["sets"];
> +	const ValueNode &setsObject = tuningData["sets"];
>   	if (!setsObject.isList()) {
>   		LOG(RkISP1Dpcc, Error)
>   			<< "'sets' parameter not found in tuning file";
> @@ -71,14 +71,14 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   
>   	for (std::size_t i = 0; i < setsObject.size(); ++i) {
>   		struct rkisp1_cif_isp_dpcc_methods_config &method = config_.methods[i];
> -		const YamlObject &set = setsObject[i];
> +		const ValueNode &set = setsObject[i];
>   		uint16_t value;
>   
>   		/* Enable set if described in YAML tuning file. */
>   		config_.set_use |= 1 << i;
>   
>   		/* PG Method */
> -		const YamlObject &pgObject = set["pg-factor"];
> +		const ValueNode &pgObject = set["pg-factor"];
>   
>   		if (pgObject.contains("green")) {
>   			method.method |=
> @@ -97,7 +97,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   		}
>   
>   		/* RO Method */
> -		const YamlObject &roObject = set["ro-limits"];
> +		const ValueNode &roObject = set["ro-limits"];
>   
>   		if (roObject.contains("green")) {
>   			method.method |=
> @@ -118,7 +118,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   		}
>   
>   		/* RG Method */
> -		const YamlObject &rgObject = set["rg-factor"];
> +		const ValueNode &rgObject = set["rg-factor"];
>   		method.rg_fac = 0;
>   
>   		if (rgObject.contains("green")) {
> @@ -138,7 +138,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   		}
>   
>   		/* RND Method */
> -		const YamlObject &rndOffsetsObject = set["rnd-offsets"];
> +		const ValueNode &rndOffsetsObject = set["rnd-offsets"];
>   
>   		if (rndOffsetsObject.contains("green")) {
>   			method.method |=
> @@ -158,7 +158,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   				RKISP1_CIF_ISP_DPCC_RND_OFFS_n_RB(i, value);
>   		}
>   
> -		const YamlObject &rndThresholdObject = set["rnd-threshold"];
> +		const ValueNode &rndThresholdObject = set["rnd-threshold"];
>   		method.rnd_thresh = 0;
>   
>   		if (rndThresholdObject.contains("green")) {
> @@ -180,7 +180,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   		}
>   
>   		/* LC Method */
> -		const YamlObject &lcThresholdObject = set["line-threshold"];
> +		const ValueNode &lcThresholdObject = set["line-threshold"];
>   		method.line_thresh = 0;
>   
>   		if (lcThresholdObject.contains("green")) {
> @@ -201,7 +201,7 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
>   				RKISP1_CIF_ISP_DPCC_LINE_THRESH_RB(value);
>   		}
>   
> -		const YamlObject &lcTMadFactorObject = set["line-mad-factor"];
> +		const ValueNode &lcTMadFactorObject = set["line-mad-factor"];
>   		method.line_mad_fac = 0;
>   
>   		if (lcTMadFactorObject.contains("green")) {
> diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h
> index b77766c300fb..50b62e9bab3f 100644
> --- a/src/ipa/rkisp1/algorithms/dpcc.h
> +++ b/src/ipa/rkisp1/algorithms/dpcc.h
> @@ -19,7 +19,7 @@ public:
>   	DefectPixelClusterCorrection();
>   	~DefectPixelClusterCorrection() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
>   		     RkISP1Params *params) override;
> diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp
> index ec989bc2421f..b681ddeb96df 100644
> --- a/src/ipa/rkisp1/algorithms/dpf.cpp
> +++ b/src/ipa/rkisp1/algorithms/dpf.cpp
> @@ -45,7 +45,7 @@ Dpf::Dpf()
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int Dpf::init([[maybe_unused]] IPAContext &context,
> -	      const YamlObject &tuningData)
> +	      const ValueNode &tuningData)
>   {
>   	std::vector<uint8_t> values;
>   
> @@ -53,7 +53,7 @@ int Dpf::init([[maybe_unused]] IPAContext &context,
>   	 * The domain kernel is configured with a 9x9 kernel for the green
>   	 * pixels, and a 13x9 or 9x9 kernel for red and blue pixels.
>   	 */
> -	const YamlObject &dFObject = tuningData["DomainFilter"];
> +	const ValueNode &dFObject = tuningData["DomainFilter"];
>   
>   	/*
>   	 * For the green component, we have the 9x9 kernel specified
> @@ -134,7 +134,7 @@ int Dpf::init([[maybe_unused]] IPAContext &context,
>   	 * which stores a piecewise linear function that characterizes the
>   	 * sensor noise profile as a noise level function curve (NLF).
>   	 */
> -	const YamlObject &rFObject = tuningData["NoiseLevelFunction"];
> +	const ValueNode &rFObject = tuningData["NoiseLevelFunction"];
>   
>   	std::vector<uint16_t> nllValues;
>   	nllValues = rFObject["coeff"].get<std::vector<uint16_t>>().value_or(std::vector<uint16_t>{});
> @@ -162,7 +162,7 @@ int Dpf::init([[maybe_unused]] IPAContext &context,
>   		return -EINVAL;
>   	}
>   
> -	const YamlObject &fSObject = tuningData["FilterStrength"];
> +	const ValueNode &fSObject = tuningData["FilterStrength"];
>   
>   	strengthConfig_.r = fSObject["r"].get<uint16_t>(64);
>   	strengthConfig_.g = fSObject["g"].get<uint16_t>(64);
> diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h
> index 2dd8cd362624..b07067cec0a5 100644
> --- a/src/ipa/rkisp1/algorithms/dpf.h
> +++ b/src/ipa/rkisp1/algorithms/dpf.h
> @@ -21,7 +21,7 @@ public:
>   	Dpf();
>   	~Dpf() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	void queueRequest(IPAContext &context, const uint32_t frame,
>   			  IPAFrameContext &frameContext,
>   			  const ControlList &controls) override;
> diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp
> index 8ad79801792f..2e9b4e285503 100644
> --- a/src/ipa/rkisp1/algorithms/filter.cpp
> +++ b/src/ipa/rkisp1/algorithms/filter.cpp
> @@ -43,7 +43,7 @@ static constexpr uint32_t kFiltModeDefault = 0x000004f2;
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int Filter::init(IPAContext &context,
> -		 [[maybe_unused]] const YamlObject &tuningData)
> +		 [[maybe_unused]] const ValueNode &tuningData)
>   {
>   	auto &cmap = context.ctrlMap;
>   	cmap[&controls::Sharpness] = ControlInfo(0.0f, 10.0f, 1.0f);
> diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h
> index 37d8938d37bd..9f0188da7880 100644
> --- a/src/ipa/rkisp1/algorithms/filter.h
> +++ b/src/ipa/rkisp1/algorithms/filter.h
> @@ -21,7 +21,7 @@ public:
>   	Filter() = default;
>   	~Filter() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	void queueRequest(IPAContext &context, const uint32_t frame,
>   			  IPAFrameContext &frameContext,
>   			  const ControlList &controls) override;
> diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp
> index 6c07bd8c71e5..e8f64bf3d5e0 100644
> --- a/src/ipa/rkisp1/algorithms/goc.cpp
> +++ b/src/ipa/rkisp1/algorithms/goc.cpp
> @@ -13,7 +13,7 @@
>   
>   #include <libcamera/control_ids.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "linux/rkisp1-config.h"
>   
> @@ -48,7 +48,7 @@ const float kDefaultGamma = 2.2f;
>   /**
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
> -int GammaOutCorrection::init(IPAContext &context, const YamlObject &tuningData)
> +int GammaOutCorrection::init(IPAContext &context, const ValueNode &tuningData)
>   {
>   	if (context.hw.numGammaOutSamples !=
>   	    RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10) {
> diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h
> index bb2ddfc92375..bd79fe19cc86 100644
> --- a/src/ipa/rkisp1/algorithms/goc.h
> +++ b/src/ipa/rkisp1/algorithms/goc.h
> @@ -19,7 +19,7 @@ public:
>   	GammaOutCorrection() = default;
>   	~GammaOutCorrection() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPACameraSensorInfo &configInfo) override;
>   	void queueRequest(IPAContext &context,
> diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp
> index 292d0e80dc57..d6272f3a39ef 100644
> --- a/src/ipa/rkisp1/algorithms/gsl.cpp
> +++ b/src/ipa/rkisp1/algorithms/gsl.cpp
> @@ -10,7 +10,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/utils.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "linux/rkisp1-config.h"
>   
> @@ -56,7 +56,7 @@ GammaSensorLinearization::GammaSensorLinearization()
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
> -				   const YamlObject &tuningData)
> +				   const ValueNode &tuningData)
>   {
>   	std::vector<uint16_t> xIntervals =
>   		tuningData["x-intervals"].get<std::vector<uint16_t>>().value_or(std::vector<uint16_t>{});
> @@ -75,7 +75,7 @@ int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
>   	for (unsigned int i = 0; i < kDegammaXIntervals; ++i)
>   		gammaDx_[i / 8] |= (xIntervals[i] & 0x07) << ((i % 8) * 4);
>   
> -	const YamlObject &yObject = tuningData["y"];
> +	const ValueNode &yObject = tuningData["y"];
>   	if (!yObject.isDictionary()) {
>   		LOG(RkISP1Gsl, Error)
>   			<< "Issue while parsing 'y' in tuning file: "
> diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h
> index 91cf6efa7925..3ef5630713ab 100644
> --- a/src/ipa/rkisp1/algorithms/gsl.h
> +++ b/src/ipa/rkisp1/algorithms/gsl.h
> @@ -19,7 +19,7 @@ public:
>   	GammaSensorLinearization();
>   	~GammaSensorLinearization() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
>   		     RkISP1Params *params) override;
> diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
> index 9c0ed44b3943..78618f65c591 100644
> --- a/src/ipa/rkisp1/algorithms/lsc.cpp
> +++ b/src/ipa/rkisp1/algorithms/lsc.cpp
> @@ -14,7 +14,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/utils.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "libipa/lsc_polynomial.h"
>   #include "linux/rkisp1-config.h"
> @@ -85,7 +85,7 @@ public:
>   	{
>   	}
>   
> -	int parseLscData(const YamlObject &yamlSets,
> +	int parseLscData(const ValueNode &yamlSets,
>   			 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
>   	{
>   		const auto &sets = yamlSets.asList();
> @@ -204,7 +204,7 @@ private:
>   class LscTableLoader
>   {
>   public:
> -	int parseLscData(const YamlObject &yamlSets,
> +	int parseLscData(const ValueNode &yamlSets,
>   			 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
>   	{
>   		const auto &sets = yamlSets.asList();
> @@ -245,7 +245,7 @@ public:
>   	}
>   
>   private:
> -	std::vector<uint16_t> parseTable(const YamlObject &tuningData,
> +	std::vector<uint16_t> parseTable(const ValueNode &tuningData,
>   					 const char *prop)
>   	{
>   		static constexpr unsigned int kLscNumSamples =
> @@ -265,7 +265,7 @@ private:
>   	}
>   };
>   
> -static std::vector<double> parseSizes(const YamlObject &tuningData,
> +static std::vector<double> parseSizes(const ValueNode &tuningData,
>   				      const char *prop)
>   {
>   	std::vector<double> sizes =
> @@ -305,7 +305,7 @@ LensShadingCorrection::LensShadingCorrection()
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
> -				const YamlObject &tuningData)
> +				const ValueNode &tuningData)
>   {
>   	xSize_ = parseSizes(tuningData, "x-size");
>   	ySize_ = parseSizes(tuningData, "y-size");
> @@ -314,7 +314,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
>   		return -EINVAL;
>   
>   	/* Get all defined sets to apply. */
> -	const YamlObject &yamlSets = tuningData["sets"];
> +	const ValueNode &yamlSets = tuningData["sets"];
>   	if (!yamlSets.isList()) {
>   		LOG(RkISP1Lsc, Error)
>   			<< "'sets' parameter not found in tuning file";
> diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
> index 5a0824e36dd5..6e0ebad11dc0 100644
> --- a/src/ipa/rkisp1/algorithms/lsc.h
> +++ b/src/ipa/rkisp1/algorithms/lsc.h
> @@ -23,7 +23,7 @@ public:
>   	LensShadingCorrection();
>   	~LensShadingCorrection() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
> diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp
> index e9717bb3bf66..86e46c492f04 100644
> --- a/src/ipa/rkisp1/algorithms/lux.cpp
> +++ b/src/ipa/rkisp1/algorithms/lux.cpp
> @@ -41,7 +41,7 @@ Lux::Lux()
>   /**
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
> -int Lux::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
> +int Lux::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>   {
>   	return lux_.parseTuningData(tuningData);
>   }
> diff --git a/src/ipa/rkisp1/algorithms/lux.h b/src/ipa/rkisp1/algorithms/lux.h
> index e0239848e252..8cb35cbae20d 100644
> --- a/src/ipa/rkisp1/algorithms/lux.h
> +++ b/src/ipa/rkisp1/algorithms/lux.h
> @@ -22,7 +22,7 @@ class Lux : public Algorithm
>   public:
>   	Lux();
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	void prepare(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
>   		     RkISP1Params *params) override;
> diff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp
> index 9e2688a05179..c3d73da2c5b2 100644
> --- a/src/ipa/rkisp1/algorithms/wdr.cpp
> +++ b/src/ipa/rkisp1/algorithms/wdr.cpp
> @@ -10,7 +10,7 @@
>   #include <libcamera/base/log.h>
>   #include <libcamera/base/utils.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include <libipa/agc_mean_luminance.h>
>   #include <libipa/histogram.h>
> @@ -110,7 +110,7 @@ WideDynamicRange::WideDynamicRange()
>    * \copydoc libcamera::ipa::Algorithm::init
>    */
>   int WideDynamicRange::init([[maybe_unused]] IPAContext &context,
> -			   [[maybe_unused]] const YamlObject &tuningData)
> +			   [[maybe_unused]] const ValueNode &tuningData)
>   {
>   	if (!(context.hw.supportedBlocks & 1 << RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR)) {
>   		LOG(RkISP1Wdr, Error)
> diff --git a/src/ipa/rkisp1/algorithms/wdr.h b/src/ipa/rkisp1/algorithms/wdr.h
> index 46f7cdeea69d..f79de66fe73b 100644
> --- a/src/ipa/rkisp1/algorithms/wdr.h
> +++ b/src/ipa/rkisp1/algorithms/wdr.h
> @@ -23,7 +23,7 @@ public:
>   	WideDynamicRange();
>   	~WideDynamicRange() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
>   
>   	void queueRequest(IPAContext &context, const uint32_t frame,
> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> index fbcc39103d4b..3230823f3a28 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -185,7 +185,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>   		return ret;
>   	}
>   
> -	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
>   	if (!data)
>   		return -EINVAL;
>   
> diff --git a/src/ipa/rpi/controller/algorithm.cpp b/src/ipa/rpi/controller/algorithm.cpp
> index beed47a1e1c4..82bc0302fd12 100644
> --- a/src/ipa/rpi/controller/algorithm.cpp
> +++ b/src/ipa/rpi/controller/algorithm.cpp
> @@ -9,7 +9,7 @@
>   
>   using namespace RPiController;
>   
> -int Algorithm::read([[maybe_unused]] const libcamera::YamlObject &params)
> +int Algorithm::read([[maybe_unused]] const libcamera::ValueNode &params)
>   {
>   	return 0;
>   }
> diff --git a/src/ipa/rpi/controller/algorithm.h b/src/ipa/rpi/controller/algorithm.h
> index 8839daa90916..214b06576bbf 100644
> --- a/src/ipa/rpi/controller/algorithm.h
> +++ b/src/ipa/rpi/controller/algorithm.h
> @@ -15,7 +15,7 @@
>   #include <memory>
>   #include <map>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "controller.h"
>   
> @@ -32,7 +32,7 @@ public:
>   	}
>   	virtual ~Algorithm() = default;
>   	virtual char const *name() const = 0;
> -	virtual int read(const libcamera::YamlObject &params);
> +	virtual int read(const libcamera::ValueNode &params);
>   	virtual void initialise();
>   	virtual void switchMode(CameraMode const &cameraMode, Metadata *metadata);
>   	virtual void prepare(Metadata *imageMetadata);
> diff --git a/src/ipa/rpi/controller/controller.cpp b/src/ipa/rpi/controller/controller.cpp
> index df45dcd345b7..162c75d8848f 100644
> --- a/src/ipa/rpi/controller/controller.cpp
> +++ b/src/ipa/rpi/controller/controller.cpp
> @@ -102,7 +102,7 @@ int Controller::read(char const *filename)
>   		return -EINVAL;
>   	}
>   
> -	std::unique_ptr<YamlObject> root = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> root = YamlParser::parse(file);
>   	if (!root)
>   		return -EINVAL;
>   
> @@ -143,7 +143,7 @@ int Controller::read(char const *filename)
>   	return 0;
>   }
>   
> -int Controller::createAlgorithm(const std::string &name, const YamlObject &params)
> +int Controller::createAlgorithm(const std::string &name, const ValueNode &params)
>   {
>   	auto it = getAlgorithms().find(name);
>   	if (it == getAlgorithms().end()) {
> diff --git a/src/ipa/rpi/controller/controller.h b/src/ipa/rpi/controller/controller.h
> index 573942886bc0..094917b08b6a 100644
> --- a/src/ipa/rpi/controller/controller.h
> +++ b/src/ipa/rpi/controller/controller.h
> @@ -16,7 +16,7 @@
>   #include <string>
>   
>   #include <libcamera/base/utils.h>
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "camera_mode.h"
>   #include "device_status.h"
> @@ -65,7 +65,7 @@ public:
>   	const HardwareConfig &getHardwareConfig() const;
>   
>   protected:
> -	int createAlgorithm(const std::string &name, const libcamera::YamlObject &params);
> +	int createAlgorithm(const std::string &name, const libcamera::ValueNode &params);
>   
>   	Metadata globalMetadata_;
>   	std::vector<AlgorithmPtr> algorithms_;
> diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp
> index 26e599303f24..47c2bf935835 100644
> --- a/src/ipa/rpi/controller/rpi/af.cpp
> +++ b/src/ipa/rpi/controller/rpi/af.cpp
> @@ -68,7 +68,7 @@ Af::CfgParams::CfgParams()
>   }
>   
>   template<typename T>
> -static void readNumber(T &dest, const libcamera::YamlObject &params, char const *name)
> +static void readNumber(T &dest, const libcamera::ValueNode &params, char const *name)
>   {
>   	auto value = params[name].get<T>();
>   	if (value)
> @@ -77,7 +77,7 @@ static void readNumber(T &dest, const libcamera::YamlObject &params, char const
>   		LOG(RPiAf, Warning) << "Missing parameter \"" << name << "\"";
>   }
>   
> -void Af::RangeDependentParams::read(const libcamera::YamlObject &params)
> +void Af::RangeDependentParams::read(const libcamera::ValueNode &params)
>   {
>   
>   	readNumber<double>(focusMin, params, "min");
> @@ -85,7 +85,7 @@ void Af::RangeDependentParams::read(const libcamera::YamlObject &params)
>   	readNumber<double>(focusDefault, params, "default");
>   }
>   
> -void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
> +void Af::SpeedDependentParams::read(const libcamera::ValueNode &params)
>   {
>   	readNumber<double>(stepCoarse, params, "step_coarse");
>   	readNumber<double>(stepFine, params, "step_fine");
> @@ -100,7 +100,7 @@ void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
>   	readNumber<uint32_t>(stepFrames, params, "step_frames");
>   }
>   
> -int Af::CfgParams::read(const libcamera::YamlObject &params)
> +int Af::CfgParams::read(const libcamera::ValueNode &params)
>   {
>   	if (params.contains("ranges")) {
>   		auto &rr = params["ranges"];
> @@ -226,7 +226,7 @@ char const *Af::name() const
>   	return NAME;
>   }
>   
> -int Af::read(const libcamera::YamlObject &params)
> +int Af::read(const libcamera::ValueNode &params)
>   {
>   	return cfg_.read(params);
>   }
> diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h
> index d35a39d12049..b464927ba92b 100644
> --- a/src/ipa/rpi/controller/rpi/af.h
> +++ b/src/ipa/rpi/controller/rpi/af.h
> @@ -47,7 +47,7 @@ public:
>   	Af(Controller *controller = NULL);
>   	~Af();
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialise() override;
>   
>   	/* IPA calls */
> @@ -87,7 +87,7 @@ private:
>   		double focusDefault;		/* default setting ("hyperfocal") */
>   
>   		RangeDependentParams();
> -		void read(const libcamera::YamlObject &params);
> +		void read(const libcamera::ValueNode &params);
>   	};
>   
>   	struct SpeedDependentParams {
> @@ -104,7 +104,7 @@ private:
>   		uint32_t stepFrames;		/* frames to skip in between steps of a scan */
>   
>   		SpeedDependentParams();
> -		void read(const libcamera::YamlObject &params);
> +		void read(const libcamera::ValueNode &params);
>   	};
>   
>   	struct CfgParams {
> @@ -118,7 +118,7 @@ private:
>   		libcamera::ipa::Pwl map;       	/* converts dioptres -> lens driver position */
>   
>   		CfgParams();
> -		int read(const libcamera::YamlObject &params);
> +		int read(const libcamera::ValueNode &params);
>   		void initialise();
>   	};
>   
> diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
> index afda2e364f64..830b26174df2 100644
> --- a/src/ipa/rpi/controller/rpi/agc.cpp
> +++ b/src/ipa/rpi/controller/rpi/agc.cpp
> @@ -31,7 +31,7 @@ char const *Agc::name() const
>   	return NAME;
>   }
>   
> -int Agc::read(const libcamera::YamlObject &params)
> +int Agc::read(const libcamera::ValueNode &params)
>   {
>   	/*
>   	 * When there is only a single channel we can read the old style syntax.
> diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
> index 966630a26303..5189ad2a2bf8 100644
> --- a/src/ipa/rpi/controller/rpi/agc.h
> +++ b/src/ipa/rpi/controller/rpi/agc.h
> @@ -27,7 +27,7 @@ class Agc : public AgcAlgorithm
>   public:
>   	Agc(Controller *controller);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	unsigned int getConvergenceFrames() const override;
>   	std::vector<double> const &getWeights() const override;
>   	void setEv(unsigned int channel, double ev) override;
> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> index c6cf1f8903f1..c0ac76c569af 100644
> --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
> +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> @@ -29,9 +29,9 @@ using namespace std::literals::chrono_literals;
>   
>   LOG_DECLARE_CATEGORY(RPiAgc)
>   
> -int AgcMeteringMode::read(const libcamera::YamlObject &params)
> +int AgcMeteringMode::read(const libcamera::ValueNode &params)
>   {
> -	const YamlObject &yamlWeights = params["weights"];
> +	const ValueNode &yamlWeights = params["weights"];
>   
>   	for (const auto &p : yamlWeights.asList()) {
>   		auto value = p.get<double>();
> @@ -45,7 +45,7 @@ int AgcMeteringMode::read(const libcamera::YamlObject &params)
>   
>   static std::tuple<int, std::string>
>   readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
> -		  const libcamera::YamlObject &params)
> +		  const libcamera::ValueNode &params)
>   {
>   	std::string first;
>   	int ret;
> @@ -64,7 +64,7 @@ readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
>   	return { 0, first };
>   }
>   
> -int AgcExposureMode::read(const libcamera::YamlObject &params)
> +int AgcExposureMode::read(const libcamera::ValueNode &params)
>   {
>   	auto value = params["shutter"].get<std::vector<double>>();
>   	if (!value)
> @@ -94,7 +94,7 @@ int AgcExposureMode::read(const libcamera::YamlObject &params)
>   
>   static std::tuple<int, std::string>
>   readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
> -		  const libcamera::YamlObject &params)
> +		  const libcamera::ValueNode &params)
>   {
>   	std::string first;
>   	int ret;
> @@ -113,7 +113,7 @@ readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
>   	return { 0, first };
>   }
>   
> -int AgcConstraint::read(const libcamera::YamlObject &params)
> +int AgcConstraint::read(const libcamera::ValueNode &params)
>   {
>   	std::string boundString = params["bound"].get<std::string>("");
>   	transform(boundString.begin(), boundString.end(),
> @@ -139,7 +139,7 @@ int AgcConstraint::read(const libcamera::YamlObject &params)
>   }
>   
>   static std::tuple<int, AgcConstraintMode>
> -readConstraintMode(const libcamera::YamlObject &params)
> +readConstraintMode(const libcamera::ValueNode &params)
>   {
>   	AgcConstraintMode mode;
>   	int ret;
> @@ -158,7 +158,7 @@ readConstraintMode(const libcamera::YamlObject &params)
>   
>   static std::tuple<int, std::string>
>   readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
> -		    const libcamera::YamlObject &params)
> +		    const libcamera::ValueNode &params)
>   {
>   	std::string first;
>   	int ret;
> @@ -175,7 +175,7 @@ readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
>   	return { 0, first };
>   }
>   
> -int AgcChannelConstraint::read(const libcamera::YamlObject &params)
> +int AgcChannelConstraint::read(const libcamera::ValueNode &params)
>   {
>   	auto channelValue = params["channel"].get<unsigned int>();
>   	if (!channelValue) {
> @@ -204,7 +204,7 @@ int AgcChannelConstraint::read(const libcamera::YamlObject &params)
>   }
>   
>   static int readChannelConstraints(std::vector<AgcChannelConstraint> &channelConstraints,
> -				  const libcamera::YamlObject &params)
> +				  const libcamera::ValueNode &params)
>   {
>   	for (const auto &p : params.asList()) {
>   		AgcChannelConstraint constraint;
> @@ -218,7 +218,7 @@ static int readChannelConstraints(std::vector<AgcChannelConstraint> &channelCons
>   	return 0;
>   }
>   
> -int AgcConfig::read(const libcamera::YamlObject &params)
> +int AgcConfig::read(const libcamera::ValueNode &params)
>   {
>   	LOG(RPiAgc, Debug) << "AgcConfig";
>   	int ret;
> @@ -290,7 +290,7 @@ AgcChannel::AgcChannel()
>   	status_.ev = ev_;
>   }
>   
> -int AgcChannel::read(const libcamera::YamlObject &params,
> +int AgcChannel::read(const libcamera::ValueNode &params,
>   		     const Controller::HardwareConfig &hardwareConfig)
>   {
>   	int ret = config_.read(params);
> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
> index 42d85ec15e8d..90e540a8a18d 100644
> --- a/src/ipa/rpi/controller/rpi/agc_channel.h
> +++ b/src/ipa/rpi/controller/rpi/agc_channel.h
> @@ -26,13 +26,13 @@ using AgcChannelTotalExposures = std::vector<libcamera::utils::Duration>;
>   
>   struct AgcMeteringMode {
>   	std::vector<double> weights;
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   };
>   
>   struct AgcExposureMode {
>   	std::vector<libcamera::utils::Duration> exposureTime;
>   	std::vector<double> gain;
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   };
>   
>   struct AgcConstraint {
> @@ -42,7 +42,7 @@ struct AgcConstraint {
>   	double qLo;
>   	double qHi;
>   	libcamera::ipa::Pwl yTarget;
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   };
>   
>   typedef std::vector<AgcConstraint> AgcConstraintMode;
> @@ -53,11 +53,11 @@ struct AgcChannelConstraint {
>   	Bound bound;
>   	unsigned int channel;
>   	double factor;
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   };
>   
>   struct AgcConfig {
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   	std::map<std::string, AgcMeteringMode> meteringModes;
>   	std::map<std::string, AgcExposureMode> exposureModes;
>   	std::map<std::string, AgcConstraintMode> constraintModes;
> @@ -85,7 +85,7 @@ class AgcChannel
>   {
>   public:
>   	AgcChannel();
> -	int read(const libcamera::YamlObject &params,
> +	int read(const libcamera::ValueNode &params,
>   		 const Controller::HardwareConfig &hardwareConfig);
>   	unsigned int getConvergenceFrames() const;
>   	std::vector<double> const &getWeights() const;
> diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp
> index 21edb819ad1c..4c852db04f7f 100644
> --- a/src/ipa/rpi/controller/rpi/alsc.cpp
> +++ b/src/ipa/rpi/controller/rpi/alsc.cpp
> @@ -50,7 +50,7 @@ char const *Alsc::name() const
>   	return NAME;
>   }
>   
> -static int generateLut(Array2D<double> &lut, const libcamera::YamlObject &params)
> +static int generateLut(Array2D<double> &lut, const libcamera::ValueNode &params)
>   {
>   	/* These must be signed ints for the co-ordinate calculations below. */
>   	int X = lut.dimensions().width, Y = lut.dimensions().height;
> @@ -82,7 +82,7 @@ static int generateLut(Array2D<double> &lut, const libcamera::YamlObject &params
>   	return 0;
>   }
>   
> -static int readLut(Array2D<double> &lut, const libcamera::YamlObject &params)
> +static int readLut(Array2D<double> &lut, const libcamera::ValueNode &params)
>   {
>   	if (params.size() != lut.size()) {
>   		LOG(RPiAlsc, Error) << "Invalid number of entries in LSC table";
> @@ -101,7 +101,7 @@ static int readLut(Array2D<double> &lut, const libcamera::YamlObject &params)
>   }
>   
>   static int readCalibrations(std::vector<AlscCalibration> &calibrations,
> -			    const libcamera::YamlObject &params,
> +			    const libcamera::ValueNode &params,
>   			    std::string const &name, const Size &size)
>   {
>   	if (params.contains(name)) {
> @@ -119,7 +119,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,
>   			AlscCalibration calibration;
>   			calibration.ct = lastCt = ct;
>   
> -			const libcamera::YamlObject &table = p["table"];
> +			const libcamera::ValueNode &table = p["table"];
>   			if (table.size() != size.width * size.height) {
>   				LOG(RPiAlsc, Error)
>   					<< "Incorrect number of values for ct "
> @@ -144,7 +144,7 @@ static int readCalibrations(std::vector<AlscCalibration> &calibrations,
>   	return 0;
>   }
>   
> -int Alsc::read(const libcamera::YamlObject &params)
> +int Alsc::read(const libcamera::ValueNode &params)
>   {
>   	config_.tableSize = getHardwareConfig().awbRegions;
>   	config_.framePeriod = params["frame_period"].get<uint16_t>(12);
> diff --git a/src/ipa/rpi/controller/rpi/alsc.h b/src/ipa/rpi/controller/rpi/alsc.h
> index 310879820fba..0952ae8358c6 100644
> --- a/src/ipa/rpi/controller/rpi/alsc.h
> +++ b/src/ipa/rpi/controller/rpi/alsc.h
> @@ -112,7 +112,7 @@ public:
>   	char const *name() const override;
>   	void initialise() override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void prepare(Metadata *imageMetadata) override;
>   	void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
>   
> diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp
> index 365b595ff9b4..1ccac36e5b4f 100644
> --- a/src/ipa/rpi/controller/rpi/awb.cpp
> +++ b/src/ipa/rpi/controller/rpi/awb.cpp
> @@ -30,7 +30,7 @@ constexpr double kDefaultCT = 4500.0;
>    * elsewhere (ALSC and AGC).
>    */
>   
> -int AwbMode::read(const libcamera::YamlObject &params)
> +int AwbMode::read(const libcamera::ValueNode &params)
>   {
>   	auto value = params["lo"].get<double>();
>   	if (!value)
> @@ -45,7 +45,7 @@ int AwbMode::read(const libcamera::YamlObject &params)
>   	return 0;
>   }
>   
> -int AwbPrior::read(const libcamera::YamlObject &params)
> +int AwbPrior::read(const libcamera::ValueNode &params)
>   {
>   	auto value = params["lux"].get<double>();
>   	if (!value)
> @@ -56,7 +56,7 @@ int AwbPrior::read(const libcamera::YamlObject &params)
>   	return prior.empty() ? -EINVAL : 0;
>   }
>   
> -static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject &params)
> +static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::ValueNode &params)
>   {
>   	if (params.size() % 3) {
>   		LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry";
> @@ -92,7 +92,7 @@ static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject
>   	return 0;
>   }
>   
> -int AwbConfig::read(const libcamera::YamlObject &params)
> +int AwbConfig::read(const libcamera::ValueNode &params)
>   {
>   	int ret;
>   
> @@ -204,7 +204,7 @@ char const *Awb::name() const
>   	return NAME;
>   }
>   
> -int Awb::read(const libcamera::YamlObject &params)
> +int Awb::read(const libcamera::ValueNode &params)
>   {
>   	return config_.read(params);
>   }
> diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h
> index 2fb912541a2b..ac73105113fb 100644
> --- a/src/ipa/rpi/controller/rpi/awb.h
> +++ b/src/ipa/rpi/controller/rpi/awb.h
> @@ -23,20 +23,20 @@ namespace RPiController {
>   /* Control algorithm to perform AWB calculations. */
>   
>   struct AwbMode {
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   	double ctLo; /* low CT value for search */
>   	double ctHi; /* high CT value for search */
>   };
>   
>   struct AwbPrior {
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   	double lux; /* lux level */
>   	libcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */
>   };
>   
>   struct AwbConfig {
>   	AwbConfig() : defaultMode(nullptr) {}
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   	/* Only repeat the AWB calculation every "this many" frames */
>   	uint16_t framePeriod;
>   	/* number of initial frames for which speed taken as 1.0 (maximum) */
> @@ -99,7 +99,7 @@ public:
>   	~Awb();
>   	char const *name() const override;
>   	void initialise() override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	unsigned int getConvergenceFrames() const override;
>   	void initialValues(double &gainR, double &gainB) override;
>   	void setMode(std::string const &name) override;
> diff --git a/src/ipa/rpi/controller/rpi/black_level.cpp b/src/ipa/rpi/controller/rpi/black_level.cpp
> index 4c968f14a1ca..42ea1505014f 100644
> --- a/src/ipa/rpi/controller/rpi/black_level.cpp
> +++ b/src/ipa/rpi/controller/rpi/black_level.cpp
> @@ -30,7 +30,7 @@ char const *BlackLevel::name() const
>   	return NAME;
>   }
>   
> -int BlackLevel::read(const libcamera::YamlObject &params)
> +int BlackLevel::read(const libcamera::ValueNode &params)
>   {
>   	/* 64 in 10 bits scaled to 16 bits */
>   	uint16_t blackLevel = params["black_level"].get<uint16_t>(4096);
> diff --git a/src/ipa/rpi/controller/rpi/black_level.h b/src/ipa/rpi/controller/rpi/black_level.h
> index f50729dbc1e3..dbf29b282e4c 100644
> --- a/src/ipa/rpi/controller/rpi/black_level.h
> +++ b/src/ipa/rpi/controller/rpi/black_level.h
> @@ -18,7 +18,7 @@ class BlackLevel : public BlackLevelAlgorithm
>   public:
>   	BlackLevel(Controller *controller);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG,
>   			   uint16_t &blackLevelB) override;
>   	void prepare(Metadata *imageMetadata) override;
> diff --git a/src/ipa/rpi/controller/rpi/cac.cpp b/src/ipa/rpi/controller/rpi/cac.cpp
> index 17779ad5469b..ddc848f21f3f 100644
> --- a/src/ipa/rpi/controller/rpi/cac.cpp
> +++ b/src/ipa/rpi/controller/rpi/cac.cpp
> @@ -27,7 +27,7 @@ char const *Cac::name() const
>   	return NAME;
>   }
>   
> -static bool arrayToSet(const libcamera::YamlObject &params, std::vector<double> &inputArray, const Size &size)
> +static bool arrayToSet(const libcamera::ValueNode &params, std::vector<double> &inputArray, const Size &size)
>   {
>   	int num = 0;
>   	int max_num = (size.width + 1) * (size.height + 1);
> @@ -51,7 +51,7 @@ static void setStrength(std::vector<double> &inputArray, std::vector<double> &ou
>   	}
>   }
>   
> -int Cac::read(const libcamera::YamlObject &params)
> +int Cac::read(const libcamera::ValueNode &params)
>   {
>   	config_.enabled = params.contains("lut_rx") && params.contains("lut_ry") &&
>   			  params.contains("lut_bx") && params.contains("lut_by");
> diff --git a/src/ipa/rpi/controller/rpi/cac.h b/src/ipa/rpi/controller/rpi/cac.h
> index 533cca44424b..11c47a7c4323 100644
> --- a/src/ipa/rpi/controller/rpi/cac.h
> +++ b/src/ipa/rpi/controller/rpi/cac.h
> @@ -24,7 +24,7 @@ class Cac : public Algorithm
>   public:
>   	Cac(Controller *controller = NULL);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void prepare(Metadata *imageMetadata) override;
>   
>   private:
> diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp
> index 2806b4967158..d3231182913d 100644
> --- a/src/ipa/rpi/controller/rpi/ccm.cpp
> +++ b/src/ipa/rpi/controller/rpi/ccm.cpp
> @@ -40,7 +40,7 @@ char const *Ccm::name() const
>   	return NAME;
>   }
>   
> -int Ccm::read(const libcamera::YamlObject &params)
> +int Ccm::read(const libcamera::ValueNode &params)
>   {
>   	if (params.contains("saturation")) {
>   		config_.saturation = params["saturation"].get<ipa::Pwl>(ipa::Pwl{});
> diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h
> index 70f28ed33d5e..a0f5b698db9b 100644
> --- a/src/ipa/rpi/controller/rpi/ccm.h
> +++ b/src/ipa/rpi/controller/rpi/ccm.h
> @@ -31,7 +31,7 @@ class Ccm : public CcmAlgorithm
>   public:
>   	Ccm(Controller *controller = NULL);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void enableAuto() override;
>   	void setSaturation(double saturation) override;
>   	void setCcm(Matrix3x3 const &matrix) override;
> diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp
> index fe866a544293..3457e04002e0 100644
> --- a/src/ipa/rpi/controller/rpi/contrast.cpp
> +++ b/src/ipa/rpi/controller/rpi/contrast.cpp
> @@ -38,7 +38,7 @@ char const *Contrast::name() const
>   	return NAME;
>   }
>   
> -int Contrast::read(const libcamera::YamlObject &params)
> +int Contrast::read(const libcamera::ValueNode &params)
>   {
>   	// enable adaptive enhancement by default
>   	config_.ceEnable = params["ce_enable"].get<int>(1);
> diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h
> index c0f7db981c7d..3571626d8623 100644
> --- a/src/ipa/rpi/controller/rpi/contrast.h
> +++ b/src/ipa/rpi/controller/rpi/contrast.h
> @@ -35,7 +35,7 @@ class Contrast : public ContrastAlgorithm
>   public:
>   	Contrast(Controller *controller = NULL);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void setBrightness(double brightness) override;
>   	void setContrast(double contrast) override;
>   	void enableCe(bool enable) override;
> diff --git a/src/ipa/rpi/controller/rpi/decompand.cpp b/src/ipa/rpi/controller/rpi/decompand.cpp
> index 2d457926c060..5646984132f6 100644
> --- a/src/ipa/rpi/controller/rpi/decompand.cpp
> +++ b/src/ipa/rpi/controller/rpi/decompand.cpp
> @@ -22,7 +22,7 @@ char const *Decompand::name() const
>   	return NAME;
>   }
>   
> -int Decompand::read(const libcamera::YamlObject &params)
> +int Decompand::read(const libcamera::ValueNode &params)
>   {
>   	config_.bitdepth = params["bitdepth"].get<uint32_t>(0);
>   	config_.decompandCurve = params["decompand_curve"].get<ipa::Pwl>(ipa::Pwl{});
> diff --git a/src/ipa/rpi/controller/rpi/decompand.h b/src/ipa/rpi/controller/rpi/decompand.h
> index 6db779c359a8..847769af4338 100644
> --- a/src/ipa/rpi/controller/rpi/decompand.h
> +++ b/src/ipa/rpi/controller/rpi/decompand.h
> @@ -17,7 +17,7 @@ class Decompand : public DecompandAlgorithm
>   public:
>   	Decompand(Controller *controller = nullptr);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialise() override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
>   	void initialValues(libcamera::ipa::Pwl &decompandCurve) override;
> diff --git a/src/ipa/rpi/controller/rpi/denoise.cpp b/src/ipa/rpi/controller/rpi/denoise.cpp
> index cabe3e2a08aa..0cd0dd53fe97 100644
> --- a/src/ipa/rpi/controller/rpi/denoise.cpp
> +++ b/src/ipa/rpi/controller/rpi/denoise.cpp
> @@ -21,7 +21,7 @@ LOG_DEFINE_CATEGORY(RPiDenoise)
>   
>   #define NAME "rpi.denoise"
>   
> -int DenoiseConfig::read(const libcamera::YamlObject &params)
> +int DenoiseConfig::read(const libcamera::ValueNode &params)
>   {
>   	sdnEnable = params.contains("sdn");
>   	if (sdnEnable) {
> @@ -82,7 +82,7 @@ char const *Denoise::name() const
>   	return NAME;
>   }
>   
> -int Denoise::read(const libcamera::YamlObject &params)
> +int Denoise::read(const libcamera::ValueNode &params)
>   {
>   	if (!params.contains("normal")) {
>   		configs_["normal"].read(params);
> diff --git a/src/ipa/rpi/controller/rpi/denoise.h b/src/ipa/rpi/controller/rpi/denoise.h
> index e23a2e8ff525..499b2ab74749 100644
> --- a/src/ipa/rpi/controller/rpi/denoise.h
> +++ b/src/ipa/rpi/controller/rpi/denoise.h
> @@ -31,7 +31,7 @@ struct DenoiseConfig {
>   	bool tdnEnable;
>   	bool sdnEnable;
>   	bool cdnEnable;
> -	int read(const libcamera::YamlObject &params);
> +	int read(const libcamera::ValueNode &params);
>   };
>   
>   class Denoise : public DenoiseAlgorithm
> @@ -39,7 +39,7 @@ class Denoise : public DenoiseAlgorithm
>   public:
>   	Denoise(Controller *controller);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialise() override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
>   	void prepare(Metadata *imageMetadata) override;
> diff --git a/src/ipa/rpi/controller/rpi/dpc.cpp b/src/ipa/rpi/controller/rpi/dpc.cpp
> index 8aac03f794fc..a92207999fab 100644
> --- a/src/ipa/rpi/controller/rpi/dpc.cpp
> +++ b/src/ipa/rpi/controller/rpi/dpc.cpp
> @@ -31,7 +31,7 @@ char const *Dpc::name() const
>   	return NAME;
>   }
>   
> -int Dpc::read(const libcamera::YamlObject &params)
> +int Dpc::read(const libcamera::ValueNode &params)
>   {
>   	config_.strength = params["strength"].get<int>(1);
>   	if (config_.strength < 0 || config_.strength > 2) {
> diff --git a/src/ipa/rpi/controller/rpi/dpc.h b/src/ipa/rpi/controller/rpi/dpc.h
> index 9cefb06d4a7c..a1a02af59cbc 100644
> --- a/src/ipa/rpi/controller/rpi/dpc.h
> +++ b/src/ipa/rpi/controller/rpi/dpc.h
> @@ -22,7 +22,7 @@ class Dpc : public Algorithm
>   public:
>   	Dpc(Controller *controller);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void prepare(Metadata *imageMetadata) override;
>   
>   private:
> diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp
> index 40e7191ba16a..9382a033f055 100644
> --- a/src/ipa/rpi/controller/rpi/geq.cpp
> +++ b/src/ipa/rpi/controller/rpi/geq.cpp
> @@ -34,7 +34,7 @@ char const *Geq::name() const
>   	return NAME;
>   }
>   
> -int Geq::read(const libcamera::YamlObject &params)
> +int Geq::read(const libcamera::ValueNode &params)
>   {
>   	config_.offset = params["offset"].get<uint16_t>(0);
>   	config_.slope = params["slope"].get<double>(0.0);
> diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h
> index e8b9f42708c0..4827051b4f46 100644
> --- a/src/ipa/rpi/controller/rpi/geq.h
> +++ b/src/ipa/rpi/controller/rpi/geq.h
> @@ -26,7 +26,7 @@ class Geq : public Algorithm
>   public:
>   	Geq(Controller *controller);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void prepare(Metadata *imageMetadata) override;
>   
>   private:
> diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp
> index 06400ea79a95..c256f7485f77 100644
> --- a/src/ipa/rpi/controller/rpi/hdr.cpp
> +++ b/src/ipa/rpi/controller/rpi/hdr.cpp
> @@ -23,7 +23,7 @@ LOG_DEFINE_CATEGORY(RPiHdr)
>   
>   #define NAME "rpi.hdr"
>   
> -void HdrConfig::read(const libcamera::YamlObject &params, const std::string &modeName)
> +void HdrConfig::read(const libcamera::ValueNode &params, const std::string &modeName)
>   {
>   	name = modeName;
>   
> @@ -111,7 +111,7 @@ char const *Hdr::name() const
>   	return NAME;
>   }
>   
> -int Hdr::read(const libcamera::YamlObject &params)
> +int Hdr::read(const libcamera::ValueNode &params)
>   {
>   	/* Make an "HDR off" mode by default so that tuning files don't have to. */
>   	HdrConfig &offMode = config_["Off"];
> diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h
> index 5c2f3988d789..58ff5ba83d2e 100644
> --- a/src/ipa/rpi/controller/rpi/hdr.h
> +++ b/src/ipa/rpi/controller/rpi/hdr.h
> @@ -52,7 +52,7 @@ struct HdrConfig {
>   	uint8_t diffPower;
>   	double motionThreshold;
>   
> -	void read(const libcamera::YamlObject &params, const std::string &name);
> +	void read(const libcamera::ValueNode &params, const std::string &name);
>   };
>   
>   class Hdr : public HdrAlgorithm
> @@ -61,7 +61,7 @@ public:
>   	Hdr(Controller *controller);
>   	char const *name() const override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void prepare(Metadata *imageMetadata) override;
>   	void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
>   	int setMode(std::string const &mode) override;
> diff --git a/src/ipa/rpi/controller/rpi/lux.cpp b/src/ipa/rpi/controller/rpi/lux.cpp
> index 7dab27cc0a90..2047a240bad5 100644
> --- a/src/ipa/rpi/controller/rpi/lux.cpp
> +++ b/src/ipa/rpi/controller/rpi/lux.cpp
> @@ -35,7 +35,7 @@ char const *Lux::name() const
>   	return NAME;
>   }
>   
> -int Lux::read(const libcamera::YamlObject &params)
> +int Lux::read(const libcamera::ValueNode &params)
>   {
>   	auto value = params["reference_shutter_speed"].get<double>();
>   	if (!value)
> diff --git a/src/ipa/rpi/controller/rpi/lux.h b/src/ipa/rpi/controller/rpi/lux.h
> index db2227e41455..c9ffe38b69db 100644
> --- a/src/ipa/rpi/controller/rpi/lux.h
> +++ b/src/ipa/rpi/controller/rpi/lux.h
> @@ -23,7 +23,7 @@ class Lux : public Algorithm
>   public:
>   	Lux(Controller *controller);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
>   	void prepare(Metadata *imageMetadata) override;
>   	void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
> diff --git a/src/ipa/rpi/controller/rpi/noise.cpp b/src/ipa/rpi/controller/rpi/noise.cpp
> index 145175fb4940..d6e01c2cfe4e 100644
> --- a/src/ipa/rpi/controller/rpi/noise.cpp
> +++ b/src/ipa/rpi/controller/rpi/noise.cpp
> @@ -41,7 +41,7 @@ void Noise::switchMode(CameraMode const &cameraMode,
>   	modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
>   }
>   
> -int Noise::read(const libcamera::YamlObject &params)
> +int Noise::read(const libcamera::ValueNode &params)
>   {
>   	auto value = params["reference_constant"].get<double>();
>   	if (!value)
> diff --git a/src/ipa/rpi/controller/rpi/noise.h b/src/ipa/rpi/controller/rpi/noise.h
> index 6deae1f0282e..c449fa52ffd2 100644
> --- a/src/ipa/rpi/controller/rpi/noise.h
> +++ b/src/ipa/rpi/controller/rpi/noise.h
> @@ -19,7 +19,7 @@ public:
>   	Noise(Controller *controller);
>   	char const *name() const override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void prepare(Metadata *imageMetadata) override;
>   
>   private:
> diff --git a/src/ipa/rpi/controller/rpi/saturation.cpp b/src/ipa/rpi/controller/rpi/saturation.cpp
> index b83c5887c02e..5001f930a1ec 100644
> --- a/src/ipa/rpi/controller/rpi/saturation.cpp
> +++ b/src/ipa/rpi/controller/rpi/saturation.cpp
> @@ -27,7 +27,7 @@ char const *Saturation::name() const
>   	return NAME;
>   }
>   
> -int Saturation::read(const libcamera::YamlObject &params)
> +int Saturation::read(const libcamera::ValueNode &params)
>   {
>   	config_.shiftR = params["shift_r"].get<uint8_t>(0);
>   	config_.shiftG = params["shift_g"].get<uint8_t>(0);
> diff --git a/src/ipa/rpi/controller/rpi/saturation.h b/src/ipa/rpi/controller/rpi/saturation.h
> index c67d496ef065..e6a22c8bb94d 100644
> --- a/src/ipa/rpi/controller/rpi/saturation.h
> +++ b/src/ipa/rpi/controller/rpi/saturation.h
> @@ -21,7 +21,7 @@ class Saturation : public Algorithm
>   public:
>   	Saturation(Controller *controller = NULL);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialise() override;
>   	void prepare(Metadata *imageMetadata) override;
>   
> diff --git a/src/ipa/rpi/controller/rpi/sdn.cpp b/src/ipa/rpi/controller/rpi/sdn.cpp
> index 594ea70133ac..13dfad02068c 100644
> --- a/src/ipa/rpi/controller/rpi/sdn.cpp
> +++ b/src/ipa/rpi/controller/rpi/sdn.cpp
> @@ -35,7 +35,7 @@ char const *Sdn::name() const
>   	return NAME;
>   }
>   
> -int Sdn::read(const libcamera::YamlObject &params)
> +int Sdn::read(const libcamera::ValueNode &params)
>   {
>   	deviation_ = params["deviation"].get<double>(3.2);
>   	strength_ = params["strength"].get<double>(0.75);
> diff --git a/src/ipa/rpi/controller/rpi/sdn.h b/src/ipa/rpi/controller/rpi/sdn.h
> index cb226de88c3c..20d847f0cefa 100644
> --- a/src/ipa/rpi/controller/rpi/sdn.h
> +++ b/src/ipa/rpi/controller/rpi/sdn.h
> @@ -18,7 +18,7 @@ class Sdn : public DenoiseAlgorithm
>   public:
>   	Sdn(Controller *controller = NULL);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialise() override;
>   	void prepare(Metadata *imageMetadata) override;
>   	void setMode(DenoiseMode mode) override;
> diff --git a/src/ipa/rpi/controller/rpi/sharpen.cpp b/src/ipa/rpi/controller/rpi/sharpen.cpp
> index 1d143ff53287..e8723916c965 100644
> --- a/src/ipa/rpi/controller/rpi/sharpen.cpp
> +++ b/src/ipa/rpi/controller/rpi/sharpen.cpp
> @@ -37,7 +37,7 @@ void Sharpen::switchMode(CameraMode const &cameraMode,
>   	modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
>   }
>   
> -int Sharpen::read(const libcamera::YamlObject &params)
> +int Sharpen::read(const libcamera::ValueNode &params)
>   {
>   	threshold_ = params["threshold"].get<double>(1.0);
>   	strength_ = params["strength"].get<double>(1.0);
> diff --git a/src/ipa/rpi/controller/rpi/sharpen.h b/src/ipa/rpi/controller/rpi/sharpen.h
> index 96ccd60934f8..2814ec85fef1 100644
> --- a/src/ipa/rpi/controller/rpi/sharpen.h
> +++ b/src/ipa/rpi/controller/rpi/sharpen.h
> @@ -19,7 +19,7 @@ public:
>   	Sharpen(Controller *controller);
>   	char const *name() const override;
>   	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void setStrength(double strength) override;
>   	void prepare(Metadata *imageMetadata) override;
>   
> diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp
> index 3422adfe7dee..3cbecf5379ac 100644
> --- a/src/ipa/rpi/controller/rpi/tonemap.cpp
> +++ b/src/ipa/rpi/controller/rpi/tonemap.cpp
> @@ -27,7 +27,7 @@ char const *Tonemap::name() const
>   	return NAME;
>   }
>   
> -int Tonemap::read(const libcamera::YamlObject &params)
> +int Tonemap::read(const libcamera::ValueNode &params)
>   {
>   	config_.detailConstant = params["detail_constant"].get<uint16_t>(0);
>   	config_.detailSlope = params["detail_slope"].get<double>(0.1);
> diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h
> index 4e513b1d00da..4d486d136499 100644
> --- a/src/ipa/rpi/controller/rpi/tonemap.h
> +++ b/src/ipa/rpi/controller/rpi/tonemap.h
> @@ -25,7 +25,7 @@ class Tonemap : public Algorithm
>   public:
>   	Tonemap(Controller *controller = NULL);
>   	char const *name() const override;
> -	int read(const libcamera::YamlObject &params) override;
> +	int read(const libcamera::ValueNode &params) override;
>   	void initialise() override;
>   	void prepare(Metadata *imageMetadata) override;
>   
> diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp
> index 464e43c278f3..677be56ed669 100644
> --- a/src/ipa/simple/algorithms/blc.cpp
> +++ b/src/ipa/simple/algorithms/blc.cpp
> @@ -24,7 +24,7 @@ BlackLevel::BlackLevel()
>   }
>   
>   int BlackLevel::init([[maybe_unused]] IPAContext &context,
> -		     const YamlObject &tuningData)
> +		     const ValueNode &tuningData)
>   {
>   	auto blackLevel = tuningData["blackLevel"].get<int16_t>();
>   	if (blackLevel.has_value()) {
> diff --git a/src/ipa/simple/algorithms/blc.h b/src/ipa/simple/algorithms/blc.h
> index a5592d08740f..2933ff1fffe7 100644
> --- a/src/ipa/simple/algorithms/blc.h
> +++ b/src/ipa/simple/algorithms/blc.h
> @@ -22,7 +22,7 @@ public:
>   	BlackLevel();
>   	~BlackLevel() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
>   	void prepare(IPAContext &context,
>   		     const uint32_t frame,
> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
> index 0a98406c1a3a..ec66f0193478 100644
> --- a/src/ipa/simple/algorithms/ccm.cpp
> +++ b/src/ipa/simple/algorithms/ccm.cpp
> @@ -27,7 +27,7 @@ namespace ipa::soft::algorithms {
>   
>   LOG_DEFINE_CATEGORY(IPASoftCcm)
>   
> -int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
> +int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>   {
>   	int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm");
>   	if (ret < 0) {
> diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h
> index 8279a3d5967e..3e3755165c03 100644
> --- a/src/ipa/simple/algorithms/ccm.h
> +++ b/src/ipa/simple/algorithms/ccm.h
> @@ -25,7 +25,7 @@ public:
>   	Ccm() = default;
>   	~Ccm() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context,
>   		      const IPAConfigInfo &configInfo) override;
>   	void queueRequest(typename Module::Context &context,
> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp
> index 54cb804e7822..5e09bce79ee1 100644
> --- a/src/ipa/simple/algorithms/lut.cpp
> +++ b/src/ipa/simple/algorithms/lut.cpp
> @@ -25,7 +25,7 @@ LOG_DEFINE_CATEGORY(IPASoftLut)
>   namespace ipa::soft::algorithms {
>   
>   int Lut::init(IPAContext &context,
> -	      [[maybe_unused]] const YamlObject &tuningData)
> +	      [[maybe_unused]] const ValueNode &tuningData)
>   {
>   	context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f);
>   	return 0;
> diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h
> index ba8b9021b37e..a3f8fac23b19 100644
> --- a/src/ipa/simple/algorithms/lut.h
> +++ b/src/ipa/simple/algorithms/lut.h
> @@ -19,7 +19,7 @@ public:
>   	Lut() = default;
>   	~Lut() = default;
>   
> -	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
>   	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
>   	void queueRequest(typename Module::Context &context,
>   			  const uint32_t frame,
> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
> index 57836c73ccfa..6200072b9268 100644
> --- a/src/ipa/simple/soft_simple.cpp
> +++ b/src/ipa/simple/soft_simple.cpp
> @@ -120,7 +120,7 @@ int IPASoftSimple::init(const IPASettings &settings,
>   		return ret;
>   	}
>   
> -	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
>   	if (!data)
>   		return -EINVAL;
>   
> diff --git a/src/libcamera/converter/converter_dw100.cpp b/src/libcamera/converter/converter_dw100.cpp
> index 5782cd0b21b7..86b530c13f96 100644
> --- a/src/libcamera/converter/converter_dw100.cpp
> +++ b/src/libcamera/converter/converter_dw100.cpp
> @@ -97,7 +97,7 @@ ConverterDW100Module::createModule(DeviceEnumerator *enumerator)
>    * \sa Dw100VertexMap::setDewarpParams()
>    * \return 0 if successful, an error code otherwise
>    */
> -int ConverterDW100Module::init(const YamlObject &params)
> +int ConverterDW100Module::init(const ValueNode &params)
>   {
>   	DewarpParms dp;
>   
> diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp
> index 621008844c74..010c1d8c35d9 100644
> --- a/src/libcamera/geometry.cpp
> +++ b/src/libcamera/geometry.cpp
> @@ -12,7 +12,7 @@
>   
>   #include <libcamera/base/log.h>
>   
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   /**
>    * \file geometry.h
> @@ -933,7 +933,7 @@ std::ostream &operator<<(std::ostream &out, const Rectangle &r)
>    */
>   template<>
>   std::optional<Size>
> -YamlObject::Accessor<Size>::get(const YamlObject &obj) const
> +ValueNode::Accessor<Size>::get(const ValueNode &obj) const
>   {
>   	if (obj.type_ != Type::List)
>   		return std::nullopt;
> diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp
> index 99d16e7c38c6..c4999d32d7c7 100644
> --- a/src/libcamera/global_configuration.cpp
> +++ b/src/libcamera/global_configuration.cpp
> @@ -65,7 +65,7 @@ bool GlobalConfiguration::loadFile(const std::filesystem::path &fileName)
>   		return true;
>   	}
>   
> -	std::unique_ptr<YamlObject> configuration = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> configuration = YamlParser::parse(file);
>   	if (!configuration) {
>   		LOG(Configuration, Error)
>   			<< "Failed to parse configuration file " << fileName;
> @@ -146,7 +146,7 @@ GlobalConfiguration::GlobalConfiguration()
>   std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
>   	const std::initializer_list<std::string_view> confPath) const
>   {
> -	const YamlObject *c = &configuration();
> +	const ValueNode *c = &configuration();
>   	for (auto part : confPath) {
>   		c = &(*c)[part];
>   		if (!*c)
> @@ -237,10 +237,10 @@ unsigned int GlobalConfiguration::version() const
>    * This returns the whole configuration stored in the top-level section
>    * `%configuration` of the YAML configuration file.
>    *
> - * The requested part of the configuration can be accessed using \a YamlObject
> + * The requested part of the configuration can be accessed using \a ValueNode
>    * methods.
>    *
> - * \note \a YamlObject type itself shouldn't be used in type declarations to
> + * \note \a ValueNode type itself shouldn't be used in type declarations to
>    * avoid trouble if we decide to change the underlying data objects in future.
>    *
>    * \return The whole configuration section
> diff --git a/src/libcamera/matrix.cpp b/src/libcamera/matrix.cpp
> index b7c07e896538..4fe210830421 100644
> --- a/src/libcamera/matrix.cpp
> +++ b/src/libcamera/matrix.cpp
> @@ -314,7 +314,7 @@ template bool matrixInvert<double>(Span<const double> data, Span<double> dataOut
>    * to the product of the number of rows and columns of the matrix (Rows x
>    * Cols). The values shall be stored in row-major order.
>    */
> -bool matrixValidateYaml(const YamlObject &obj, unsigned int size)
> +bool matrixValidateYaml(const ValueNode &obj, unsigned int size)
>   {
>   	if (!obj.isList())
>   		return false;
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index da89aa3714c3..a24c1ec63c96 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -57,8 +57,8 @@ libcamera_internal_sources = files([
>       'v4l2_request.cpp',
>       'v4l2_subdevice.cpp',
>       'v4l2_videodevice.cpp',
> +    'value_node.cpp',
>       'vector.cpp',
> -    'yaml_object.cpp',
>       'yaml_parser.cpp',
>   ])
>   
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> index c7e3b185f89b..b2cfcf7395ca 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> @@ -443,7 +443,7 @@ int RkISP1CameraData::loadTuningFile(const std::string &path)
>   		return ret;
>   	}
>   
> -	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
>   	if (!data)
>   		return -EINVAL;
>   
> diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> index 684438bd5fb7..a2929bca2a03 100644
> --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> @@ -1116,7 +1116,7 @@ int CameraData::loadPipelineConfiguration()
>   
>   	LOG(RPI, Info) << "Using configuration file '" << filename << "'";
>   
> -	std::unique_ptr<YamlObject> root = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> root = YamlParser::parse(file);
>   	if (!root) {
>   		LOG(RPI, Warning) << "Failed to parse configuration file, using defaults";
>   		return 0;
> @@ -1129,7 +1129,7 @@ int CameraData::loadPipelineConfiguration()
>   		return 0;
>   	}
>   
> -	const YamlObject &phConfig = (*root)["pipeline_handler"];
> +	const ValueNode &phConfig = (*root)["pipeline_handler"];
>   
>   	if (phConfig.contains("disable_startup_frame_drops"))
>   		LOG(RPI, Warning)
> diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h
> index c69a690f580c..d959f27705ef 100644
> --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h
> +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h
> @@ -26,7 +26,7 @@
>   #include "libcamera/internal/pipeline_handler.h"
>   #include "libcamera/internal/request.h"
>   #include "libcamera/internal/v4l2_videodevice.h"
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include <libcamera/ipa/raspberrypi_ipa_interface.h>
>   #include <libcamera/ipa/raspberrypi_ipa_proxy.h>
> @@ -96,7 +96,7 @@ public:
>   	virtual V4L2VideoDevice::Formats rawFormats() const = 0;
>   	virtual V4L2VideoDevice *frontendDevice() = 0;
>   
> -	virtual int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) = 0;
> +	virtual int platformPipelineConfigure(const std::unique_ptr<ValueNode> &root) = 0;
>   
>   	std::unique_ptr<ipa::RPi::IPAProxyRPi> ipa_;
>   
> diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp
> index 7bcba32b9b58..f4133de47f23 100644
> --- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp
> +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp
> @@ -743,7 +743,7 @@ public:
>   	CameraConfiguration::Status
>   	platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override;
>   
> -	int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override;
> +	int platformPipelineConfigure(const std::unique_ptr<ValueNode> &root) override;
>   
>   	void platformStart() override;
>   	void platformStop() override;
> @@ -1333,7 +1333,7 @@ PiSPCameraData::platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const
>   	return status;
>   }
>   
> -int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root)
> +int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<ValueNode> &root)
>   {
>   	config_ = {
>   		.numCfeConfigStatsBuffers = 12,
> @@ -1358,7 +1358,7 @@ int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject>
>   		return -EINVAL;
>   	}
>   
> -	const YamlObject &phConfig = (*root)["pipeline_handler"];
> +	const ValueNode &phConfig = (*root)["pipeline_handler"];
>   	config_.numCfeConfigStatsBuffers =
>   		phConfig["num_cfe_config_stats_buffers"].get<unsigned int>(config_.numCfeConfigStatsBuffers);
>   	config_.numCfeConfigQueue =
> diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
> index 8a80439e9082..b5d4a2f670a9 100644
> --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp
> +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
> @@ -69,7 +69,7 @@ public:
>   
>   	CameraConfiguration::Status platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override;
>   
> -	int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override;
> +	int platformPipelineConfigure(const std::unique_ptr<ValueNode> &root) override;
>   
>   	void platformStart() override;
>   	void platformStop() override;
> @@ -498,7 +498,7 @@ CameraConfiguration::Status Vc4CameraData::platformValidate(RPi::RPiCameraConfig
>   	return status;
>   }
>   
> -int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root)
> +int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<ValueNode> &root)
>   {
>   	config_ = {
>   		.minUnicamBuffers = 2,
> @@ -521,7 +521,7 @@ int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &
>   		return -EINVAL;
>   	}
>   
> -	const YamlObject &phConfig = (*root)["pipeline_handler"];
> +	const ValueNode &phConfig = (*root)["pipeline_handler"];
>   	config_.minUnicamBuffers =
>   		phConfig["min_unicam_buffers"].get<unsigned int>(config_.minUnicamBuffers);
>   	config_.minTotalUnicamBuffers =
> diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md
> index a9f39c15172b..0a02a9ea44b3 100644
> --- a/src/libcamera/pipeline/virtual/README.md
> +++ b/src/libcamera/pipeline/virtual/README.md
> @@ -48,7 +48,7 @@ in Virtual Pipeline Handler. `parseConfigFile()` is exposed to use in
>   Virtual Pipeline Handler.
>   
>   This is the procedure of the Parser class:
> -1. `parseConfigFile()` parses the config file to `YamlObject` using `YamlParser::parse()`.
> +1. `parseConfigFile()` parses the config file to `ValueNode` using `YamlParser::parse()`.
>       - Parse the top level of config file which are the camera ids and look into
>         each camera properties.
>   2. For each camera, `parseCameraConfigData()` returns a camera with the configuration.
> diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp
> index fdc729509371..5169fd39bc38 100644
> --- a/src/libcamera/pipeline/virtual/config_parser.cpp
> +++ b/src/libcamera/pipeline/virtual/config_parser.cpp
> @@ -29,7 +29,7 @@ ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
>   {
>   	std::vector<std::unique_ptr<VirtualCameraData>> configurations;
>   
> -	std::unique_ptr<YamlObject> cameras = YamlParser::parse(file);
> +	std::unique_ptr<ValueNode> cameras = YamlParser::parse(file);
>   	if (!cameras) {
>   		LOG(Virtual, Error) << "Failed to parse config file.";
>   		return configurations;
> @@ -72,7 +72,7 @@ ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
>   }
>   
>   std::unique_ptr<VirtualCameraData>
> -ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData,
> +ConfigParser::parseCameraConfigData(const ValueNode &cameraConfigData,
>   				    PipelineHandler *pipe)
>   {
>   	std::vector<VirtualCameraData::Resolution> resolutions;
> @@ -94,13 +94,13 @@ ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData,
>   	return data;
>   }
>   
> -int ConfigParser::parseSupportedFormats(const YamlObject &cameraConfigData,
> +int ConfigParser::parseSupportedFormats(const ValueNode &cameraConfigData,
>   					std::vector<VirtualCameraData::Resolution> *resolutions)
>   {
>   	if (cameraConfigData.contains("supported_formats")) {
> -		const YamlObject &supportedResolutions = cameraConfigData["supported_formats"];
> +		const ValueNode &supportedResolutions = cameraConfigData["supported_formats"];
>   
> -		for (const YamlObject &supportedResolution : supportedResolutions.asList()) {
> +		for (const ValueNode &supportedResolution : supportedResolutions.asList()) {
>   			unsigned int width = supportedResolution["width"].get<unsigned int>(1920);
>   			unsigned int height = supportedResolution["height"].get<unsigned int>(1080);
>   			if (width == 0 || height == 0) {
> @@ -152,7 +152,7 @@ int ConfigParser::parseSupportedFormats(const YamlObject &cameraConfigData,
>   	return 0;
>   }
>   
> -int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data)
> +int ConfigParser::parseFrameGenerator(const ValueNode &cameraConfigData, VirtualCameraData *data)
>   {
>   	const std::string testPatternKey = "test_pattern";
>   	const std::string framesKey = "frames";
> @@ -178,7 +178,7 @@ int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, Virtua
>   		return 0;
>   	}
>   
> -	const YamlObject &frames = cameraConfigData[framesKey];
> +	const ValueNode &frames = cameraConfigData[framesKey];
>   
>   	/* When there is no frames provided in the config file, use color bar test pattern */
>   	if (!frames) {
> @@ -231,7 +231,7 @@ int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, Virtua
>   	return 0;
>   }
>   
> -int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data)
> +int ConfigParser::parseLocation(const ValueNode &cameraConfigData, VirtualCameraData *data)
>   {
>   	/* Default value is properties::CameraLocationFront */
>   	int32_t location = properties::CameraLocationFront;
> @@ -252,7 +252,7 @@ int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCamer
>   	return 0;
>   }
>   
> -int ConfigParser::parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data)
> +int ConfigParser::parseModel(const ValueNode &cameraConfigData, VirtualCameraData *data)
>   {
>   	std::string model = cameraConfigData["model"].get<std::string>("Unknown");
>   
> diff --git a/src/libcamera/pipeline/virtual/config_parser.h b/src/libcamera/pipeline/virtual/config_parser.h
> index f696d8862897..97d6dffa31ec 100644
> --- a/src/libcamera/pipeline/virtual/config_parser.h
> +++ b/src/libcamera/pipeline/virtual/config_parser.h
> @@ -13,7 +13,7 @@
>   #include <libcamera/base/file.h>
>   
>   #include "libcamera/internal/pipeline_handler.h"
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "virtual.h"
>   
> @@ -27,13 +27,13 @@ public:
>   
>   private:
>   	std::unique_ptr<VirtualCameraData>
> -	parseCameraConfigData(const YamlObject &cameraConfigData, PipelineHandler *pipe);
> +	parseCameraConfigData(const ValueNode &cameraConfigData, PipelineHandler *pipe);
>   
> -	int parseSupportedFormats(const YamlObject &cameraConfigData,
> +	int parseSupportedFormats(const ValueNode &cameraConfigData,
>   				  std::vector<VirtualCameraData::Resolution> *resolutions);
> -	int parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data);
> -	int parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data);
> -	int parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data);
> +	int parseFrameGenerator(const ValueNode &cameraConfigData, VirtualCameraData *data);
> +	int parseLocation(const ValueNode &cameraConfigData, VirtualCameraData *data);
> +	int parseModel(const ValueNode &cameraConfigData, VirtualCameraData *data);
>   };
>   
>   } /* namespace libcamera */
> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
> index b032db54ded4..90f2110ade8c 100644
> --- a/src/libcamera/pipeline/virtual/virtual.cpp
> +++ b/src/libcamera/pipeline/virtual/virtual.cpp
> @@ -36,7 +36,7 @@
>   #include "libcamera/internal/framebuffer.h"
>   #include "libcamera/internal/pipeline_handler.h"
>   #include "libcamera/internal/request.h"
> -#include "libcamera/internal/yaml_object.h"
> +#include "libcamera/internal/value_node.h"
>   
>   #include "pipeline/virtual/config_parser.h"
>   
> diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp
> new file mode 100644
> index 000000000000..7374e7bdde8c
> --- /dev/null
> +++ b/src/libcamera/value_node.cpp
> @@ -0,0 +1,484 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2022, Google Inc.
> + * Copyright (C) 2025, Ideas on Board.
> + *
> + * Data structure to manage tree of values
> + */
> +
> +#include "libcamera/internal/value_node.h"
> +
> +#include <charconv>
> +#include <errno.h>
> +#include <string>
> +#include <vector>
> +
> +#include <libcamera/base/utils.h>
> +
> +/**
> + * \file value_node.h
> + * \brief Data structure to manage tree of values
> + */
> +
> +namespace libcamera {
> +
> +namespace {
> +
> +/* Empty static ValueNode as a safe result for invalid operations */
> +static const ValueNode empty;
> +
> +} /* namespace */
> +
> +/**
> + * \class ValueNode
> + * \brief A class representing a tree structure of values
> + *
> + * The ValueNode class is designed to model a tree of values. Each node in the
> + * tree is represented by a ValueNode instance. Intermediate nodes store
> + * children either as an ordered list (sequence) or a string-indexed dictionary
> + * (mapping). Leaf nodes can be empty or store a string value.
> + */
> +
> +ValueNode::ValueNode()
> +	: type_(Type::Empty)
> +{
> +}
> +
> +ValueNode::~ValueNode() = default;
> +
> +/**
> + * \fn ValueNode::isValue()
> + * \brief Return whether the ValueNode is a value
> + *
> + * \return True if the ValueNode is a value, false otherwise
> + */
> +
> +/**
> + * \fn ValueNode::isList()
> + * \brief Return whether the ValueNode is a list
> + *
> + * \return True if the ValueNode is a list, false otherwise
> + */
> +
> +/**
> + * \fn ValueNode::isDictionary()
> + * \brief Return whether the ValueNode is a dictionary
> + *
> + * \return True if the ValueNode is a dictionary, false otherwise
> + */
> +
> +/**
> + * \fn ValueNode::isEmpty()
> + * \brief Return whether the ValueNode is an empty
> + *
> + * \return True if the ValueNode is empty, false otherwise
> + */
> +
> +/**
> + * \fn ValueNode::operator bool()
> + * \brief Return whether the ValueNode is a non-empty
> + *
> + * \return False if the ValueNode is empty, true otherwise
> + */
> +
> +/**
> + * \fn ValueNode::size()
> + * \brief Retrieve the number of elements in a dictionary or list ValueNode
> + *
> + * This function retrieves the size of the ValueNode, defined as the number of
> + * child elements it contains. Only ValueNode instances of Dictionary or List
> + * types have a size, calling this function on other types of instances is
> + * invalid and results in undefined behaviour.
> + *
> + * \return The size of the ValueNode
> + */
> +std::size_t ValueNode::size() const
> +{
> +	switch (type_) {
> +	case Type::Dictionary:
> +	case Type::List:
> +		return list_.size();
> +	default:
> +		return 0;
> +	}
> +}
> +
> +/**
> + * \fn template<typename T> ValueNode::get<T>() const
> + * \brief Parse the ValueNode as a \a T value
> + * \tparam T Type of the value
> + *
> + * This function parses the value of the ValueNode as a \a T object, and
> + * returns the value. If parsing fails (usually because the ValueNode doesn't
> + * store a \a T value), std::nullopt is returned.
> + *
> + * If the type \a T is an std::vector, the ValueNode will be parsed as a list
> + * of values.
> + *
> + * \return The ValueNode value, or std::nullopt if parsing failed
> + */
> +
> +/**
> + * \fn template<typename T, typename U> ValueNode::get<T>(U &&defaultValue) const
> + * \brief Parse the ValueNode as a \a T value
> + * \tparam T Type of the value
> + * \tparam U Type of the default value
> + * \param[in] defaultValue The default value when failing to parse
> + *
> + * This function parses the value of the ValueNode as a \a T object, and
> + * returns the value. If parsing fails (usually because the ValueNode doesn't
> + * store a \a T value), the \a defaultValue is returned. Type \a U must be
> + * convertible to type \a T.
> + *
> + * Unlike the get() function, this overload does not support std::vector for the
> + * type \a T.
> + *
> + * \return The ValueNode value, or \a defaultValue if parsing failed
> + */
> +
> +/**
> + * \fn template<typename T> ValueNode::set<T>(T &&value)
> + * \brief Set the value of a ValueNode
> + * \tparam T Type of the value
> + * \param[in] value The value
> + *
> + * This function sets the value stored in a ValueNode to \a value. The value is
> + * converted to a string in an implementation-specific way that guarantees that
> + * subsequent calls to get<T>() will return the same value.
> + *
> + * Values can only be set on ValueNode of Type::Value type or empty ValueNode.
> + * Attempting to set a value on a node of type Type::Dict or Type::List does not
> + * modify the ValueNode.
> + */
> +
> +#ifndef __DOXYGEN__
> +
> +template<>
> +std::optional<bool>
> +ValueNode::Accessor<bool>::get(const ValueNode &obj) const
> +{
> +	if (obj.type_ != Type::Value)
> +		return std::nullopt;
> +
> +	if (obj.value_ == "true")
> +		return true;
> +	else if (obj.value_ == "false")
> +		return false;
> +
> +	return std::nullopt;
> +}
> +
> +template<>
> +void ValueNode::Accessor<bool>::set(ValueNode &obj, bool value)
> +{
> +	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> +		return;
> +
> +	obj.type_ = Type::Value;
> +	obj.value_ = value ? "true" : "false";
> +}
> +
> +template<typename T>
> +struct ValueNode::Accessor<T, std::enable_if_t<
> +	std::is_same_v<int8_t, T> ||
> +	std::is_same_v<uint8_t, T> ||
> +	std::is_same_v<int16_t, T> ||
> +	std::is_same_v<uint16_t, T> ||
> +	std::is_same_v<int32_t, T> ||
> +	std::is_same_v<uint32_t, T>>>
> +{
> +	std::optional<T> get(const ValueNode &obj) const
> +	{
> +		if (obj.type_ != Type::Value)
> +			return std::nullopt;
> +
> +		const std::string &str = obj.value_;
> +		T value;
> +
> +		auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(),
> +						 value);
> +		if (ptr != str.data() + str.size() || ec != std::errc())
> +			return std::nullopt;
> +
> +		return value;
> +	}
> +
> +	void set(ValueNode &obj, T value)
> +	{
> +		if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> +			return;
> +
> +		obj.type_ = Type::Value;
> +		obj.value_ = std::to_string(value);
> +	}
> +};
> +
> +template struct ValueNode::Accessor<int8_t>;
> +template struct ValueNode::Accessor<uint8_t>;
> +template struct ValueNode::Accessor<int16_t>;
> +template struct ValueNode::Accessor<uint16_t>;
> +template struct ValueNode::Accessor<int32_t>;
> +template struct ValueNode::Accessor<uint32_t>;
> +
> +template<>
> +std::optional<float>
> +ValueNode::Accessor<float>::get(const ValueNode &obj) const
> +{
> +	return obj.get<double>();
> +}
> +
> +template<>
> +void ValueNode::Accessor<float>::set(ValueNode &obj, float value)
> +{
> +	obj.set<double>(std::forward<float>(value));
> +}
> +
> +template<>
> +std::optional<double>
> +ValueNode::Accessor<double>::get(const ValueNode &obj) const
> +{
> +	if (obj.type_ != Type::Value)
> +		return std::nullopt;
> +
> +	if (obj.value_.empty())
> +		return std::nullopt;
> +
> +	char *end;
> +
> +	errno = 0;
> +	double value = utils::strtod(obj.value_.c_str(), &end);
> +
> +	if ('\0' != *end || errno == ERANGE)
> +		return std::nullopt;
> +
> +	return value;
> +}
> +
> +template<>
> +void ValueNode::Accessor<double>::set(ValueNode &obj, double value)
> +{
> +	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> +		return;
> +
> +	obj.type_ = Type::Value;
> +	obj.value_ = std::to_string(value);
> +}
> +
> +template<>
> +std::optional<std::string>
> +ValueNode::Accessor<std::string>::get(const ValueNode &obj) const
> +{
> +	if (obj.type_ != Type::Value)
> +		return std::nullopt;
> +
> +	return obj.value_;
> +}
> +
> +template<>
> +void ValueNode::Accessor<std::string>::set(ValueNode &obj, std::string value)
> +{
> +	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> +		return;
> +
> +	obj.type_ = Type::Value;
> +	obj.value_ = std::move(value);
> +}
> +
> +template<typename T>
> +struct ValueNode::Accessor<std::vector<T>, std::enable_if_t<
> +	std::is_same_v<bool, T> ||
> +	std::is_same_v<float, T> ||
> +	std::is_same_v<double, T> ||
> +	std::is_same_v<int8_t, T> ||
> +	std::is_same_v<uint8_t, T> ||
> +	std::is_same_v<int16_t, T> ||
> +	std::is_same_v<uint16_t, T> ||
> +	std::is_same_v<int32_t, T> ||
> +	std::is_same_v<uint32_t, T> ||
> +	std::is_same_v<std::string, T>>>
> +{
> +	std::optional<std::vector<T>> get(const ValueNode &obj) const
> +	{
> +		if (obj.type_ != Type::List)
> +			return std::nullopt;
> +
> +		std::vector<T> values;
> +		values.reserve(obj.list_.size());
> +
> +		for (const ValueNode &entry : obj.asList()) {
> +			const auto value = entry.get<T>();
> +			if (!value)
> +				return std::nullopt;
> +			values.emplace_back(*value);
> +		}
> +
> +		return values;
> +	}
> +};
> +
> +template struct ValueNode::Accessor<std::vector<bool>>;
> +template struct ValueNode::Accessor<std::vector<float>>;
> +template struct ValueNode::Accessor<std::vector<double>>;
> +template struct ValueNode::Accessor<std::vector<int8_t>>;
> +template struct ValueNode::Accessor<std::vector<uint8_t>>;
> +template struct ValueNode::Accessor<std::vector<int16_t>>;
> +template struct ValueNode::Accessor<std::vector<uint16_t>>;
> +template struct ValueNode::Accessor<std::vector<int32_t>>;
> +template struct ValueNode::Accessor<std::vector<uint32_t>>;
> +template struct ValueNode::Accessor<std::vector<std::string>>;
> +#endif /* __DOXYGEN__ */
> +
> +/**
> + * \fn ValueNode::asDict() const
> + * \brief Wrap a dictionary ValueNode in an adapter that exposes iterators
> + *
> + * The ValueNode class doesn't directly implement iterators, as the iterator
> + * type depends on whether the node is a Dictionary or List. This function wraps
> + * a ValueNode of Dictionary type into an adapter that exposes iterators, as
> + * well as begin() and end() functions, allowing usage of range-based for loops
> + * with ValueNode. As mappings are not ordered, the iteration order is not
> + * specified.
> + *
> + * The iterator's value_type is a
> + * <em>std::pair<const std::string &, const \ref ValueNode &></em>.
> + *
> + * If the ValueNode is not of Dictionary type, the returned adapter operates
> + * as an empty container.
> + *
> + * \return An adapter of unspecified type compatible with range-based for loops
> + */
> +
> +/**
> + * \fn ValueNode::asList() const
> + * \brief Wrap a list ValueNode in an adapter that exposes iterators
> + *
> + * The ValueNode class doesn't directly implement iterators, as the iterator
> + * type depends on whether the node is a Dictionary or List. This function wraps
> + * a ValueNode of List type into an adapter that exposes iterators, as well as
> + * begin() and end() functions, allowing usage of range-based for loops with
> + * ValueNode. As lists are ordered, the iteration order is matches the order in
> + * which child nodes have been added.
> + *
> + * The iterator's value_type is a <em>const ValueNode &</em>.
> + *
> + * If the ValueNode is not of List type, the returned adapter operates as an
> + * empty container.
> + *
> + * \return An adapter of unspecified type compatible with range-based for loops
> + */
> +
> +/**
> + * \brief Retrieve the element from list ValueNode by index
> + * \param[in] index The element index
> + *
> + * This function retrieves an element of the ValueNode. Only ValueNode
> + * instances of List type associate elements with index, calling this function
> + * on other types of instances or with an invalid index results in an empty
> + * node.
> + *
> + * \return The ValueNode as an element of the list
> + */
> +const ValueNode &ValueNode::operator[](std::size_t index) const
> +{
> +	if (type_ != Type::List || index >= size())
> +		return empty;
> +
> +	return *list_[index].value;
> +}
> +
> +/**
> + * \brief Check if an element of a dictionary exists
> + * \param[in] key The element key
> + *
> + * This function check if the ValueNode contains an element for the given \a
> + * key. Only ValueNode instances of Dictionary type associate elements with
> + * keys, calling this function on other types of instances is invalid and
> + * results in undefined behaviour.
> + *
> + * \return True if an element exists, false otherwise
> + */
> +bool ValueNode::contains(std::string_view key) const
> +{
> +	return dictionary_.find(key) != dictionary_.end();
> +}
> +
> +/**
> + * \brief Retrieve a member by key from the dictionary
> + * \param[in] key The element key
> + *
> + * This function retrieves a member of a ValueNode by \a key. Only ValueNode
> + * instances of Dictionary type associate elements with keys, calling this
> + * function on other types of instances or with a nonexistent key results in an
> + * empty node.
> + *
> + * \return The ValueNode corresponding to the \a key member
> + */
> +const ValueNode &ValueNode::operator[](std::string_view key) const
> +{
> +	if (type_ != Type::Dictionary)
> +		return empty;
> +
> +	auto iter = dictionary_.find(key);
> +	if (iter == dictionary_.end())
> +		return empty;
> +
> +	return *iter->second;
> +}
> +
> +/**
> + * \brief Add a child node to a list
> + * \param[in] child The child node
> + *
> + * Append the \a child node as the last element of this node's children list.
> + * This node must be empty, in which case it is converted to the Type::List
> + * type, or be a list. Otherwise, the \a child is discarded and the function
> + * returns a nullptr.
> + *
> + * \return A pointer to the \a child node if successfully added, nullptr
> + * otherwise
> + */
> +ValueNode *ValueNode::add(std::unique_ptr<ValueNode> child)
> +{
> +	if (type_ == Type::Empty)
> +		type_ = Type::List;
> +
> +	if (type_ != Type::List)
> +		return nullptr;
> +
> +	Value &elem = list_.emplace_back(std::string{}, std::move(child));
> +	return elem.value.get();
> +}
> +
> +/**
> + * \brief Add a child node to a dictionary
> + * \param[in] key The dictionary key
> + * \param[in] child The child node
> + *
> + * Add the \a child node with the given \a key to this node's children. This
> + * node must be empty, in which case it is converted to the Type::Dictionary
> + * type, or be a dictionary. Otherwise, the \a child is discarded and the
> + * function returns a nullptr.
> + *
> + * Keys are unique. If a child with the same \a key already exist, the \a child
> + * is discarded and the function returns a nullptr.
> + *
> + * \return A pointer to the \a child node if successfully added, nullptr
> + * otherwise
> + */
> +ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)
> +{
> +	if (type_ == Type::Empty)
> +		type_ = Type::Dictionary;
> +
> +	if (type_ != Type::Dictionary)
> +		return nullptr;
> +
> +	if (dictionary_.find(key) != dictionary_.end())
> +		return nullptr;
> +
> +	Value &elem = list_.emplace_back(std::move(key), std::move(child));
> +	dictionary_.emplace(elem.key, elem.value.get());
> +	return elem.value.get();
> +}
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp
> index 4dad1b9001c5..86b9f9bb6d5e 100644
> --- a/src/libcamera/vector.cpp
> +++ b/src/libcamera/vector.cpp
> @@ -337,7 +337,7 @@ LOG_DEFINE_CATEGORY(Vector)
>    */
>   
>   #ifndef __DOXYGEN__
> -bool vectorValidateYaml(const YamlObject &obj, unsigned int size)
> +bool vectorValidateYaml(const ValueNode &obj, unsigned int size)
>   {
>   	if (!obj.isList())
>   		return false;
> diff --git a/src/libcamera/yaml_object.cpp b/src/libcamera/yaml_object.cpp
> deleted file mode 100644
> index 4328ccbff8f1..000000000000
> --- a/src/libcamera/yaml_object.cpp
> +++ /dev/null
> @@ -1,480 +0,0 @@
> -/* SPDX-License-Identifier: LGPL-2.1-or-later */
> -/*
> - * Copyright (C) 2022, Google Inc.
> - * Copyright (C) 2025, Ideas on Board.
> - *
> - * libcamera YAML object
> - */
> -
> -#include "libcamera/internal/yaml_object.h"
> -
> -#include <charconv>
> -#include <errno.h>
> -#include <string>
> -#include <vector>
> -
> -#include <libcamera/base/utils.h>
> -
> -/**
> - * \file yaml_object.h
> - * \brief YAML objects
> - */
> -
> -namespace libcamera {
> -
> -namespace {
> -
> -/* Empty static YamlObject as a safe result for invalid operations */
> -static const YamlObject empty;
> -
> -} /* namespace */
> -
> -/**
> - * \class YamlObject
> - * \brief A class representing the tree structure of the YAML content
> - *
> - * The YamlObject class represents the tree structure of YAML content. A
> - * YamlObject can be empty, a dictionary or list of YamlObjects, or a value if a
> - * tree leaf.
> - */
> -
> -YamlObject::YamlObject()
> -	: type_(Type::Empty)
> -{
> -}
> -
> -YamlObject::~YamlObject() = default;
> -
> -/**
> - * \fn YamlObject::isValue()
> - * \brief Return whether the YamlObject is a value
> - *
> - * \return True if the YamlObject is a value, false otherwise
> - */
> -
> -/**
> - * \fn YamlObject::isList()
> - * \brief Return whether the YamlObject is a list
> - *
> - * \return True if the YamlObject is a list, false otherwise
> - */
> -
> -/**
> - * \fn YamlObject::isDictionary()
> - * \brief Return whether the YamlObject is a dictionary
> - *
> - * \return True if the YamlObject is a dictionary, false otherwise
> - */
> -
> -/**
> - * \fn YamlObject::isEmpty()
> - * \brief Return whether the YamlObject is an empty
> - *
> - * \return True if the YamlObject is empty, false otherwise
> - */
> -
> -/**
> - * \fn YamlObject::operator bool()
> - * \brief Return whether the YamlObject is a non-empty
> - *
> - * \return False if the YamlObject is empty, true otherwise
> - */
> -
> -/**
> - * \brief Retrieve the number of elements in a dictionary or list YamlObject
> - *
> - * This function retrieves the size of the YamlObject, defined as the number of
> - * child elements it contains. Only YamlObject instances of Dictionary or List
> - * types have a size, calling this function on other types of instances is
> - * invalid and results in undefined behaviour.
> - *
> - * \return The size of the YamlObject
> - */
> -std::size_t YamlObject::size() const
> -{
> -	switch (type_) {
> -	case Type::Dictionary:
> -	case Type::List:
> -		return list_.size();
> -	default:
> -		return 0;
> -	}
> -}
> -
> -/**
> - * \fn template<typename T> YamlObject::get<T>() const
> - * \brief Parse the YamlObject as a \a T value
> - * \tparam T Type of the value
> - *
> - * This function parses the value of the YamlObject as a \a T object, and
> - * returns the value. If parsing fails (usually because the YamlObject doesn't
> - * store a \a T value), std::nullopt is returned.
> - *
> - * If the type \a T is an std::vector, the YamlObject will be parsed as a list
> - * of values.
> - *
> - * \return The YamlObject value, or std::nullopt if parsing failed
> - */
> -
> -/**
> - * \fn template<typename T, typename U> YamlObject::get<T>(U &&defaultValue) const
> - * \brief Parse the YamlObject as a \a T value
> - * \tparam T Type of the value
> - * \tparam U Type of the default value
> - * \param[in] defaultValue The default value when failing to parse
> - *
> - * This function parses the value of the YamlObject as a \a T object, and
> - * returns the value. If parsing fails (usually because the YamlObject doesn't
> - * store a \a T value), the \a defaultValue is returned. Type \a U must be
> - * convertible to type \a T.
> - *
> - * Unlike the get() function, this overload does not support std::vector for the
> - * type \a T.
> - *
> - * \return The YamlObject value, or \a defaultValue if parsing failed
> - */
> -
> -/**
> - * \fn template<typename T> YamlObject::set<T>(T &&value)
> - * \brief Set the value of a YamlObject
> - * \tparam T Type of the value
> - * \param[in] value The value
> - *
> - * This function sets the value stored in a YamlObject to \a value. The value is
> - * converted to a string in an implementation-specific way that guarantees that
> - * subsequent calls to get<T>() will return the same value.
> - *
> - * Values can only be set on YamlObject of Type::Value type or empty YamlObject.
> - * Attempting to set a value on an object of type Type::Dict or Type::List does
> - * not modify the YamlObject.
> - */
> -
> -#ifndef __DOXYGEN__
> -
> -template<>
> -std::optional<bool>
> -YamlObject::Accessor<bool>::get(const YamlObject &obj) const
> -{
> -	if (obj.type_ != Type::Value)
> -		return std::nullopt;
> -
> -	if (obj.value_ == "true")
> -		return true;
> -	else if (obj.value_ == "false")
> -		return false;
> -
> -	return std::nullopt;
> -}
> -
> -template<>
> -void YamlObject::Accessor<bool>::set(YamlObject &obj, bool value)
> -{
> -	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> -		return;
> -
> -	obj.type_ = Type::Value;
> -	obj.value_ = value ? "true" : "false";
> -}
> -
> -template<typename T>
> -struct YamlObject::Accessor<T, std::enable_if_t<
> -	std::is_same_v<int8_t, T> ||
> -	std::is_same_v<uint8_t, T> ||
> -	std::is_same_v<int16_t, T> ||
> -	std::is_same_v<uint16_t, T> ||
> -	std::is_same_v<int32_t, T> ||
> -	std::is_same_v<uint32_t, T>>>
> -{
> -	std::optional<T> get(const YamlObject &obj) const
> -	{
> -		if (obj.type_ != Type::Value)
> -			return std::nullopt;
> -
> -		const std::string &str = obj.value_;
> -		T value;
> -
> -		auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(),
> -						 value);
> -		if (ptr != str.data() + str.size() || ec != std::errc())
> -			return std::nullopt;
> -
> -		return value;
> -	}
> -
> -	void set(YamlObject &obj, T value)
> -	{
> -		if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> -			return;
> -
> -		obj.type_ = Type::Value;
> -		obj.value_ = std::to_string(value);
> -	}
> -};
> -
> -template struct YamlObject::Accessor<int8_t>;
> -template struct YamlObject::Accessor<uint8_t>;
> -template struct YamlObject::Accessor<int16_t>;
> -template struct YamlObject::Accessor<uint16_t>;
> -template struct YamlObject::Accessor<int32_t>;
> -template struct YamlObject::Accessor<uint32_t>;
> -
> -template<>
> -std::optional<float>
> -YamlObject::Accessor<float>::get(const YamlObject &obj) const
> -{
> -	return obj.get<double>();
> -}
> -
> -template<>
> -void YamlObject::Accessor<float>::set(YamlObject &obj, float value)
> -{
> -	obj.set<double>(std::forward<float>(value));
> -}
> -
> -template<>
> -std::optional<double>
> -YamlObject::Accessor<double>::get(const YamlObject &obj) const
> -{
> -	if (obj.type_ != Type::Value)
> -		return std::nullopt;
> -
> -	if (obj.value_.empty())
> -		return std::nullopt;
> -
> -	char *end;
> -
> -	errno = 0;
> -	double value = utils::strtod(obj.value_.c_str(), &end);
> -
> -	if ('\0' != *end || errno == ERANGE)
> -		return std::nullopt;
> -
> -	return value;
> -}
> -
> -template<>
> -void YamlObject::Accessor<double>::set(YamlObject &obj, double value)
> -{
> -	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> -		return;
> -
> -	obj.type_ = Type::Value;
> -	obj.value_ = std::to_string(value);
> -}
> -
> -template<>
> -std::optional<std::string>
> -YamlObject::Accessor<std::string>::get(const YamlObject &obj) const
> -{
> -	if (obj.type_ != Type::Value)
> -		return std::nullopt;
> -
> -	return obj.value_;
> -}
> -
> -template<>
> -void YamlObject::Accessor<std::string>::set(YamlObject &obj, std::string value)
> -{
> -	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
> -		return;
> -
> -	obj.type_ = Type::Value;
> -	obj.value_ = std::move(value);
> -}
> -
> -template<typename T>
> -struct YamlObject::Accessor<std::vector<T>, std::enable_if_t<
> -	std::is_same_v<bool, T> ||
> -	std::is_same_v<float, T> ||
> -	std::is_same_v<double, T> ||
> -	std::is_same_v<int8_t, T> ||
> -	std::is_same_v<uint8_t, T> ||
> -	std::is_same_v<int16_t, T> ||
> -	std::is_same_v<uint16_t, T> ||
> -	std::is_same_v<int32_t, T> ||
> -	std::is_same_v<uint32_t, T> ||
> -	std::is_same_v<std::string, T>>>
> -{
> -	std::optional<std::vector<T>> get(const YamlObject &obj) const
> -	{
> -		if (obj.type_ != Type::List)
> -			return std::nullopt;
> -
> -		std::vector<T> values;
> -		values.reserve(obj.list_.size());
> -
> -		for (const YamlObject &entry : obj.asList()) {
> -			const auto value = entry.get<T>();
> -			if (!value)
> -				return std::nullopt;
> -			values.emplace_back(*value);
> -		}
> -
> -		return values;
> -	}
> -};
> -
> -template struct YamlObject::Accessor<std::vector<bool>>;
> -template struct YamlObject::Accessor<std::vector<float>>;
> -template struct YamlObject::Accessor<std::vector<double>>;
> -template struct YamlObject::Accessor<std::vector<int8_t>>;
> -template struct YamlObject::Accessor<std::vector<uint8_t>>;
> -template struct YamlObject::Accessor<std::vector<int16_t>>;
> -template struct YamlObject::Accessor<std::vector<uint16_t>>;
> -template struct YamlObject::Accessor<std::vector<int32_t>>;
> -template struct YamlObject::Accessor<std::vector<uint32_t>>;
> -template struct YamlObject::Accessor<std::vector<std::string>>;
> -#endif /* __DOXYGEN__ */
> -
> -/**
> - * \fn YamlObject::asDict() const
> - * \brief Wrap a dictionary YamlObject in an adapter that exposes iterators
> - *
> - * The YamlObject class doesn't directly implement iterators, as the iterator
> - * type depends on whether the object is a Dictionary or List. This function
> - * wraps a YamlObject of Dictionary type into an adapter that exposes
> - * iterators, as well as begin() and end() functions, allowing usage of
> - * range-based for loops with YamlObject. As YAML mappings are not ordered, the
> - * iteration order is not specified.
> - *
> - * The iterator's value_type is a
> - * <em>std::pair<const std::string &, const \ref YamlObject &></em>.
> - *
> - * If the YamlObject is not of Dictionary type, the returned adapter operates
> - * as an empty container.
> - *
> - * \return An adapter of unspecified type compatible with range-based for loops
> - */
> -
> -/**
> - * \fn YamlObject::asList() const
> - * \brief Wrap a list YamlObject in an adapter that exposes iterators
> - *
> - * The YamlObject class doesn't directly implement iterators, as the iterator
> - * type depends on whether the object is a Dictionary or List. This function
> - * wraps a YamlObject of List type into an adapter that exposes iterators, as
> - * well as begin() and end() functions, allowing usage of range-based for loops
> - * with YamlObject. As YAML lists are ordered, the iteration order is identical
> - * to the list order in the YAML data.
> - *
> - * The iterator's value_type is a <em>const YamlObject &</em>.
> - *
> - * If the YamlObject is not of List type, the returned adapter operates as an
> - * empty container.
> - *
> - * \return An adapter of unspecified type compatible with range-based for loops
> - */
> -
> -/**
> - * \brief Retrieve the element from list YamlObject by index
> - * \param[in] index The element index
> - *
> - * This function retrieves an element of the YamlObject. Only YamlObject
> - * instances of List type associate elements with index, calling this function
> - * on other types of instances or with an invalid index results in an empty
> - * object.
> - *
> - * \return The YamlObject as an element of the list
> - */
> -const YamlObject &YamlObject::operator[](std::size_t index) const
> -{
> -	if (type_ != Type::List || index >= size())
> -		return empty;
> -
> -	return *list_[index].value;
> -}
> -
> -/**
> - * \brief Check if an element of a dictionary exists
> - * \param[in] key The element key
> - *
> - * This function check if the YamlObject contains an element for the given \a
> - * key. Only YamlObject instances of Dictionary type associate elements with
> - * keys, calling this function on other types of instances is invalid and
> - * results in undefined behaviour.
> - *
> - * \return True if an element exists, false otherwise
> - */
> -bool YamlObject::contains(std::string_view key) const
> -{
> -	return dictionary_.find(key) != dictionary_.end();
> -}
> -
> -/**
> - * \brief Retrieve a member by key from the dictionary
> - * \param[in] key The element key
> - *
> - * This function retrieves a member of a YamlObject by \a key. Only YamlObject
> - * instances of Dictionary type associate elements with keys, calling this
> - * function on other types of instances or with a nonexistent key results in an
> - * empty object.
> - *
> - * \return The YamlObject corresponding to the \a key member
> - */
> -const YamlObject &YamlObject::operator[](std::string_view key) const
> -{
> -	if (type_ != Type::Dictionary)
> -		return empty;
> -
> -	auto iter = dictionary_.find(key);
> -	if (iter == dictionary_.end())
> -		return empty;
> -
> -	return *iter->second;
> -}
> -
> -/**
> - * \brief Add a child object to a list
> - * \param[in] child The child object
> - *
> - * Append the \a child node as the last element of this node's children list.
> - * This node must be empty, in which case it is converted to the Type::List
> - * type, or be a list. Otherwise, the \a child is discarded and the function
> - * returns a nullptr.
> - *
> - * \return The child object if successfully added, nullptr otherwise
> - */
> -YamlObject *YamlObject::add(std::unique_ptr<YamlObject> child)
> -{
> -	if (type_ == Type::Empty)
> -		type_ = Type::List;
> -
> -	if (type_ != Type::List)
> -		return nullptr;
> -
> -	Value &elem = list_.emplace_back(std::string{}, std::move(child));
> -	return elem.value.get();
> -}
> -
> -/**
> - * \brief Add a child object to a dictionary
> - * \param[in] key The dictionary key
> - * \param[in] child The child object
> - *
> - * Add the \a child node with the given \a key to this node's children. This
> - * node must be empty, in which case it is converted to the Type::Dictionary
> - * type, or be a dictionary. Otherwise, the \a child is discarded and the
> - * function returns a nullptr.
> - *
> - * Keys are unique. If a child with the same \a key already exist, the \a child
> - * is discarded and the function returns a nullptr.
> - *
> - * \return The child object if successfully added, nullptr otherwise
> - */
> -YamlObject *YamlObject::add(std::string key, std::unique_ptr<YamlObject> child)
> -{
> -	if (type_ == Type::Empty)
> -		type_ = Type::Dictionary;
> -
> -	if (type_ != Type::Dictionary)
> -		return nullptr;
> -
> -	if (dictionary_.find(key) != dictionary_.end())
> -		return nullptr;
> -
> -	Value &elem = list_.emplace_back(std::move(key), std::move(child));
> -	dictionary_.emplace(elem.key, elem.value.get());
> -	return elem.value.get();
> -}
> -
> -} /* namespace libcamera */
> diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
> index d83df0fb9597..b44818319845 100644
> --- a/src/libcamera/yaml_parser.cpp
> +++ b/src/libcamera/yaml_parser.cpp
> @@ -35,7 +35,7 @@ public:
>   	~YamlParserContext();
>   
>   	int init(File &file);
> -	int parseContent(YamlObject &yamlObject);
> +	int parseContent(ValueNode &valueNode);
>   
>   private:
>   	struct EventDeleter {
> @@ -55,7 +55,7 @@ private:
>   	std::string readValue(EventPtr event);
>   	int parseDictionaryOrList(yaml_event_type_t endEventType,
>   				  const std::function<int(EventPtr event)> &parseItem);
> -	int parseNextYamlObject(YamlObject &yamlObject, EventPtr event);
> +	int parseNextNode(ValueNode &valueNode, EventPtr event);
>   
>   	bool parserValid_;
>   	yaml_parser_t parser_;
> @@ -153,15 +153,15 @@ YamlParserContext::EventPtr YamlParserContext::nextEvent()
>   
>   /**
>    * \brief Parse the content of a YAML document
> - * \param[in] yamlObject The result of YamlObject
> + * \param[in] valueNode The result of ValueNode
>    *
>    * Check YAML start and end events of a YAML document, and parse the root object
> - * of the YAML document into a YamlObject.
> + * of the YAML document into a ValueNode.
>    *
>    * \return 0 on success or a negative error code otherwise
>    * \retval -EINVAL The parser has failed to validate end of a YAML file
>    */
> -int YamlParserContext::parseContent(YamlObject &yamlObject)
> +int YamlParserContext::parseContent(ValueNode &valueNode)
>   {
>   	/* Check start of the YAML file. */
>   	EventPtr event = nextEvent();
> @@ -174,7 +174,7 @@ int YamlParserContext::parseContent(YamlObject &yamlObject)
>   
>   	/* Parse the root object. */
>   	event = nextEvent();
> -	if (parseNextYamlObject(yamlObject, std::move(event)))
> +	if (parseNextNode(valueNode, std::move(event)))
>   		return -EINVAL;
>   
>   	/* Check end of the YAML file. */
> @@ -247,8 +247,8 @@ int YamlParserContext::parseDictionaryOrList(yaml_event_type_t endEventType,
>   }
>   
>   /**
> - * \brief Parse next YAML event and read it as a YamlObject
> - * \param[in] yamlObject The result of YamlObject
> + * \brief Parse next YAML event and read it as a ValueNode
> + * \param[in] valueNode The result of ValueNode
>    * \param[in] event The leading event of the object
>    *
>    * Parse next YAML object separately as a value, list or dictionary.
> @@ -256,26 +256,26 @@ int YamlParserContext::parseDictionaryOrList(yaml_event_type_t endEventType,
>    * \return 0 on success or a negative error code otherwise
>    * \retval -EINVAL Fail to parse the YAML file.
>    */
> -int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event)
> +int YamlParserContext::parseNextNode(ValueNode &valueNode, EventPtr event)
>   {
>   	if (!event)
>   		return -EINVAL;
>   
>   	switch (event->type) {
>   	case YAML_SCALAR_EVENT:
> -		yamlObject.set(readValue(std::move(event)));
> +		valueNode.set(readValue(std::move(event)));
>   		return 0;
>   
>   	case YAML_SEQUENCE_START_EVENT: {
> -		auto handler = [this, &yamlObject](EventPtr evt) {
> -			YamlObject *child = yamlObject.add(std::make_unique<YamlObject>());
> -			return parseNextYamlObject(*child, std::move(evt));
> +		auto handler = [this, &valueNode](EventPtr evt) {
> +			ValueNode *child = valueNode.add(std::make_unique<ValueNode>());
> +			return parseNextNode(*child, std::move(evt));
>   		};
>   		return parseDictionaryOrList(YAML_SEQUENCE_END_EVENT, handler);
>   	}
>   
>   	case YAML_MAPPING_START_EVENT: {
> -		auto handler = [this, &yamlObject](EventPtr evtKey) {
> +		auto handler = [this, &valueNode](EventPtr evtKey) {
>   			/* Parse key */
>   			if (evtKey->type != YAML_SCALAR_EVENT) {
>   				LOG(YamlParser, Error) << "Expect key at line: "
> @@ -292,9 +292,9 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
>   			if (!evtValue)
>   				return -EINVAL;
>   
> -			YamlObject *child = yamlObject.add(std::move(key),
> -							   std::make_unique<YamlObject>());
> -			return parseNextYamlObject(*child, std::move(evtValue));
> +			ValueNode *child = valueNode.add(std::move(key),
> +							 std::make_unique<ValueNode>());
> +			return parseNextNode(*child, std::move(evtValue));
>   		};
>   		int ret = parseDictionaryOrList(YAML_MAPPING_END_EVENT, handler);
>   		if (ret)
> @@ -316,7 +316,7 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
>    * \brief A helper class for parsing a YAML file
>    *
>    * The YamlParser class provides an easy interface to parse the contents of a
> - * YAML file into a tree of YamlObject instances.
> + * YAML file into a tree of ValueNode instances.
>    *
>    * Example usage:
>    *
> @@ -334,17 +334,17 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
>    *
>    * \code{.cpp}
>    *
> - * std::unique_ptr<YamlObject> root = YamlParser::parse(fh);
> + * std::unique_ptr<ValueNode> root = YamlParser::parse(fh);
>    * if (!root)
>    *   return;
>    *
>    * if (!root->isDictionary())
>    *   return;
>    *
> - * const YamlObject &name = (*root)["name"];
> + * const ValueNode &name = (*root)["name"];
>    * std::cout << name.get<std::string>("") << std::endl;
>    *
> - * const YamlObject &numbers = (*root)["numbers"];
> + * const ValueNode &numbers = (*root)["numbers"];
>    * if (!numbers.isList())
>    *   return;
>    *
> @@ -354,7 +354,7 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
>    * \endcode
>    *
>    * The YamlParser::parse() function takes an open FILE, parses its contents, and
> - * returns a pointer to a YamlObject corresponding to the root node of the YAML
> + * returns a pointer to a ValueNode corresponding to the root node of the YAML
>    * document.
>    *
>    * The parser preserves the order of items in the YAML file, for both lists and
> @@ -362,23 +362,23 @@ int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
>    */
>   
>   /**
> - * \brief Parse a YAML file as a YamlObject
> + * \brief Parse a YAML file as a ValueNode
>    * \param[in] file The YAML file to parse
>    *
>    * The YamlParser::parse() function takes a file, parses its contents, and
> - * returns a pointer to a YamlObject corresponding to the root node of the YAML
> + * returns a pointer to a ValueNode corresponding to the root node of the YAML
>    * document.
>    *
> - * \return Pointer to result YamlObject on success or nullptr otherwise
> + * \return Pointer to result ValueNode on success or nullptr otherwise
>    */
> -std::unique_ptr<YamlObject> YamlParser::parse(File &file)
> +std::unique_ptr<ValueNode> YamlParser::parse(File &file)
>   {
>   	YamlParserContext context;
>   
>   	if (context.init(file))
>   		return nullptr;
>   
> -	std::unique_ptr<YamlObject> root = std::make_unique<YamlObject>();
> +	std::unique_ptr<ValueNode> root = std::make_unique<ValueNode>();
>   
>   	if (context.parseContent(*root)) {
>   		LOG(YamlParser, Error)
> diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
> index 01e734d23059..8c5826f4885b 100644
> --- a/test/yaml-parser.cpp
> +++ b/test/yaml-parser.cpp
> @@ -94,7 +94,7 @@ protected:
>   		Dictionary,
>   	};
>   
> -	int testObjectType(const YamlObject &obj, const char *name, Type type)
> +	int testObjectType(const ValueNode &obj, const char *name, Type type)
>   	{
>   		bool isList = type == Type::List || type == Type::Size;
>   		bool isScalar = !isList && type != Type::Dictionary;
> @@ -194,7 +194,7 @@ protected:
>   		return TestPass;
>   	}
>   
> -	int testIntegerObject(const YamlObject &obj, const char *name, Type type,
> +	int testIntegerObject(const ValueNode &obj, const char *name, Type type,
>   			      int64_t value)
>   	{
>   		uint64_t unsignedValue = static_cast<uint64_t>(value);
> @@ -292,7 +292,7 @@ protected:
>   			return TestFail;
>   		}
>   
> -		std::unique_ptr<YamlObject> root = YamlParser::parse(file);
> +		std::unique_ptr<ValueNode> root = YamlParser::parse(file);
>   		if (root) {
>   			cerr << "Invalid YAML file parse successfully" << std::endl;
>   			return TestFail;
Laurent Pinchart Jan. 18, 2026, 10:48 p.m. UTC | #2
On Wed, Jan 14, 2026 at 12:28:37PM +0100, Barnabás Pőcze wrote:
> 2026. 01. 13. 1:07 keltezéssel, Laurent Pinchart írta:
> > The YamlObject class is now a generic data container to model trees of
> > values. Rename it to ValueNode and expand the class documentation.
> > 
> > While at it, drop the unneeded libcamera:: namespace prefix when using
> > the ValueNode class.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> 
> Seems ok to me. There are no "YamlObject" left in the source, so it
> must be correct, right?

That's my understanding too :-)

> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> 
> >   .../internal/converter/converter_dw100.h      |   2 +-
> >   .../libcamera/internal/global_configuration.h |  10 +-
> >   include/libcamera/internal/matrix.h           |  10 +-
> >   include/libcamera/internal/meson.build        |   2 +-
> >   .../internal/{yaml_object.h => value_node.h}  |  34 +-
> >   include/libcamera/internal/vector.h           |  10 +-
> >   include/libcamera/internal/yaml_parser.h      |   4 +-
> >   src/android/camera_hal_config.cpp             |  16 +-
> >   src/ipa/ipu3/algorithms/agc.cpp               |   4 +-
> >   src/ipa/ipu3/algorithms/agc.h                 |   2 +-
> >   src/ipa/ipu3/ipu3.cpp                         |   2 +-
> >   src/ipa/libipa/agc_mean_luminance.cpp         |  16 +-
> >   src/ipa/libipa/agc_mean_luminance.h           |  12 +-
> >   src/ipa/libipa/algorithm.cpp                  |   2 +-
> >   src/ipa/libipa/algorithm.h                    |   4 +-
> >   src/ipa/libipa/awb.cpp                        |   6 +-
> >   src/ipa/libipa/awb.h                          |   6 +-
> >   src/ipa/libipa/awb_bayes.cpp                  |   4 +-
> >   src/ipa/libipa/awb_bayes.h                    |   6 +-
> >   src/ipa/libipa/awb_grey.cpp                   |   2 +-
> >   src/ipa/libipa/awb_grey.h                     |   2 +-
> >   src/ipa/libipa/interpolator.cpp               |   2 +-
> >   src/ipa/libipa/interpolator.h                 |   4 +-
> >   src/ipa/libipa/lsc_polynomial.h               |   6 +-
> >   src/ipa/libipa/lux.cpp                        |   6 +-
> >   src/ipa/libipa/lux.h                          |   4 +-
> >   src/ipa/libipa/module.cpp                     |   2 +-
> >   src/ipa/libipa/module.h                       |   6 +-
> >   src/ipa/libipa/pwl.cpp                        |   2 +-
> >   src/ipa/mali-c55/algorithms/agc.cpp           |   2 +-
> >   src/ipa/mali-c55/algorithms/agc.h             |   2 +-
> >   src/ipa/mali-c55/algorithms/blc.cpp           |   4 +-
> >   src/ipa/mali-c55/algorithms/blc.h             |   2 +-
> >   src/ipa/mali-c55/algorithms/lsc.cpp           |   6 +-
> >   src/ipa/mali-c55/algorithms/lsc.h             |   2 +-
> >   src/ipa/mali-c55/mali-c55.cpp                 |   2 +-
> >   src/ipa/rkisp1/algorithms/agc.cpp             |  10 +-
> >   src/ipa/rkisp1/algorithms/agc.h               |   4 +-
> >   src/ipa/rkisp1/algorithms/awb.cpp             |   2 +-
> >   src/ipa/rkisp1/algorithms/awb.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/blc.cpp             |   4 +-
> >   src/ipa/rkisp1/algorithms/blc.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/ccm.cpp             |   4 +-
> >   src/ipa/rkisp1/algorithms/ccm.h               |   4 +-
> >   src/ipa/rkisp1/algorithms/cproc.cpp           |   2 +-
> >   src/ipa/rkisp1/algorithms/cproc.h             |   2 +-
> >   src/ipa/rkisp1/algorithms/dpcc.cpp            |  22 +-
> >   src/ipa/rkisp1/algorithms/dpcc.h              |   2 +-
> >   src/ipa/rkisp1/algorithms/dpf.cpp             |   8 +-
> >   src/ipa/rkisp1/algorithms/dpf.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/filter.cpp          |   2 +-
> >   src/ipa/rkisp1/algorithms/filter.h            |   2 +-
> >   src/ipa/rkisp1/algorithms/goc.cpp             |   4 +-
> >   src/ipa/rkisp1/algorithms/goc.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/gsl.cpp             |   6 +-
> >   src/ipa/rkisp1/algorithms/gsl.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/lsc.cpp             |  14 +-
> >   src/ipa/rkisp1/algorithms/lsc.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/lux.cpp             |   2 +-
> >   src/ipa/rkisp1/algorithms/lux.h               |   2 +-
> >   src/ipa/rkisp1/algorithms/wdr.cpp             |   4 +-
> >   src/ipa/rkisp1/algorithms/wdr.h               |   2 +-
> >   src/ipa/rkisp1/rkisp1.cpp                     |   2 +-
> >   src/ipa/rpi/controller/algorithm.cpp          |   2 +-
> >   src/ipa/rpi/controller/algorithm.h            |   4 +-
> >   src/ipa/rpi/controller/controller.cpp         |   4 +-
> >   src/ipa/rpi/controller/controller.h           |   4 +-
> >   src/ipa/rpi/controller/rpi/af.cpp             |  10 +-
> >   src/ipa/rpi/controller/rpi/af.h               |   8 +-
> >   src/ipa/rpi/controller/rpi/agc.cpp            |   2 +-
> >   src/ipa/rpi/controller/rpi/agc.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/agc_channel.cpp    |  24 +-
> >   src/ipa/rpi/controller/rpi/agc_channel.h      |  12 +-
> >   src/ipa/rpi/controller/rpi/alsc.cpp           |  10 +-
> >   src/ipa/rpi/controller/rpi/alsc.h             |   2 +-
> >   src/ipa/rpi/controller/rpi/awb.cpp            |  10 +-
> >   src/ipa/rpi/controller/rpi/awb.h              |   8 +-
> >   src/ipa/rpi/controller/rpi/black_level.cpp    |   2 +-
> >   src/ipa/rpi/controller/rpi/black_level.h      |   2 +-
> >   src/ipa/rpi/controller/rpi/cac.cpp            |   4 +-
> >   src/ipa/rpi/controller/rpi/cac.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/ccm.cpp            |   2 +-
> >   src/ipa/rpi/controller/rpi/ccm.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/contrast.cpp       |   2 +-
> >   src/ipa/rpi/controller/rpi/contrast.h         |   2 +-
> >   src/ipa/rpi/controller/rpi/decompand.cpp      |   2 +-
> >   src/ipa/rpi/controller/rpi/decompand.h        |   2 +-
> >   src/ipa/rpi/controller/rpi/denoise.cpp        |   4 +-
> >   src/ipa/rpi/controller/rpi/denoise.h          |   4 +-
> >   src/ipa/rpi/controller/rpi/dpc.cpp            |   2 +-
> >   src/ipa/rpi/controller/rpi/dpc.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/geq.cpp            |   2 +-
> >   src/ipa/rpi/controller/rpi/geq.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/hdr.cpp            |   4 +-
> >   src/ipa/rpi/controller/rpi/hdr.h              |   4 +-
> >   src/ipa/rpi/controller/rpi/lux.cpp            |   2 +-
> >   src/ipa/rpi/controller/rpi/lux.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/noise.cpp          |   2 +-
> >   src/ipa/rpi/controller/rpi/noise.h            |   2 +-
> >   src/ipa/rpi/controller/rpi/saturation.cpp     |   2 +-
> >   src/ipa/rpi/controller/rpi/saturation.h       |   2 +-
> >   src/ipa/rpi/controller/rpi/sdn.cpp            |   2 +-
> >   src/ipa/rpi/controller/rpi/sdn.h              |   2 +-
> >   src/ipa/rpi/controller/rpi/sharpen.cpp        |   2 +-
> >   src/ipa/rpi/controller/rpi/sharpen.h          |   2 +-
> >   src/ipa/rpi/controller/rpi/tonemap.cpp        |   2 +-
> >   src/ipa/rpi/controller/rpi/tonemap.h          |   2 +-
> >   src/ipa/simple/algorithms/blc.cpp             |   2 +-
> >   src/ipa/simple/algorithms/blc.h               |   2 +-
> >   src/ipa/simple/algorithms/ccm.cpp             |   2 +-
> >   src/ipa/simple/algorithms/ccm.h               |   2 +-
> >   src/ipa/simple/algorithms/lut.cpp             |   2 +-
> >   src/ipa/simple/algorithms/lut.h               |   2 +-
> >   src/ipa/simple/soft_simple.cpp                |   2 +-
> >   src/libcamera/converter/converter_dw100.cpp   |   2 +-
> >   src/libcamera/geometry.cpp                    |   4 +-
> >   src/libcamera/global_configuration.cpp        |   8 +-
> >   src/libcamera/matrix.cpp                      |   2 +-
> >   src/libcamera/meson.build                     |   2 +-
> >   src/libcamera/pipeline/rkisp1/rkisp1.cpp      |   2 +-
> >   .../pipeline/rpi/common/pipeline_base.cpp     |   4 +-
> >   .../pipeline/rpi/common/pipeline_base.h       |   4 +-
> >   src/libcamera/pipeline/rpi/pisp/pisp.cpp      |   6 +-
> >   src/libcamera/pipeline/rpi/vc4/vc4.cpp        |   6 +-
> >   src/libcamera/pipeline/virtual/README.md      |   2 +-
> >   .../pipeline/virtual/config_parser.cpp        |  18 +-
> >   .../pipeline/virtual/config_parser.h          |  12 +-
> >   src/libcamera/pipeline/virtual/virtual.cpp    |   2 +-
> >   src/libcamera/value_node.cpp                  | 484 ++++++++++++++++++
> >   src/libcamera/vector.cpp                      |   2 +-
> >   src/libcamera/yaml_object.cpp                 | 480 -----------------
> >   src/libcamera/yaml_parser.cpp                 |  54 +-
> >   test/yaml-parser.cpp                          |   6 +-
> >   133 files changed, 807 insertions(+), 803 deletions(-)
> >   rename include/libcamera/internal/{yaml_object.h => value_node.h} (81%)
> >   create mode 100644 src/libcamera/value_node.cpp
> >   delete mode 100644 src/libcamera/yaml_object.cpp

[snip]

Patch
diff mbox series

diff --git a/include/libcamera/internal/converter/converter_dw100.h b/include/libcamera/internal/converter/converter_dw100.h
index d70c8fe76185..054750ef0866 100644
--- a/include/libcamera/internal/converter/converter_dw100.h
+++ b/include/libcamera/internal/converter/converter_dw100.h
@@ -31,7 +31,7 @@  public:
 
 	static std::unique_ptr<ConverterDW100Module> createModule(DeviceEnumerator *enumerator);
 
-	int init(const YamlObject &params);
+	int init(const ValueNode &params);
 
 	int configure(const StreamConfiguration &inputCfg,
 		      const std::vector<std::reference_wrapper<StreamConfiguration>>
diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
index 16c6a21f2a8a..7ae923977aa6 100644
--- a/include/libcamera/internal/global_configuration.h
+++ b/include/libcamera/internal/global_configuration.h
@@ -14,14 +14,14 @@ 
 
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
 class GlobalConfiguration
 {
 public:
-	using Configuration = const YamlObject &;
+	using Configuration = const ValueNode &;
 
 	GlobalConfiguration();
 
@@ -32,7 +32,7 @@  public:
 	std::optional<T> option(
 		const std::initializer_list<std::string_view> confPath) const
 	{
-		const YamlObject *c = &configuration();
+		const ValueNode *c = &configuration();
 		for (auto part : confPath) {
 			c = &(*c)[part];
 			if (!*c)
@@ -55,8 +55,8 @@  private:
 	bool loadFile(const std::filesystem::path &fileName);
 	void load();
 
-	std::unique_ptr<YamlObject> yamlConfiguration_ =
-		std::make_unique<YamlObject>();
+	std::unique_ptr<ValueNode> yamlConfiguration_ =
+		std::make_unique<ValueNode>();
 };
 
 } /* namespace libcamera */
diff --git a/include/libcamera/internal/matrix.h b/include/libcamera/internal/matrix.h
index 11fccd27547e..0f72263f2536 100644
--- a/include/libcamera/internal/matrix.h
+++ b/include/libcamera/internal/matrix.h
@@ -14,7 +14,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/span.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
@@ -188,7 +188,7 @@  constexpr Matrix<T, Rows, Cols> operator+(const Matrix<T, Rows, Cols> &m1, const
 }
 
 #ifndef __DOXYGEN__
-bool matrixValidateYaml(const YamlObject &obj, unsigned int size);
+bool matrixValidateYaml(const ValueNode &obj, unsigned int size);
 #endif /* __DOXYGEN__ */
 
 #ifndef __DOXYGEN__
@@ -200,8 +200,8 @@  std::ostream &operator<<(std::ostream &out, const Matrix<T, Rows, Cols> &m)
 }
 
 template<typename T, unsigned int Rows, unsigned int Cols>
-struct YamlObject::Accessor<Matrix<T, Rows, Cols>> {
-	std::optional<Matrix<T, Rows, Cols>> get(const YamlObject &obj) const
+struct ValueNode::Accessor<Matrix<T, Rows, Cols>> {
+	std::optional<Matrix<T, Rows, Cols>> get(const ValueNode &obj) const
 	{
 		if (!matrixValidateYaml(obj, Rows * Cols))
 			return std::nullopt;
@@ -210,7 +210,7 @@  struct YamlObject::Accessor<Matrix<T, Rows, Cols>> {
 		T *data = &matrix[0][0];
 
 		unsigned int i = 0;
-		for (const YamlObject &entry : obj.asList()) {
+		for (const ValueNode &entry : obj.asList()) {
 			const auto value = entry.get<T>();
 			if (!value)
 				return std::nullopt;
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index c24c9f062f29..76ab43115768 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -49,8 +49,8 @@  libcamera_internal_headers = files([
     'v4l2_request.h',
     'v4l2_subdevice.h',
     'v4l2_videodevice.h',
+    'value_node.h',
     'vector.h',
-    'yaml_object.h',
     'yaml_parser.h',
 ])
 
diff --git a/include/libcamera/internal/yaml_object.h b/include/libcamera/internal/value_node.h
similarity index 81%
rename from include/libcamera/internal/yaml_object.h
rename to include/libcamera/internal/value_node.h
index 1771f8821e2e..a336c1d08e1c 100644
--- a/include/libcamera/internal/yaml_object.h
+++ b/include/libcamera/internal/value_node.h
@@ -3,7 +3,7 @@ 
  * Copyright (C) 2022, Google Inc.
  * Copyright (C) 2025, Ideas on Board
  *
- * libcamera YAML object
+ * Data structure to manage tree of values
  */
 
 #pragma once
@@ -22,16 +22,16 @@ 
 
 namespace libcamera {
 
-class YamlObject
+class ValueNode
 {
 private:
 	struct Value {
-		Value(std::string &&k, std::unique_ptr<YamlObject> &&v)
+		Value(std::string &&k, std::unique_ptr<ValueNode> &&v)
 			: key(std::move(k)), value(std::move(v))
 		{
 		}
 		std::string key;
-		std::unique_ptr<YamlObject> value;
+		std::unique_ptr<ValueNode> value;
 	};
 
 	using ValueContainer = std::vector<Value>;
@@ -103,8 +103,8 @@  public:
 	class ListIterator : public Iterator<ListIterator>
 	{
 	public:
-		using value_type = const YamlObject &;
-		using pointer = const YamlObject *;
+		using value_type = const ValueNode &;
+		using pointer = const ValueNode *;
 		using reference = value_type;
 
 		value_type operator*() const
@@ -121,7 +121,7 @@  public:
 	class DictIterator : public Iterator<DictIterator>
 	{
 	public:
-		using value_type = std::pair<const std::string &, const YamlObject &>;
+		using value_type = std::pair<const std::string &, const ValueNode &>;
 		using pointer = value_type *;
 		using reference = value_type &;
 
@@ -142,8 +142,8 @@  public:
 	};
 #endif /* __DOXYGEN__ */
 
-	YamlObject();
-	~YamlObject();
+	ValueNode();
+	~ValueNode();
 
 	bool isValue() const
 	{
@@ -189,16 +189,16 @@  public:
 	DictAdapter asDict() const { return DictAdapter{ list_ }; }
 	ListAdapter asList() const { return ListAdapter{ list_ }; }
 
-	const YamlObject &operator[](std::size_t index) const;
+	const ValueNode &operator[](std::size_t index) const;
 
 	bool contains(std::string_view key) const;
-	const YamlObject &operator[](std::string_view key) const;
+	const ValueNode &operator[](std::string_view key) const;
 
-	YamlObject *add(std::unique_ptr<YamlObject> child);
-	YamlObject *add(std::string key, std::unique_ptr<YamlObject> child);
+	ValueNode *add(std::unique_ptr<ValueNode> child);
+	ValueNode *add(std::string key, std::unique_ptr<ValueNode> child);
 
 private:
-	LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject)
+	LIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)
 
 	template<typename T>
 	friend struct Accessor;
@@ -212,15 +212,15 @@  private:
 
 	template<typename T, typename Enable = void>
 	struct Accessor {
-		std::optional<T> get(const YamlObject &obj) const;
-		void set(YamlObject &obj, T value);
+		std::optional<T> get(const ValueNode &obj) const;
+		void set(ValueNode &obj, T value);
 	};
 
 	Type type_;
 
 	std::string value_;
 	ValueContainer list_;
-	std::map<std::string, YamlObject *, std::less<>> dictionary_;
+	std::map<std::string, ValueNode *, std::less<>> dictionary_;
 };
 
 } /* namespace libcamera */
diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
index af24485f3bb1..ed7490e16e9e 100644
--- a/include/libcamera/internal/vector.h
+++ b/include/libcamera/internal/vector.h
@@ -19,7 +19,7 @@ 
 #include <libcamera/base/span.h>
 
 #include "libcamera/internal/matrix.h"
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
@@ -329,7 +329,7 @@  bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
 }
 
 #ifndef __DOXYGEN__
-bool vectorValidateYaml(const YamlObject &obj, unsigned int size);
+bool vectorValidateYaml(const ValueNode &obj, unsigned int size);
 #endif /* __DOXYGEN__ */
 
 #ifndef __DOXYGEN__
@@ -347,8 +347,8 @@  std::ostream &operator<<(std::ostream &out, const Vector<T, Rows> &v)
 }
 
 template<typename T, unsigned int Rows>
-struct YamlObject::Accessor<Vector<T, Rows>> {
-	std::optional<Vector<T, Rows>> get(const YamlObject &obj) const
+struct ValueNode::Accessor<Vector<T, Rows>> {
+	std::optional<Vector<T, Rows>> get(const ValueNode &obj) const
 	{
 		if (!vectorValidateYaml(obj, Rows))
 			return std::nullopt;
@@ -356,7 +356,7 @@  struct YamlObject::Accessor<Vector<T, Rows>> {
 		Vector<T, Rows> vector;
 
 		unsigned int i = 0;
-		for (const YamlObject &entry : obj.asList()) {
+		for (const ValueNode &entry : obj.asList()) {
 			const auto value = entry.get<T>();
 			if (!value)
 				return std::nullopt;
diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
index e503e83a80da..9d4ba0a1d0e1 100644
--- a/include/libcamera/internal/yaml_parser.h
+++ b/include/libcamera/internal/yaml_parser.h
@@ -9,7 +9,7 @@ 
 
 #include <memory>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
@@ -18,7 +18,7 @@  class File;
 class YamlParser final
 {
 public:
-	static std::unique_ptr<YamlObject> parse(File &file);
+	static std::unique_ptr<ValueNode> parse(File &file);
 };
 
 } /* namespace libcamera */
diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
index 7ef451ef8ab9..4a9c0577a0be 100644
--- a/src/android/camera_hal_config.cpp
+++ b/src/android/camera_hal_config.cpp
@@ -30,9 +30,9 @@  public:
 	int parseConfigFile(File &file, std::map<std::string, CameraConfigData> *cameras);
 
 private:
-	int parseCameraConfigData(const std::string &cameraId, const YamlObject &);
-	int parseLocation(const YamlObject &, CameraConfigData &cameraConfigData);
-	int parseRotation(const YamlObject &, CameraConfigData &cameraConfigData);
+	int parseCameraConfigData(const std::string &cameraId, const ValueNode &);
+	int parseLocation(const ValueNode &, CameraConfigData &cameraConfigData);
+	int parseRotation(const ValueNode &, CameraConfigData &cameraConfigData);
 
 	std::map<std::string, CameraConfigData> *cameras_;
 };
@@ -65,7 +65,7 @@  int CameraHalConfig::Private::parseConfigFile(File &file,
 
 	cameras_ = cameras;
 
-	std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> root = YamlParser::parse(file);
 	if (!root)
 		return -EINVAL;
 
@@ -76,7 +76,7 @@  int CameraHalConfig::Private::parseConfigFile(File &file,
 	if (!root->contains("cameras"))
 		return -EINVAL;
 
-	const YamlObject &yamlObjectCameras = (*root)["cameras"];
+	const ValueNode &yamlObjectCameras = (*root)["cameras"];
 
 	if (!yamlObjectCameras.isDictionary())
 		return -EINVAL;
@@ -90,7 +90,7 @@  int CameraHalConfig::Private::parseConfigFile(File &file,
 }
 
 int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId,
-						    const YamlObject &cameraObject)
+						    const ValueNode &cameraObject)
 
 {
 	if (!cameraObject.isDictionary())
@@ -109,7 +109,7 @@  int CameraHalConfig::Private::parseCameraConfigData(const std::string &cameraId,
 	return 0;
 }
 
-int CameraHalConfig::Private::parseLocation(const YamlObject &cameraObject,
+int CameraHalConfig::Private::parseLocation(const ValueNode &cameraObject,
 					    CameraConfigData &cameraConfigData)
 {
 	if (!cameraObject.contains("location"))
@@ -127,7 +127,7 @@  int CameraHalConfig::Private::parseLocation(const YamlObject &cameraObject,
 	return 0;
 }
 
-int CameraHalConfig::Private::parseRotation(const YamlObject &cameraObject,
+int CameraHalConfig::Private::parseRotation(const ValueNode &cameraObject,
 					    CameraConfigData &cameraConfigData)
 {
 	if (!cameraObject.contains("rotation"))
diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index b0d89541da85..d6a7036c6504 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -65,14 +65,14 @@  Agc::Agc()
 /**
  * \brief Initialise the AGC algorithm from tuning files
  * \param[in] context The shared IPA context
- * \param[in] tuningData The YamlObject containing Agc tuning data
+ * \param[in] tuningData The ValueNode containing Agc tuning data
  *
  * This function calls the base class' tuningData parsers to discover which
  * control values are supported.
  *
  * \return 0 on success or errors from the base class
  */
-int Agc::init(IPAContext &context, const YamlObject &tuningData)
+int Agc::init(IPAContext &context, const ValueNode &tuningData)
 {
 	int ret;
 
diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h
index 890c271b4462..d274a2350485 100644
--- a/src/ipa/ipu3/algorithms/agc.h
+++ b/src/ipa/ipu3/algorithms/agc.h
@@ -30,7 +30,7 @@  public:
 	Agc();
 	~Agc() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void process(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index b926f579a9a3..09f6aeb9400d 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -324,7 +324,7 @@  int IPAIPU3::init(const IPASettings &settings,
 		return ret;
 	}
 
-	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
 	if (!data)
 		return -EINVAL;
 
diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 72988096d384..1d385551adc6 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -159,7 +159,7 @@  AgcMeanLuminance::AgcMeanLuminance()
 
 AgcMeanLuminance::~AgcMeanLuminance() = default;
 
-int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
+int AgcMeanLuminance::parseRelativeLuminanceTarget(const ValueNode &tuningData)
 {
 	auto &target = tuningData["relativeLuminanceTarget"];
 	if (!target) {
@@ -178,7 +178,7 @@  int AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData)
 	return 0;
 }
 
-int AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id)
+int AgcMeanLuminance::parseConstraint(const ValueNode &modeDict, int32_t id)
 {
 	for (const auto &[boundName, content] : modeDict.asDict()) {
 		if (boundName != "upper" && boundName != "lower") {
@@ -212,11 +212,11 @@  int AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id)
 	return 0;
 }
 
-int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData)
+int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData)
 {
 	std::vector<ControlValue> availableConstraintModes;
 
-	const YamlObject &yamlConstraintModes = tuningData[controls::AeConstraintMode.name()];
+	const ValueNode &yamlConstraintModes = tuningData[controls::AeConstraintMode.name()];
 	if (yamlConstraintModes.isDictionary()) {
 		for (const auto &[modeName, modeDict] : yamlConstraintModes.asDict()) {
 			if (AeConstraintModeNameValueMap.find(modeName) ==
@@ -267,11 +267,11 @@  int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData)
 	return 0;
 }
 
-int AgcMeanLuminance::parseExposureModes(const YamlObject &tuningData)
+int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData)
 {
 	std::vector<ControlValue> availableExposureModes;
 
-	const YamlObject &yamlExposureModes = tuningData[controls::AeExposureMode.name()];
+	const ValueNode &yamlExposureModes = tuningData[controls::AeExposureMode.name()];
 	if (yamlExposureModes.isDictionary()) {
 		for (const auto &[modeName, modeValues] : yamlExposureModes.asDict()) {
 			if (AeExposureModeNameValueMap.find(modeName) ==
@@ -361,7 +361,7 @@  void AgcMeanLuminance::configure(utils::Duration lineDuration,
 
 /**
  * \brief Parse tuning data for AeConstraintMode and AeExposureMode controls
- * \param[in] tuningData the YamlObject representing the tuning data
+ * \param[in] tuningData the ValueNode representing the tuning data
  *
  * This function parses tuning data to build the list of allowed values for the
  * AeConstraintMode and AeExposureMode controls. Those tuning data must provide
@@ -414,7 +414,7 @@  void AgcMeanLuminance::configure(utils::Duration lineDuration,
  *
  * \return 0 on success or a negative error code
  */
-int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
+int AgcMeanLuminance::parseTuningData(const ValueNode &tuningData)
 {
 	int ret;
 
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index dfe1ccbe948b..27e92b6fce7a 100644
--- a/src/ipa/libipa/agc_mean_luminance.h
+++ b/src/ipa/libipa/agc_mean_luminance.h
@@ -16,7 +16,7 @@ 
 
 #include <libcamera/controls.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "exposure_mode_helper.h"
 #include "histogram.h"
@@ -44,7 +44,7 @@  public:
 	};
 
 	void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper);
-	int parseTuningData(const YamlObject &tuningData);
+	int parseTuningData(const ValueNode &tuningData);
 
 	void setExposureCompensation(double gain)
 	{
@@ -88,10 +88,10 @@  public:
 private:
 	virtual double estimateLuminance(const double gain) const = 0;
 
-	int parseRelativeLuminanceTarget(const YamlObject &tuningData);
-	int parseConstraint(const YamlObject &modeDict, int32_t id);
-	int parseConstraintModes(const YamlObject &tuningData);
-	int parseExposureModes(const YamlObject &tuningData);
+	int parseRelativeLuminanceTarget(const ValueNode &tuningData);
+	int parseConstraint(const ValueNode &modeDict, int32_t id);
+	int parseConstraintModes(const ValueNode &tuningData);
+	int parseExposureModes(const ValueNode &tuningData);
 	double estimateInitialGain() const;
 	double constraintClampGain(uint32_t constraintModeIndex,
 				   const Histogram &hist,
diff --git a/src/ipa/libipa/algorithm.cpp b/src/ipa/libipa/algorithm.cpp
index 201efdfdba25..757ce3519652 100644
--- a/src/ipa/libipa/algorithm.cpp
+++ b/src/ipa/libipa/algorithm.cpp
@@ -44,7 +44,7 @@  namespace ipa {
  * \param[in] tuningData The tuning data for the algorithm
  *
  * This function is called once, when the IPA module is initialized, to
- * initialize the algorithm. The \a tuningData YamlObject contains the tuning
+ * initialize the algorithm. The \a tuningData ValueNode contains the tuning
  * data for algorithm.
  *
  * \return 0 if successful, an error code otherwise
diff --git a/src/ipa/libipa/algorithm.h b/src/ipa/libipa/algorithm.h
index 9a19dbd61b31..4ddb16ef3920 100644
--- a/src/ipa/libipa/algorithm.h
+++ b/src/ipa/libipa/algorithm.h
@@ -14,7 +14,7 @@ 
 
 namespace libcamera {
 
-class YamlObject;
+class ValueNode;
 
 namespace ipa {
 
@@ -27,7 +27,7 @@  public:
 	virtual ~Algorithm() {}
 
 	virtual int init([[maybe_unused]] typename Module::Context &context,
-			 [[maybe_unused]] const YamlObject &tuningData)
+			 [[maybe_unused]] const ValueNode &tuningData)
 	{
 		return 0;
 	}
diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
index 214bac8b56a6..af966f1e007c 100644
--- a/src/ipa/libipa/awb.cpp
+++ b/src/ipa/libipa/awb.cpp
@@ -132,7 +132,7 @@  namespace ipa {
 
 /**
  * \brief Parse the mode configurations from the tuning data
- * \param[in] tuningData the YamlObject representing the tuning data
+ * \param[in] tuningData the ValueNode representing the tuning data
  * \param[in] def The default value for the AwbMode control
  *
  * Utility function to parse the tuning data for an AwbMode entry and read all
@@ -162,12 +162,12 @@  namespace ipa {
  * \sa controls::AwbModeEnum
  * \return Zero on success, negative error code otherwise
  */
-int AwbAlgorithm::parseModeConfigs(const YamlObject &tuningData,
+int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,
 				   const ControlValue &def)
 {
 	std::vector<ControlValue> availableModes;
 
-	const YamlObject &yamlModes = tuningData[controls::AwbMode.name()];
+	const ValueNode &yamlModes = tuningData[controls::AwbMode.name()];
 	if (!yamlModes.isDictionary()) {
 		LOG(Awb, Error)
 			<< "AwbModes must be a dictionary.";
diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
index 3f25d13feaa8..09c00e47d604 100644
--- a/src/ipa/libipa/awb.h
+++ b/src/ipa/libipa/awb.h
@@ -13,8 +13,8 @@ 
 #include <libcamera/control_ids.h>
 #include <libcamera/controls.h>
 
+#include "libcamera/internal/value_node.h"
 #include "libcamera/internal/vector.h"
-#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
@@ -38,7 +38,7 @@  class AwbAlgorithm
 public:
 	virtual ~AwbAlgorithm() = default;
 
-	virtual int init(const YamlObject &tuningData) = 0;
+	virtual int init(const ValueNode &tuningData) = 0;
 	virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
 	virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;
 
@@ -50,7 +50,7 @@  public:
 	virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
 
 protected:
-	int parseModeConfigs(const YamlObject &tuningData,
+	int parseModeConfigs(const ValueNode &tuningData,
 			     const ControlValue &def = {});
 
 	struct ModeConfig {
diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
index 595bd0705732..a1412c8bd2b5 100644
--- a/src/ipa/libipa/awb_bayes.cpp
+++ b/src/ipa/libipa/awb_bayes.cpp
@@ -143,7 +143,7 @@  void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl
  * \brief The currently selected mode
  */
 
-int AwbBayes::init(const YamlObject &tuningData)
+int AwbBayes::init(const ValueNode &tuningData)
 {
 	int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
 	if (ret) {
@@ -188,7 +188,7 @@  int AwbBayes::init(const YamlObject &tuningData)
 	return 0;
 }
 
-int AwbBayes::readPriors(const YamlObject &tuningData)
+int AwbBayes::readPriors(const ValueNode &tuningData)
 {
 	const auto &priorsList = tuningData["priors"];
 	std::map<uint32_t, Pwl> priors;
diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
index 79334ad3e7a3..1e3373676bc0 100644
--- a/src/ipa/libipa/awb_bayes.h
+++ b/src/ipa/libipa/awb_bayes.h
@@ -9,8 +9,8 @@ 
 
 #include <libcamera/controls.h>
 
+#include "libcamera/internal/value_node.h"
 #include "libcamera/internal/vector.h"
-#include "libcamera/internal/yaml_object.h"
 
 #include "awb.h"
 #include "interpolator.h"
@@ -25,13 +25,13 @@  class AwbBayes : public AwbAlgorithm
 public:
 	AwbBayes() = default;
 
-	int init(const YamlObject &tuningData) override;
+	int init(const ValueNode &tuningData) override;
 	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
 	std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;
 	void handleControls(const ControlList &controls) override;
 
 private:
-	int readPriors(const YamlObject &tuningData);
+	int readPriors(const ValueNode &tuningData);
 
 	void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
 			const AwbStats &stats) const;
diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
index d252edb2b6c6..b14b096810ae 100644
--- a/src/ipa/libipa/awb_grey.cpp
+++ b/src/ipa/libipa/awb_grey.cpp
@@ -41,7 +41,7 @@  namespace ipa {
  *
  * \return 0 on success, a negative error code otherwise
  */
-int AwbGrey::init(const YamlObject &tuningData)
+int AwbGrey::init(const ValueNode &tuningData)
 {
 	Interpolator<Vector<double, 2>> gains;
 	int ret = gains.readYaml(tuningData["colourGains"], "ct", "gains");
diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
index f82a368d11cc..154a2af97f15 100644
--- a/src/ipa/libipa/awb_grey.h
+++ b/src/ipa/libipa/awb_grey.h
@@ -23,7 +23,7 @@  class AwbGrey : public AwbAlgorithm
 public:
 	AwbGrey() = default;
 
-	int init(const YamlObject &tuningData) override;
+	int init(const ValueNode &tuningData) override;
 	AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
 	std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;
 
diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp
index 9355802f796a..6bd83849262e 100644
--- a/src/ipa/libipa/interpolator.cpp
+++ b/src/ipa/libipa/interpolator.cpp
@@ -53,7 +53,7 @@  namespace ipa {
  */
 
 /**
- * \fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml,
+ * \fn int Interpolator<T>::readYaml(const ValueNode &yaml,
 		                     const std::string &key_name,
 		                     const std::string &value_name)
  * \brief Initialize an Interpolator instance from yaml
diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h
index 08003a06dbba..c67091e21ca6 100644
--- a/src/ipa/libipa/interpolator.h
+++ b/src/ipa/libipa/interpolator.h
@@ -15,7 +15,7 @@ 
 
 #include <libcamera/base/log.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
@@ -39,7 +39,7 @@  public:
 
 	~Interpolator() = default;
 
-	int readYaml(const libcamera::YamlObject &yaml,
+	int readYaml(const ValueNode &yaml,
 		     const std::string &key_name,
 		     const std::string &value_name)
 	{
diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
index 19da46860849..d7d9ae42e360 100644
--- a/src/ipa/libipa/lsc_polynomial.h
+++ b/src/ipa/libipa/lsc_polynomial.h
@@ -16,7 +16,7 @@ 
 
 #include <libcamera/geometry.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
@@ -83,8 +83,8 @@  private:
 #ifndef __DOXYGEN__
 
 template<>
-struct YamlObject::Accessor<ipa::LscPolynomial> {
-	std::optional<ipa::LscPolynomial> get(const YamlObject &obj) const
+struct ValueNode::Accessor<ipa::LscPolynomial> {
+	std::optional<ipa::LscPolynomial> get(const ValueNode &obj) const
 	{
 		std::optional<double> cx = obj["cx"].get<double>();
 		std::optional<double> cy = obj["cy"].get<double>();
diff --git a/src/ipa/libipa/lux.cpp b/src/ipa/libipa/lux.cpp
index d79b116a3339..46cc63a115b5 100644
--- a/src/ipa/libipa/lux.cpp
+++ b/src/ipa/libipa/lux.cpp
@@ -12,7 +12,7 @@ 
 
 #include <libcamera/base/log.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "histogram.h"
 
@@ -78,7 +78,7 @@  Lux::Lux()
 
 /**
  * \brief Parse tuning data
- * \param[in] tuningData The YamlObject representing the tuning data
+ * \param[in] tuningData The ValueNode representing the tuning data
  *
  * This function parses yaml tuning data for the common Lux module. It requires
  * reference exposure time, analogue gain, digital gain, and lux values.
@@ -95,7 +95,7 @@  Lux::Lux()
  *
  * \return 0 on success or a negative error code
  */
-int Lux::parseTuningData(const YamlObject &tuningData)
+int Lux::parseTuningData(const ValueNode &tuningData)
 {
 	auto value = tuningData["referenceExposureTime"].get<double>();
 	if (!value) {
diff --git a/src/ipa/libipa/lux.h b/src/ipa/libipa/lux.h
index d95bcdafcfcd..b6837ab729de 100644
--- a/src/ipa/libipa/lux.h
+++ b/src/ipa/libipa/lux.h
@@ -12,7 +12,7 @@ 
 
 namespace libcamera {
 
-class YamlObject;
+class ValueNode;
 
 namespace ipa {
 
@@ -23,7 +23,7 @@  class Lux
 public:
 	Lux();
 
-	int parseTuningData(const YamlObject &tuningData);
+	int parseTuningData(const ValueNode &tuningData);
 	double estimateLux(utils::Duration exposureTime,
 			   double aGain, double dGain,
 			   const Histogram &yHist) const;
diff --git a/src/ipa/libipa/module.cpp b/src/ipa/libipa/module.cpp
index a95dca696adf..76e10e250b73 100644
--- a/src/ipa/libipa/module.cpp
+++ b/src/ipa/libipa/module.cpp
@@ -87,7 +87,7 @@  namespace ipa {
  * \fn Module::createAlgorithms()
  * \brief Create algorithms from YAML configuration data
  * \param[in] context The IPA context
- * \param[in] algorithms Algorithms configuration data as a parsed YamlObject
+ * \param[in] algorithms Algorithms configuration data as a parsed ValueNode
  *
  * This function iterates over the list of \a algorithms parsed from the YAML
  * configuration file, and instantiates and initializes the corresponding
diff --git a/src/ipa/libipa/module.h b/src/ipa/libipa/module.h
index 8e6c658a6b63..3e2408ca30b6 100644
--- a/src/ipa/libipa/module.h
+++ b/src/ipa/libipa/module.h
@@ -15,7 +15,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "algorithm.h"
 
@@ -43,7 +43,7 @@  public:
 		return algorithms_;
 	}
 
-	int createAlgorithms(Context &context, const YamlObject &algorithms)
+	int createAlgorithms(Context &context, const ValueNode &algorithms)
 	{
 		const auto &list = algorithms.asList();
 
@@ -71,7 +71,7 @@  public:
 	}
 
 private:
-	int createAlgorithm(Context &context, const YamlObject &data)
+	int createAlgorithm(Context &context, const ValueNode &data)
 	{
 		const auto &[name, algoData] = *data.asDict().begin();
 
diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp
index f38573e69c13..9fddd5bf70e2 100644
--- a/src/ipa/libipa/pwl.cpp
+++ b/src/ipa/libipa/pwl.cpp
@@ -435,7 +435,7 @@  std::string Pwl::toString() const
  */
 template<>
 std::optional<ipa::Pwl>
-YamlObject::Accessor<ipa::Pwl>::get(const YamlObject &obj) const
+ValueNode::Accessor<ipa::Pwl>::get(const ValueNode &obj) const
 {
 	/* Treat a single value as single point PWL. */
 	if (obj.isValue()) {
diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp
index 014fd12452ac..e4ac735ccabc 100644
--- a/src/ipa/mali-c55/algorithms/agc.cpp
+++ b/src/ipa/mali-c55/algorithms/agc.cpp
@@ -131,7 +131,7 @@  Agc::Agc()
 {
 }
 
-int Agc::init(IPAContext &context, const YamlObject &tuningData)
+int Agc::init(IPAContext &context, const ValueNode &tuningData)
 {
 	int ret = parseTuningData(tuningData);
 	if (ret)
diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h
index 9684fff664bc..ee913de2b2e2 100644
--- a/src/ipa/mali-c55/algorithms/agc.h
+++ b/src/ipa/mali-c55/algorithms/agc.h
@@ -49,7 +49,7 @@  public:
 	Agc();
 	~Agc() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void queueRequest(IPAContext &context, const uint32_t frame,
diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp
index 3bdf19141e1d..591a5eaf2dc8 100644
--- a/src/ipa/mali-c55/algorithms/blc.cpp
+++ b/src/ipa/mali-c55/algorithms/blc.cpp
@@ -10,7 +10,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/control_ids.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 /**
  * \file blc.h
@@ -36,7 +36,7 @@  BlackLevelCorrection::BlackLevelCorrection()
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
-			       const YamlObject &tuningData)
+			       const ValueNode &tuningData)
 {
 	offset00 = tuningData["offset00"].get<uint32_t>(0);
 	offset01 = tuningData["offset01"].get<uint32_t>(0);
diff --git a/src/ipa/mali-c55/algorithms/blc.h b/src/ipa/mali-c55/algorithms/blc.h
index fc5a7ea310cb..bce343e20c55 100644
--- a/src/ipa/mali-c55/algorithms/blc.h
+++ b/src/ipa/mali-c55/algorithms/blc.h
@@ -18,7 +18,7 @@  public:
 	BlackLevelCorrection();
 	~BlackLevelCorrection() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void prepare(IPAContext &context, const uint32_t frame,
diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
index e32f5a83485e..fe230fdff418 100644
--- a/src/ipa/mali-c55/algorithms/lsc.cpp
+++ b/src/ipa/mali-c55/algorithms/lsc.cpp
@@ -7,7 +7,7 @@ 
 
 #include "lsc.h"
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 namespace libcamera {
 
@@ -15,7 +15,7 @@  namespace ipa::mali_c55::algorithms {
 
 LOG_DEFINE_CATEGORY(MaliC55Lsc)
 
-int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
 {
 	if (!tuningData.contains("meshScale")) {
 		LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
@@ -24,7 +24,7 @@  int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
 
 	meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
 
-	const YamlObject &yamlSets = tuningData["sets"];
+	const ValueNode &yamlSets = tuningData["sets"];
 	if (!yamlSets.isList()) {
 		LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
 		return -EINVAL;
diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
index e7092bc74a0b..3d35fd72bfa8 100644
--- a/src/ipa/mali-c55/algorithms/lsc.h
+++ b/src/ipa/mali-c55/algorithms/lsc.h
@@ -20,7 +20,7 @@  public:
 	Lsc() = default;
 	~Lsc() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     MaliC55Params *params) override;
diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
index 1d2a4f75cf8c..e4637548ac4d 100644
--- a/src/ipa/mali-c55/mali-c55.cpp
+++ b/src/ipa/mali-c55/mali-c55.cpp
@@ -118,7 +118,7 @@  int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
 		return ret;
 	}
 
-	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
 	if (!data)
 		return -EINVAL;
 
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index ef16a3775056..523930488a3b 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -19,7 +19,7 @@ 
 #include <libcamera/control_ids.h>
 #include <libcamera/ipa/core_ipa_interface.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "libipa/histogram.h"
 
@@ -40,7 +40,7 @@  namespace ipa::rkisp1::algorithms {
 
 LOG_DEFINE_CATEGORY(RkISP1Agc)
 
-int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData)
+int Agc::parseMeteringModes(IPAContext &context, const ValueNode &tuningData)
 {
 	if (!tuningData.isDictionary())
 		LOG(RkISP1Agc, Warning)
@@ -127,14 +127,14 @@  Agc::Agc()
 /**
  * \brief Initialise the AGC algorithm from tuning files
  * \param[in] context The shared IPA context
- * \param[in] tuningData The YamlObject containing Agc tuning data
+ * \param[in] tuningData The ValueNode containing Agc tuning data
  *
  * This function calls the base class' tuningData parsers to discover which
  * control values are supported.
  *
  * \return 0 on success or errors from the base class
  */
-int Agc::init(IPAContext &context, const YamlObject &tuningData)
+int Agc::init(IPAContext &context, const ValueNode &tuningData)
 {
 	int ret;
 
@@ -142,7 +142,7 @@  int Agc::init(IPAContext &context, const YamlObject &tuningData)
 	if (ret)
 		return ret;
 
-	const YamlObject &yamlMeteringModes = tuningData["AeMeteringMode"];
+	const ValueNode &yamlMeteringModes = tuningData["AeMeteringMode"];
 	ret = parseMeteringModes(context, yamlMeteringModes);
 	if (ret)
 		return ret;
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index 7867eed9c4e3..dec79f2f399c 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -28,7 +28,7 @@  public:
 	Agc();
 	~Agc() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
 	void queueRequest(IPAContext &context,
 			  const uint32_t frame,
@@ -43,7 +43,7 @@  public:
 		     ControlList &metadata) override;
 
 private:
-	int parseMeteringModes(IPAContext &context, const YamlObject &tuningData);
+	int parseMeteringModes(IPAContext &context, const ValueNode &tuningData);
 	uint8_t computeHistogramPredivider(const Size &size,
 					   enum rkisp1_cif_isp_histogram_mode mode);
 
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index e8da7974a1d6..1d248beba61f 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -84,7 +84,7 @@  Awb::Awb()
 /**
  * \copydoc libcamera::ipa::Algorithm::init
  */
-int Awb::init(IPAContext &context, const YamlObject &tuningData)
+int Awb::init(IPAContext &context, const ValueNode &tuningData)
 {
 	auto &cmap = context.ctrlMap;
 	cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,
diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
index 02651cc732bf..60d9ef111495 100644
--- a/src/ipa/rkisp1/algorithms/awb.h
+++ b/src/ipa/rkisp1/algorithms/awb.h
@@ -24,7 +24,7 @@  public:
 	Awb();
 	~Awb() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
 	void queueRequest(IPAContext &context, const uint32_t frame,
 			  IPAFrameContext &frameContext,
diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index 19c262fffa73..1ed700a205c8 100644
--- a/src/ipa/rkisp1/algorithms/blc.cpp
+++ b/src/ipa/rkisp1/algorithms/blc.cpp
@@ -13,7 +13,7 @@ 
 
 #include <libcamera/control_ids.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 /**
  * \file blc.h
@@ -53,7 +53,7 @@  BlackLevelCorrection::BlackLevelCorrection()
 /**
  * \copydoc libcamera::ipa::Algorithm::init
  */
-int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData)
+int BlackLevelCorrection::init(IPAContext &context, const ValueNode &tuningData)
 {
 	std::optional<int16_t> levelRed = tuningData["R"].get<int16_t>();
 	std::optional<int16_t> levelGreenR = tuningData["Gr"].get<int16_t>();
diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h
index f797ae44d639..3b2b0ce6e2a8 100644
--- a/src/ipa/rkisp1/algorithms/blc.h
+++ b/src/ipa/rkisp1/algorithms/blc.h
@@ -19,7 +19,7 @@  public:
 	BlackLevelCorrection();
 	~BlackLevelCorrection() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void prepare(IPAContext &context, const uint32_t frame,
diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
index 3ed307280677..710989de1d74 100644
--- a/src/ipa/rkisp1/algorithms/ccm.cpp
+++ b/src/ipa/rkisp1/algorithms/ccm.cpp
@@ -16,7 +16,7 @@ 
 
 #include <libcamera/ipa/core_ipa_interface.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "libipa/fixedpoint.h"
 #include "libipa/interpolator.h"
@@ -41,7 +41,7 @@  constexpr Matrix<float, 3, 3> kIdentity3x3 = Matrix<float, 3, 3>::identity();
 /**
  * \copydoc libcamera::ipa::Algorithm::init
  */
-int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
 {
 	auto &cmap = context.ctrlMap;
 	cmap[&controls::ColourCorrectionMatrix] = ControlInfo(
diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h
index c301e6e531c8..9ac537426d16 100644
--- a/src/ipa/rkisp1/algorithms/ccm.h
+++ b/src/ipa/rkisp1/algorithms/ccm.h
@@ -25,7 +25,7 @@  public:
 	Ccm() {}
 	~Ccm() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void queueRequest(IPAContext &context,
@@ -41,7 +41,7 @@  public:
 		     ControlList &metadata) override;
 
 private:
-	void parseYaml(const YamlObject &tuningData);
+	void parseYaml(const ValueNode &tuningData);
 	void setParameters(struct rkisp1_cif_isp_ctk_config &config,
 			   const Matrix<float, 3, 3> &matrix,
 			   const Matrix<int16_t, 3, 1> &offsets);
diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp
index d1fff6990d37..ae3effacd359 100644
--- a/src/ipa/rkisp1/algorithms/cproc.cpp
+++ b/src/ipa/rkisp1/algorithms/cproc.cpp
@@ -55,7 +55,7 @@  int convertContrastOrSaturation(const float v)
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int ColorProcessing::init(IPAContext &context,
-			  [[maybe_unused]] const YamlObject &tuningData)
+			  [[maybe_unused]] const ValueNode &tuningData)
 {
 	auto &cmap = context.ctrlMap;
 
diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h
index fd38fd17e8bb..a3863b94fb6e 100644
--- a/src/ipa/rkisp1/algorithms/cproc.h
+++ b/src/ipa/rkisp1/algorithms/cproc.h
@@ -21,7 +21,7 @@  public:
 	ColorProcessing() = default;
 	~ColorProcessing() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void queueRequest(IPAContext &context, const uint32_t frame,
diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp
index c195334750e1..eb8cbf2049c5 100644
--- a/src/ipa/rkisp1/algorithms/dpcc.cpp
+++ b/src/ipa/rkisp1/algorithms/dpcc.cpp
@@ -9,7 +9,7 @@ 
 
 #include <libcamera/base/log.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "linux/rkisp1-config.h"
 
@@ -45,7 +45,7 @@  DefectPixelClusterCorrection::DefectPixelClusterCorrection()
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
-				       const YamlObject &tuningData)
+				       const ValueNode &tuningData)
 {
 	config_.mode = RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE;
 	config_.output_mode = RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER
@@ -55,7 +55,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 			? RKISP1_CIF_ISP_DPCC_SET_USE_STAGE1_USE_FIX_SET : 0;
 
 	/* Get all defined sets to apply (up to 3). */
-	const YamlObject &setsObject = tuningData["sets"];
+	const ValueNode &setsObject = tuningData["sets"];
 	if (!setsObject.isList()) {
 		LOG(RkISP1Dpcc, Error)
 			<< "'sets' parameter not found in tuning file";
@@ -71,14 +71,14 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 
 	for (std::size_t i = 0; i < setsObject.size(); ++i) {
 		struct rkisp1_cif_isp_dpcc_methods_config &method = config_.methods[i];
-		const YamlObject &set = setsObject[i];
+		const ValueNode &set = setsObject[i];
 		uint16_t value;
 
 		/* Enable set if described in YAML tuning file. */
 		config_.set_use |= 1 << i;
 
 		/* PG Method */
-		const YamlObject &pgObject = set["pg-factor"];
+		const ValueNode &pgObject = set["pg-factor"];
 
 		if (pgObject.contains("green")) {
 			method.method |=
@@ -97,7 +97,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 		}
 
 		/* RO Method */
-		const YamlObject &roObject = set["ro-limits"];
+		const ValueNode &roObject = set["ro-limits"];
 
 		if (roObject.contains("green")) {
 			method.method |=
@@ -118,7 +118,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 		}
 
 		/* RG Method */
-		const YamlObject &rgObject = set["rg-factor"];
+		const ValueNode &rgObject = set["rg-factor"];
 		method.rg_fac = 0;
 
 		if (rgObject.contains("green")) {
@@ -138,7 +138,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 		}
 
 		/* RND Method */
-		const YamlObject &rndOffsetsObject = set["rnd-offsets"];
+		const ValueNode &rndOffsetsObject = set["rnd-offsets"];
 
 		if (rndOffsetsObject.contains("green")) {
 			method.method |=
@@ -158,7 +158,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 				RKISP1_CIF_ISP_DPCC_RND_OFFS_n_RB(i, value);
 		}
 
-		const YamlObject &rndThresholdObject = set["rnd-threshold"];
+		const ValueNode &rndThresholdObject = set["rnd-threshold"];
 		method.rnd_thresh = 0;
 
 		if (rndThresholdObject.contains("green")) {
@@ -180,7 +180,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 		}
 
 		/* LC Method */
-		const YamlObject &lcThresholdObject = set["line-threshold"];
+		const ValueNode &lcThresholdObject = set["line-threshold"];
 		method.line_thresh = 0;
 
 		if (lcThresholdObject.contains("green")) {
@@ -201,7 +201,7 @@  int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
 				RKISP1_CIF_ISP_DPCC_LINE_THRESH_RB(value);
 		}
 
-		const YamlObject &lcTMadFactorObject = set["line-mad-factor"];
+		const ValueNode &lcTMadFactorObject = set["line-mad-factor"];
 		method.line_mad_fac = 0;
 
 		if (lcTMadFactorObject.contains("green")) {
diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h
index b77766c300fb..50b62e9bab3f 100644
--- a/src/ipa/rkisp1/algorithms/dpcc.h
+++ b/src/ipa/rkisp1/algorithms/dpcc.h
@@ -19,7 +19,7 @@  public:
 	DefectPixelClusterCorrection();
 	~DefectPixelClusterCorrection() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     RkISP1Params *params) override;
diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp
index ec989bc2421f..b681ddeb96df 100644
--- a/src/ipa/rkisp1/algorithms/dpf.cpp
+++ b/src/ipa/rkisp1/algorithms/dpf.cpp
@@ -45,7 +45,7 @@  Dpf::Dpf()
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int Dpf::init([[maybe_unused]] IPAContext &context,
-	      const YamlObject &tuningData)
+	      const ValueNode &tuningData)
 {
 	std::vector<uint8_t> values;
 
@@ -53,7 +53,7 @@  int Dpf::init([[maybe_unused]] IPAContext &context,
 	 * The domain kernel is configured with a 9x9 kernel for the green
 	 * pixels, and a 13x9 or 9x9 kernel for red and blue pixels.
 	 */
-	const YamlObject &dFObject = tuningData["DomainFilter"];
+	const ValueNode &dFObject = tuningData["DomainFilter"];
 
 	/*
 	 * For the green component, we have the 9x9 kernel specified
@@ -134,7 +134,7 @@  int Dpf::init([[maybe_unused]] IPAContext &context,
 	 * which stores a piecewise linear function that characterizes the
 	 * sensor noise profile as a noise level function curve (NLF).
 	 */
-	const YamlObject &rFObject = tuningData["NoiseLevelFunction"];
+	const ValueNode &rFObject = tuningData["NoiseLevelFunction"];
 
 	std::vector<uint16_t> nllValues;
 	nllValues = rFObject["coeff"].get<std::vector<uint16_t>>().value_or(std::vector<uint16_t>{});
@@ -162,7 +162,7 @@  int Dpf::init([[maybe_unused]] IPAContext &context,
 		return -EINVAL;
 	}
 
-	const YamlObject &fSObject = tuningData["FilterStrength"];
+	const ValueNode &fSObject = tuningData["FilterStrength"];
 
 	strengthConfig_.r = fSObject["r"].get<uint16_t>(64);
 	strengthConfig_.g = fSObject["g"].get<uint16_t>(64);
diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h
index 2dd8cd362624..b07067cec0a5 100644
--- a/src/ipa/rkisp1/algorithms/dpf.h
+++ b/src/ipa/rkisp1/algorithms/dpf.h
@@ -21,7 +21,7 @@  public:
 	Dpf();
 	~Dpf() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	void queueRequest(IPAContext &context, const uint32_t frame,
 			  IPAFrameContext &frameContext,
 			  const ControlList &controls) override;
diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp
index 8ad79801792f..2e9b4e285503 100644
--- a/src/ipa/rkisp1/algorithms/filter.cpp
+++ b/src/ipa/rkisp1/algorithms/filter.cpp
@@ -43,7 +43,7 @@  static constexpr uint32_t kFiltModeDefault = 0x000004f2;
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int Filter::init(IPAContext &context,
-		 [[maybe_unused]] const YamlObject &tuningData)
+		 [[maybe_unused]] const ValueNode &tuningData)
 {
 	auto &cmap = context.ctrlMap;
 	cmap[&controls::Sharpness] = ControlInfo(0.0f, 10.0f, 1.0f);
diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h
index 37d8938d37bd..9f0188da7880 100644
--- a/src/ipa/rkisp1/algorithms/filter.h
+++ b/src/ipa/rkisp1/algorithms/filter.h
@@ -21,7 +21,7 @@  public:
 	Filter() = default;
 	~Filter() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	void queueRequest(IPAContext &context, const uint32_t frame,
 			  IPAFrameContext &frameContext,
 			  const ControlList &controls) override;
diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp
index 6c07bd8c71e5..e8f64bf3d5e0 100644
--- a/src/ipa/rkisp1/algorithms/goc.cpp
+++ b/src/ipa/rkisp1/algorithms/goc.cpp
@@ -13,7 +13,7 @@ 
 
 #include <libcamera/control_ids.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "linux/rkisp1-config.h"
 
@@ -48,7 +48,7 @@  const float kDefaultGamma = 2.2f;
 /**
  * \copydoc libcamera::ipa::Algorithm::init
  */
-int GammaOutCorrection::init(IPAContext &context, const YamlObject &tuningData)
+int GammaOutCorrection::init(IPAContext &context, const ValueNode &tuningData)
 {
 	if (context.hw.numGammaOutSamples !=
 	    RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10) {
diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h
index bb2ddfc92375..bd79fe19cc86 100644
--- a/src/ipa/rkisp1/algorithms/goc.h
+++ b/src/ipa/rkisp1/algorithms/goc.h
@@ -19,7 +19,7 @@  public:
 	GammaOutCorrection() = default;
 	~GammaOutCorrection() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void queueRequest(IPAContext &context,
diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp
index 292d0e80dc57..d6272f3a39ef 100644
--- a/src/ipa/rkisp1/algorithms/gsl.cpp
+++ b/src/ipa/rkisp1/algorithms/gsl.cpp
@@ -10,7 +10,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "linux/rkisp1-config.h"
 
@@ -56,7 +56,7 @@  GammaSensorLinearization::GammaSensorLinearization()
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
-				   const YamlObject &tuningData)
+				   const ValueNode &tuningData)
 {
 	std::vector<uint16_t> xIntervals =
 		tuningData["x-intervals"].get<std::vector<uint16_t>>().value_or(std::vector<uint16_t>{});
@@ -75,7 +75,7 @@  int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
 	for (unsigned int i = 0; i < kDegammaXIntervals; ++i)
 		gammaDx_[i / 8] |= (xIntervals[i] & 0x07) << ((i % 8) * 4);
 
-	const YamlObject &yObject = tuningData["y"];
+	const ValueNode &yObject = tuningData["y"];
 	if (!yObject.isDictionary()) {
 		LOG(RkISP1Gsl, Error)
 			<< "Issue while parsing 'y' in tuning file: "
diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h
index 91cf6efa7925..3ef5630713ab 100644
--- a/src/ipa/rkisp1/algorithms/gsl.h
+++ b/src/ipa/rkisp1/algorithms/gsl.h
@@ -19,7 +19,7 @@  public:
 	GammaSensorLinearization();
 	~GammaSensorLinearization() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     RkISP1Params *params) override;
diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 9c0ed44b3943..78618f65c591 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -14,7 +14,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "libipa/lsc_polynomial.h"
 #include "linux/rkisp1-config.h"
@@ -85,7 +85,7 @@  public:
 	{
 	}
 
-	int parseLscData(const YamlObject &yamlSets,
+	int parseLscData(const ValueNode &yamlSets,
 			 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
 	{
 		const auto &sets = yamlSets.asList();
@@ -204,7 +204,7 @@  private:
 class LscTableLoader
 {
 public:
-	int parseLscData(const YamlObject &yamlSets,
+	int parseLscData(const ValueNode &yamlSets,
 			 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
 	{
 		const auto &sets = yamlSets.asList();
@@ -245,7 +245,7 @@  public:
 	}
 
 private:
-	std::vector<uint16_t> parseTable(const YamlObject &tuningData,
+	std::vector<uint16_t> parseTable(const ValueNode &tuningData,
 					 const char *prop)
 	{
 		static constexpr unsigned int kLscNumSamples =
@@ -265,7 +265,7 @@  private:
 	}
 };
 
-static std::vector<double> parseSizes(const YamlObject &tuningData,
+static std::vector<double> parseSizes(const ValueNode &tuningData,
 				      const char *prop)
 {
 	std::vector<double> sizes =
@@ -305,7 +305,7 @@  LensShadingCorrection::LensShadingCorrection()
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
-				const YamlObject &tuningData)
+				const ValueNode &tuningData)
 {
 	xSize_ = parseSizes(tuningData, "x-size");
 	ySize_ = parseSizes(tuningData, "y-size");
@@ -314,7 +314,7 @@  int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 		return -EINVAL;
 
 	/* Get all defined sets to apply. */
-	const YamlObject &yamlSets = tuningData["sets"];
+	const ValueNode &yamlSets = tuningData["sets"];
 	if (!yamlSets.isList()) {
 		LOG(RkISP1Lsc, Error)
 			<< "'sets' parameter not found in tuning file";
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
index 5a0824e36dd5..6e0ebad11dc0 100644
--- a/src/ipa/rkisp1/algorithms/lsc.h
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -23,7 +23,7 @@  public:
 	LensShadingCorrection();
 	~LensShadingCorrection() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp
index e9717bb3bf66..86e46c492f04 100644
--- a/src/ipa/rkisp1/algorithms/lux.cpp
+++ b/src/ipa/rkisp1/algorithms/lux.cpp
@@ -41,7 +41,7 @@  Lux::Lux()
 /**
  * \copydoc libcamera::ipa::Algorithm::init
  */
-int Lux::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+int Lux::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
 {
 	return lux_.parseTuningData(tuningData);
 }
diff --git a/src/ipa/rkisp1/algorithms/lux.h b/src/ipa/rkisp1/algorithms/lux.h
index e0239848e252..8cb35cbae20d 100644
--- a/src/ipa/rkisp1/algorithms/lux.h
+++ b/src/ipa/rkisp1/algorithms/lux.h
@@ -22,7 +22,7 @@  class Lux : public Algorithm
 public:
 	Lux();
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     RkISP1Params *params) override;
diff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp
index 9e2688a05179..c3d73da2c5b2 100644
--- a/src/ipa/rkisp1/algorithms/wdr.cpp
+++ b/src/ipa/rkisp1/algorithms/wdr.cpp
@@ -10,7 +10,7 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include <libipa/agc_mean_luminance.h>
 #include <libipa/histogram.h>
@@ -110,7 +110,7 @@  WideDynamicRange::WideDynamicRange()
  * \copydoc libcamera::ipa::Algorithm::init
  */
 int WideDynamicRange::init([[maybe_unused]] IPAContext &context,
-			   [[maybe_unused]] const YamlObject &tuningData)
+			   [[maybe_unused]] const ValueNode &tuningData)
 {
 	if (!(context.hw.supportedBlocks & 1 << RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR)) {
 		LOG(RkISP1Wdr, Error)
diff --git a/src/ipa/rkisp1/algorithms/wdr.h b/src/ipa/rkisp1/algorithms/wdr.h
index 46f7cdeea69d..f79de66fe73b 100644
--- a/src/ipa/rkisp1/algorithms/wdr.h
+++ b/src/ipa/rkisp1/algorithms/wdr.h
@@ -23,7 +23,7 @@  public:
 	WideDynamicRange();
 	~WideDynamicRange() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
 
 	void queueRequest(IPAContext &context, const uint32_t frame,
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index fbcc39103d4b..3230823f3a28 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -185,7 +185,7 @@  int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
 		return ret;
 	}
 
-	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
 	if (!data)
 		return -EINVAL;
 
diff --git a/src/ipa/rpi/controller/algorithm.cpp b/src/ipa/rpi/controller/algorithm.cpp
index beed47a1e1c4..82bc0302fd12 100644
--- a/src/ipa/rpi/controller/algorithm.cpp
+++ b/src/ipa/rpi/controller/algorithm.cpp
@@ -9,7 +9,7 @@ 
 
 using namespace RPiController;
 
-int Algorithm::read([[maybe_unused]] const libcamera::YamlObject &params)
+int Algorithm::read([[maybe_unused]] const libcamera::ValueNode &params)
 {
 	return 0;
 }
diff --git a/src/ipa/rpi/controller/algorithm.h b/src/ipa/rpi/controller/algorithm.h
index 8839daa90916..214b06576bbf 100644
--- a/src/ipa/rpi/controller/algorithm.h
+++ b/src/ipa/rpi/controller/algorithm.h
@@ -15,7 +15,7 @@ 
 #include <memory>
 #include <map>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "controller.h"
 
@@ -32,7 +32,7 @@  public:
 	}
 	virtual ~Algorithm() = default;
 	virtual char const *name() const = 0;
-	virtual int read(const libcamera::YamlObject &params);
+	virtual int read(const libcamera::ValueNode &params);
 	virtual void initialise();
 	virtual void switchMode(CameraMode const &cameraMode, Metadata *metadata);
 	virtual void prepare(Metadata *imageMetadata);
diff --git a/src/ipa/rpi/controller/controller.cpp b/src/ipa/rpi/controller/controller.cpp
index df45dcd345b7..162c75d8848f 100644
--- a/src/ipa/rpi/controller/controller.cpp
+++ b/src/ipa/rpi/controller/controller.cpp
@@ -102,7 +102,7 @@  int Controller::read(char const *filename)
 		return -EINVAL;
 	}
 
-	std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> root = YamlParser::parse(file);
 	if (!root)
 		return -EINVAL;
 
@@ -143,7 +143,7 @@  int Controller::read(char const *filename)
 	return 0;
 }
 
-int Controller::createAlgorithm(const std::string &name, const YamlObject &params)
+int Controller::createAlgorithm(const std::string &name, const ValueNode &params)
 {
 	auto it = getAlgorithms().find(name);
 	if (it == getAlgorithms().end()) {
diff --git a/src/ipa/rpi/controller/controller.h b/src/ipa/rpi/controller/controller.h
index 573942886bc0..094917b08b6a 100644
--- a/src/ipa/rpi/controller/controller.h
+++ b/src/ipa/rpi/controller/controller.h
@@ -16,7 +16,7 @@ 
 #include <string>
 
 #include <libcamera/base/utils.h>
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "camera_mode.h"
 #include "device_status.h"
@@ -65,7 +65,7 @@  public:
 	const HardwareConfig &getHardwareConfig() const;
 
 protected:
-	int createAlgorithm(const std::string &name, const libcamera::YamlObject &params);
+	int createAlgorithm(const std::string &name, const libcamera::ValueNode &params);
 
 	Metadata globalMetadata_;
 	std::vector<AlgorithmPtr> algorithms_;
diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp
index 26e599303f24..47c2bf935835 100644
--- a/src/ipa/rpi/controller/rpi/af.cpp
+++ b/src/ipa/rpi/controller/rpi/af.cpp
@@ -68,7 +68,7 @@  Af::CfgParams::CfgParams()
 }
 
 template<typename T>
-static void readNumber(T &dest, const libcamera::YamlObject &params, char const *name)
+static void readNumber(T &dest, const libcamera::ValueNode &params, char const *name)
 {
 	auto value = params[name].get<T>();
 	if (value)
@@ -77,7 +77,7 @@  static void readNumber(T &dest, const libcamera::YamlObject &params, char const
 		LOG(RPiAf, Warning) << "Missing parameter \"" << name << "\"";
 }
 
-void Af::RangeDependentParams::read(const libcamera::YamlObject &params)
+void Af::RangeDependentParams::read(const libcamera::ValueNode &params)
 {
 
 	readNumber<double>(focusMin, params, "min");
@@ -85,7 +85,7 @@  void Af::RangeDependentParams::read(const libcamera::YamlObject &params)
 	readNumber<double>(focusDefault, params, "default");
 }
 
-void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
+void Af::SpeedDependentParams::read(const libcamera::ValueNode &params)
 {
 	readNumber<double>(stepCoarse, params, "step_coarse");
 	readNumber<double>(stepFine, params, "step_fine");
@@ -100,7 +100,7 @@  void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
 	readNumber<uint32_t>(stepFrames, params, "step_frames");
 }
 
-int Af::CfgParams::read(const libcamera::YamlObject &params)
+int Af::CfgParams::read(const libcamera::ValueNode &params)
 {
 	if (params.contains("ranges")) {
 		auto &rr = params["ranges"];
@@ -226,7 +226,7 @@  char const *Af::name() const
 	return NAME;
 }
 
-int Af::read(const libcamera::YamlObject &params)
+int Af::read(const libcamera::ValueNode &params)
 {
 	return cfg_.read(params);
 }
diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h
index d35a39d12049..b464927ba92b 100644
--- a/src/ipa/rpi/controller/rpi/af.h
+++ b/src/ipa/rpi/controller/rpi/af.h
@@ -47,7 +47,7 @@  public:
 	Af(Controller *controller = NULL);
 	~Af();
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialise() override;
 
 	/* IPA calls */
@@ -87,7 +87,7 @@  private:
 		double focusDefault;		/* default setting ("hyperfocal") */
 
 		RangeDependentParams();
-		void read(const libcamera::YamlObject &params);
+		void read(const libcamera::ValueNode &params);
 	};
 
 	struct SpeedDependentParams {
@@ -104,7 +104,7 @@  private:
 		uint32_t stepFrames;		/* frames to skip in between steps of a scan */
 
 		SpeedDependentParams();
-		void read(const libcamera::YamlObject &params);
+		void read(const libcamera::ValueNode &params);
 	};
 
 	struct CfgParams {
@@ -118,7 +118,7 @@  private:
 		libcamera::ipa::Pwl map;       	/* converts dioptres -> lens driver position */
 
 		CfgParams();
-		int read(const libcamera::YamlObject &params);
+		int read(const libcamera::ValueNode &params);
 		void initialise();
 	};
 
diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
index afda2e364f64..830b26174df2 100644
--- a/src/ipa/rpi/controller/rpi/agc.cpp
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -31,7 +31,7 @@  char const *Agc::name() const
 	return NAME;
 }
 
-int Agc::read(const libcamera::YamlObject &params)
+int Agc::read(const libcamera::ValueNode &params)
 {
 	/*
 	 * When there is only a single channel we can read the old style syntax.
diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
index 966630a26303..5189ad2a2bf8 100644
--- a/src/ipa/rpi/controller/rpi/agc.h
+++ b/src/ipa/rpi/controller/rpi/agc.h
@@ -27,7 +27,7 @@  class Agc : public AgcAlgorithm
 public:
 	Agc(Controller *controller);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	unsigned int getConvergenceFrames() const override;
 	std::vector<double> const &getWeights() const override;
 	void setEv(unsigned int channel, double ev) override;
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index c6cf1f8903f1..c0ac76c569af 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -29,9 +29,9 @@  using namespace std::literals::chrono_literals;
 
 LOG_DECLARE_CATEGORY(RPiAgc)
 
-int AgcMeteringMode::read(const libcamera::YamlObject &params)
+int AgcMeteringMode::read(const libcamera::ValueNode &params)
 {
-	const YamlObject &yamlWeights = params["weights"];
+	const ValueNode &yamlWeights = params["weights"];
 
 	for (const auto &p : yamlWeights.asList()) {
 		auto value = p.get<double>();
@@ -45,7 +45,7 @@  int AgcMeteringMode::read(const libcamera::YamlObject &params)
 
 static std::tuple<int, std::string>
 readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
-		  const libcamera::YamlObject &params)
+		  const libcamera::ValueNode &params)
 {
 	std::string first;
 	int ret;
@@ -64,7 +64,7 @@  readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
 	return { 0, first };
 }
 
-int AgcExposureMode::read(const libcamera::YamlObject &params)
+int AgcExposureMode::read(const libcamera::ValueNode &params)
 {
 	auto value = params["shutter"].get<std::vector<double>>();
 	if (!value)
@@ -94,7 +94,7 @@  int AgcExposureMode::read(const libcamera::YamlObject &params)
 
 static std::tuple<int, std::string>
 readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
-		  const libcamera::YamlObject &params)
+		  const libcamera::ValueNode &params)
 {
 	std::string first;
 	int ret;
@@ -113,7 +113,7 @@  readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
 	return { 0, first };
 }
 
-int AgcConstraint::read(const libcamera::YamlObject &params)
+int AgcConstraint::read(const libcamera::ValueNode &params)
 {
 	std::string boundString = params["bound"].get<std::string>("");
 	transform(boundString.begin(), boundString.end(),
@@ -139,7 +139,7 @@  int AgcConstraint::read(const libcamera::YamlObject &params)
 }
 
 static std::tuple<int, AgcConstraintMode>
-readConstraintMode(const libcamera::YamlObject &params)
+readConstraintMode(const libcamera::ValueNode &params)
 {
 	AgcConstraintMode mode;
 	int ret;
@@ -158,7 +158,7 @@  readConstraintMode(const libcamera::YamlObject &params)
 
 static std::tuple<int, std::string>
 readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
-		    const libcamera::YamlObject &params)
+		    const libcamera::ValueNode &params)
 {
 	std::string first;
 	int ret;
@@ -175,7 +175,7 @@  readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
 	return { 0, first };
 }
 
-int AgcChannelConstraint::read(const libcamera::YamlObject &params)
+int AgcChannelConstraint::read(const libcamera::ValueNode &params)
 {
 	auto channelValue = params["channel"].get<unsigned int>();
 	if (!channelValue) {
@@ -204,7 +204,7 @@  int AgcChannelConstraint::read(const libcamera::YamlObject &params)
 }
 
 static int readChannelConstraints(std::vector<AgcChannelConstraint> &channelConstraints,
-				  const libcamera::YamlObject &params)
+				  const libcamera::ValueNode &params)
 {
 	for (const auto &p : params.asList()) {
 		AgcChannelConstraint constraint;
@@ -218,7 +218,7 @@  static int readChannelConstraints(std::vector<AgcChannelConstraint> &channelCons
 	return 0;
 }
 
-int AgcConfig::read(const libcamera::YamlObject &params)
+int AgcConfig::read(const libcamera::ValueNode &params)
 {
 	LOG(RPiAgc, Debug) << "AgcConfig";
 	int ret;
@@ -290,7 +290,7 @@  AgcChannel::AgcChannel()
 	status_.ev = ev_;
 }
 
-int AgcChannel::read(const libcamera::YamlObject &params,
+int AgcChannel::read(const libcamera::ValueNode &params,
 		     const Controller::HardwareConfig &hardwareConfig)
 {
 	int ret = config_.read(params);
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
index 42d85ec15e8d..90e540a8a18d 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.h
+++ b/src/ipa/rpi/controller/rpi/agc_channel.h
@@ -26,13 +26,13 @@  using AgcChannelTotalExposures = std::vector<libcamera::utils::Duration>;
 
 struct AgcMeteringMode {
 	std::vector<double> weights;
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 };
 
 struct AgcExposureMode {
 	std::vector<libcamera::utils::Duration> exposureTime;
 	std::vector<double> gain;
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 };
 
 struct AgcConstraint {
@@ -42,7 +42,7 @@  struct AgcConstraint {
 	double qLo;
 	double qHi;
 	libcamera::ipa::Pwl yTarget;
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 };
 
 typedef std::vector<AgcConstraint> AgcConstraintMode;
@@ -53,11 +53,11 @@  struct AgcChannelConstraint {
 	Bound bound;
 	unsigned int channel;
 	double factor;
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 };
 
 struct AgcConfig {
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 	std::map<std::string, AgcMeteringMode> meteringModes;
 	std::map<std::string, AgcExposureMode> exposureModes;
 	std::map<std::string, AgcConstraintMode> constraintModes;
@@ -85,7 +85,7 @@  class AgcChannel
 {
 public:
 	AgcChannel();
-	int read(const libcamera::YamlObject &params,
+	int read(const libcamera::ValueNode &params,
 		 const Controller::HardwareConfig &hardwareConfig);
 	unsigned int getConvergenceFrames() const;
 	std::vector<double> const &getWeights() const;
diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp
index 21edb819ad1c..4c852db04f7f 100644
--- a/src/ipa/rpi/controller/rpi/alsc.cpp
+++ b/src/ipa/rpi/controller/rpi/alsc.cpp
@@ -50,7 +50,7 @@  char const *Alsc::name() const
 	return NAME;
 }
 
-static int generateLut(Array2D<double> &lut, const libcamera::YamlObject &params)
+static int generateLut(Array2D<double> &lut, const libcamera::ValueNode &params)
 {
 	/* These must be signed ints for the co-ordinate calculations below. */
 	int X = lut.dimensions().width, Y = lut.dimensions().height;
@@ -82,7 +82,7 @@  static int generateLut(Array2D<double> &lut, const libcamera::YamlObject &params
 	return 0;
 }
 
-static int readLut(Array2D<double> &lut, const libcamera::YamlObject &params)
+static int readLut(Array2D<double> &lut, const libcamera::ValueNode &params)
 {
 	if (params.size() != lut.size()) {
 		LOG(RPiAlsc, Error) << "Invalid number of entries in LSC table";
@@ -101,7 +101,7 @@  static int readLut(Array2D<double> &lut, const libcamera::YamlObject &params)
 }
 
 static int readCalibrations(std::vector<AlscCalibration> &calibrations,
-			    const libcamera::YamlObject &params,
+			    const libcamera::ValueNode &params,
 			    std::string const &name, const Size &size)
 {
 	if (params.contains(name)) {
@@ -119,7 +119,7 @@  static int readCalibrations(std::vector<AlscCalibration> &calibrations,
 			AlscCalibration calibration;
 			calibration.ct = lastCt = ct;
 
-			const libcamera::YamlObject &table = p["table"];
+			const libcamera::ValueNode &table = p["table"];
 			if (table.size() != size.width * size.height) {
 				LOG(RPiAlsc, Error)
 					<< "Incorrect number of values for ct "
@@ -144,7 +144,7 @@  static int readCalibrations(std::vector<AlscCalibration> &calibrations,
 	return 0;
 }
 
-int Alsc::read(const libcamera::YamlObject &params)
+int Alsc::read(const libcamera::ValueNode &params)
 {
 	config_.tableSize = getHardwareConfig().awbRegions;
 	config_.framePeriod = params["frame_period"].get<uint16_t>(12);
diff --git a/src/ipa/rpi/controller/rpi/alsc.h b/src/ipa/rpi/controller/rpi/alsc.h
index 310879820fba..0952ae8358c6 100644
--- a/src/ipa/rpi/controller/rpi/alsc.h
+++ b/src/ipa/rpi/controller/rpi/alsc.h
@@ -112,7 +112,7 @@  public:
 	char const *name() const override;
 	void initialise() override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void prepare(Metadata *imageMetadata) override;
 	void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
 
diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp
index 365b595ff9b4..1ccac36e5b4f 100644
--- a/src/ipa/rpi/controller/rpi/awb.cpp
+++ b/src/ipa/rpi/controller/rpi/awb.cpp
@@ -30,7 +30,7 @@  constexpr double kDefaultCT = 4500.0;
  * elsewhere (ALSC and AGC).
  */
 
-int AwbMode::read(const libcamera::YamlObject &params)
+int AwbMode::read(const libcamera::ValueNode &params)
 {
 	auto value = params["lo"].get<double>();
 	if (!value)
@@ -45,7 +45,7 @@  int AwbMode::read(const libcamera::YamlObject &params)
 	return 0;
 }
 
-int AwbPrior::read(const libcamera::YamlObject &params)
+int AwbPrior::read(const libcamera::ValueNode &params)
 {
 	auto value = params["lux"].get<double>();
 	if (!value)
@@ -56,7 +56,7 @@  int AwbPrior::read(const libcamera::YamlObject &params)
 	return prior.empty() ? -EINVAL : 0;
 }
 
-static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject &params)
+static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::ValueNode &params)
 {
 	if (params.size() % 3) {
 		LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry";
@@ -92,7 +92,7 @@  static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject
 	return 0;
 }
 
-int AwbConfig::read(const libcamera::YamlObject &params)
+int AwbConfig::read(const libcamera::ValueNode &params)
 {
 	int ret;
 
@@ -204,7 +204,7 @@  char const *Awb::name() const
 	return NAME;
 }
 
-int Awb::read(const libcamera::YamlObject &params)
+int Awb::read(const libcamera::ValueNode &params)
 {
 	return config_.read(params);
 }
diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h
index 2fb912541a2b..ac73105113fb 100644
--- a/src/ipa/rpi/controller/rpi/awb.h
+++ b/src/ipa/rpi/controller/rpi/awb.h
@@ -23,20 +23,20 @@  namespace RPiController {
 /* Control algorithm to perform AWB calculations. */
 
 struct AwbMode {
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 	double ctLo; /* low CT value for search */
 	double ctHi; /* high CT value for search */
 };
 
 struct AwbPrior {
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 	double lux; /* lux level */
 	libcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */
 };
 
 struct AwbConfig {
 	AwbConfig() : defaultMode(nullptr) {}
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 	/* Only repeat the AWB calculation every "this many" frames */
 	uint16_t framePeriod;
 	/* number of initial frames for which speed taken as 1.0 (maximum) */
@@ -99,7 +99,7 @@  public:
 	~Awb();
 	char const *name() const override;
 	void initialise() override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	unsigned int getConvergenceFrames() const override;
 	void initialValues(double &gainR, double &gainB) override;
 	void setMode(std::string const &name) override;
diff --git a/src/ipa/rpi/controller/rpi/black_level.cpp b/src/ipa/rpi/controller/rpi/black_level.cpp
index 4c968f14a1ca..42ea1505014f 100644
--- a/src/ipa/rpi/controller/rpi/black_level.cpp
+++ b/src/ipa/rpi/controller/rpi/black_level.cpp
@@ -30,7 +30,7 @@  char const *BlackLevel::name() const
 	return NAME;
 }
 
-int BlackLevel::read(const libcamera::YamlObject &params)
+int BlackLevel::read(const libcamera::ValueNode &params)
 {
 	/* 64 in 10 bits scaled to 16 bits */
 	uint16_t blackLevel = params["black_level"].get<uint16_t>(4096);
diff --git a/src/ipa/rpi/controller/rpi/black_level.h b/src/ipa/rpi/controller/rpi/black_level.h
index f50729dbc1e3..dbf29b282e4c 100644
--- a/src/ipa/rpi/controller/rpi/black_level.h
+++ b/src/ipa/rpi/controller/rpi/black_level.h
@@ -18,7 +18,7 @@  class BlackLevel : public BlackLevelAlgorithm
 public:
 	BlackLevel(Controller *controller);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG,
 			   uint16_t &blackLevelB) override;
 	void prepare(Metadata *imageMetadata) override;
diff --git a/src/ipa/rpi/controller/rpi/cac.cpp b/src/ipa/rpi/controller/rpi/cac.cpp
index 17779ad5469b..ddc848f21f3f 100644
--- a/src/ipa/rpi/controller/rpi/cac.cpp
+++ b/src/ipa/rpi/controller/rpi/cac.cpp
@@ -27,7 +27,7 @@  char const *Cac::name() const
 	return NAME;
 }
 
-static bool arrayToSet(const libcamera::YamlObject &params, std::vector<double> &inputArray, const Size &size)
+static bool arrayToSet(const libcamera::ValueNode &params, std::vector<double> &inputArray, const Size &size)
 {
 	int num = 0;
 	int max_num = (size.width + 1) * (size.height + 1);
@@ -51,7 +51,7 @@  static void setStrength(std::vector<double> &inputArray, std::vector<double> &ou
 	}
 }
 
-int Cac::read(const libcamera::YamlObject &params)
+int Cac::read(const libcamera::ValueNode &params)
 {
 	config_.enabled = params.contains("lut_rx") && params.contains("lut_ry") &&
 			  params.contains("lut_bx") && params.contains("lut_by");
diff --git a/src/ipa/rpi/controller/rpi/cac.h b/src/ipa/rpi/controller/rpi/cac.h
index 533cca44424b..11c47a7c4323 100644
--- a/src/ipa/rpi/controller/rpi/cac.h
+++ b/src/ipa/rpi/controller/rpi/cac.h
@@ -24,7 +24,7 @@  class Cac : public Algorithm
 public:
 	Cac(Controller *controller = NULL);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void prepare(Metadata *imageMetadata) override;
 
 private:
diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp
index 2806b4967158..d3231182913d 100644
--- a/src/ipa/rpi/controller/rpi/ccm.cpp
+++ b/src/ipa/rpi/controller/rpi/ccm.cpp
@@ -40,7 +40,7 @@  char const *Ccm::name() const
 	return NAME;
 }
 
-int Ccm::read(const libcamera::YamlObject &params)
+int Ccm::read(const libcamera::ValueNode &params)
 {
 	if (params.contains("saturation")) {
 		config_.saturation = params["saturation"].get<ipa::Pwl>(ipa::Pwl{});
diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h
index 70f28ed33d5e..a0f5b698db9b 100644
--- a/src/ipa/rpi/controller/rpi/ccm.h
+++ b/src/ipa/rpi/controller/rpi/ccm.h
@@ -31,7 +31,7 @@  class Ccm : public CcmAlgorithm
 public:
 	Ccm(Controller *controller = NULL);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void enableAuto() override;
 	void setSaturation(double saturation) override;
 	void setCcm(Matrix3x3 const &matrix) override;
diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp
index fe866a544293..3457e04002e0 100644
--- a/src/ipa/rpi/controller/rpi/contrast.cpp
+++ b/src/ipa/rpi/controller/rpi/contrast.cpp
@@ -38,7 +38,7 @@  char const *Contrast::name() const
 	return NAME;
 }
 
-int Contrast::read(const libcamera::YamlObject &params)
+int Contrast::read(const libcamera::ValueNode &params)
 {
 	// enable adaptive enhancement by default
 	config_.ceEnable = params["ce_enable"].get<int>(1);
diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h
index c0f7db981c7d..3571626d8623 100644
--- a/src/ipa/rpi/controller/rpi/contrast.h
+++ b/src/ipa/rpi/controller/rpi/contrast.h
@@ -35,7 +35,7 @@  class Contrast : public ContrastAlgorithm
 public:
 	Contrast(Controller *controller = NULL);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void setBrightness(double brightness) override;
 	void setContrast(double contrast) override;
 	void enableCe(bool enable) override;
diff --git a/src/ipa/rpi/controller/rpi/decompand.cpp b/src/ipa/rpi/controller/rpi/decompand.cpp
index 2d457926c060..5646984132f6 100644
--- a/src/ipa/rpi/controller/rpi/decompand.cpp
+++ b/src/ipa/rpi/controller/rpi/decompand.cpp
@@ -22,7 +22,7 @@  char const *Decompand::name() const
 	return NAME;
 }
 
-int Decompand::read(const libcamera::YamlObject &params)
+int Decompand::read(const libcamera::ValueNode &params)
 {
 	config_.bitdepth = params["bitdepth"].get<uint32_t>(0);
 	config_.decompandCurve = params["decompand_curve"].get<ipa::Pwl>(ipa::Pwl{});
diff --git a/src/ipa/rpi/controller/rpi/decompand.h b/src/ipa/rpi/controller/rpi/decompand.h
index 6db779c359a8..847769af4338 100644
--- a/src/ipa/rpi/controller/rpi/decompand.h
+++ b/src/ipa/rpi/controller/rpi/decompand.h
@@ -17,7 +17,7 @@  class Decompand : public DecompandAlgorithm
 public:
 	Decompand(Controller *controller = nullptr);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialise() override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
 	void initialValues(libcamera::ipa::Pwl &decompandCurve) override;
diff --git a/src/ipa/rpi/controller/rpi/denoise.cpp b/src/ipa/rpi/controller/rpi/denoise.cpp
index cabe3e2a08aa..0cd0dd53fe97 100644
--- a/src/ipa/rpi/controller/rpi/denoise.cpp
+++ b/src/ipa/rpi/controller/rpi/denoise.cpp
@@ -21,7 +21,7 @@  LOG_DEFINE_CATEGORY(RPiDenoise)
 
 #define NAME "rpi.denoise"
 
-int DenoiseConfig::read(const libcamera::YamlObject &params)
+int DenoiseConfig::read(const libcamera::ValueNode &params)
 {
 	sdnEnable = params.contains("sdn");
 	if (sdnEnable) {
@@ -82,7 +82,7 @@  char const *Denoise::name() const
 	return NAME;
 }
 
-int Denoise::read(const libcamera::YamlObject &params)
+int Denoise::read(const libcamera::ValueNode &params)
 {
 	if (!params.contains("normal")) {
 		configs_["normal"].read(params);
diff --git a/src/ipa/rpi/controller/rpi/denoise.h b/src/ipa/rpi/controller/rpi/denoise.h
index e23a2e8ff525..499b2ab74749 100644
--- a/src/ipa/rpi/controller/rpi/denoise.h
+++ b/src/ipa/rpi/controller/rpi/denoise.h
@@ -31,7 +31,7 @@  struct DenoiseConfig {
 	bool tdnEnable;
 	bool sdnEnable;
 	bool cdnEnable;
-	int read(const libcamera::YamlObject &params);
+	int read(const libcamera::ValueNode &params);
 };
 
 class Denoise : public DenoiseAlgorithm
@@ -39,7 +39,7 @@  class Denoise : public DenoiseAlgorithm
 public:
 	Denoise(Controller *controller);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialise() override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
 	void prepare(Metadata *imageMetadata) override;
diff --git a/src/ipa/rpi/controller/rpi/dpc.cpp b/src/ipa/rpi/controller/rpi/dpc.cpp
index 8aac03f794fc..a92207999fab 100644
--- a/src/ipa/rpi/controller/rpi/dpc.cpp
+++ b/src/ipa/rpi/controller/rpi/dpc.cpp
@@ -31,7 +31,7 @@  char const *Dpc::name() const
 	return NAME;
 }
 
-int Dpc::read(const libcamera::YamlObject &params)
+int Dpc::read(const libcamera::ValueNode &params)
 {
 	config_.strength = params["strength"].get<int>(1);
 	if (config_.strength < 0 || config_.strength > 2) {
diff --git a/src/ipa/rpi/controller/rpi/dpc.h b/src/ipa/rpi/controller/rpi/dpc.h
index 9cefb06d4a7c..a1a02af59cbc 100644
--- a/src/ipa/rpi/controller/rpi/dpc.h
+++ b/src/ipa/rpi/controller/rpi/dpc.h
@@ -22,7 +22,7 @@  class Dpc : public Algorithm
 public:
 	Dpc(Controller *controller);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void prepare(Metadata *imageMetadata) override;
 
 private:
diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp
index 40e7191ba16a..9382a033f055 100644
--- a/src/ipa/rpi/controller/rpi/geq.cpp
+++ b/src/ipa/rpi/controller/rpi/geq.cpp
@@ -34,7 +34,7 @@  char const *Geq::name() const
 	return NAME;
 }
 
-int Geq::read(const libcamera::YamlObject &params)
+int Geq::read(const libcamera::ValueNode &params)
 {
 	config_.offset = params["offset"].get<uint16_t>(0);
 	config_.slope = params["slope"].get<double>(0.0);
diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h
index e8b9f42708c0..4827051b4f46 100644
--- a/src/ipa/rpi/controller/rpi/geq.h
+++ b/src/ipa/rpi/controller/rpi/geq.h
@@ -26,7 +26,7 @@  class Geq : public Algorithm
 public:
 	Geq(Controller *controller);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void prepare(Metadata *imageMetadata) override;
 
 private:
diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp
index 06400ea79a95..c256f7485f77 100644
--- a/src/ipa/rpi/controller/rpi/hdr.cpp
+++ b/src/ipa/rpi/controller/rpi/hdr.cpp
@@ -23,7 +23,7 @@  LOG_DEFINE_CATEGORY(RPiHdr)
 
 #define NAME "rpi.hdr"
 
-void HdrConfig::read(const libcamera::YamlObject &params, const std::string &modeName)
+void HdrConfig::read(const libcamera::ValueNode &params, const std::string &modeName)
 {
 	name = modeName;
 
@@ -111,7 +111,7 @@  char const *Hdr::name() const
 	return NAME;
 }
 
-int Hdr::read(const libcamera::YamlObject &params)
+int Hdr::read(const libcamera::ValueNode &params)
 {
 	/* Make an "HDR off" mode by default so that tuning files don't have to. */
 	HdrConfig &offMode = config_["Off"];
diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h
index 5c2f3988d789..58ff5ba83d2e 100644
--- a/src/ipa/rpi/controller/rpi/hdr.h
+++ b/src/ipa/rpi/controller/rpi/hdr.h
@@ -52,7 +52,7 @@  struct HdrConfig {
 	uint8_t diffPower;
 	double motionThreshold;
 
-	void read(const libcamera::YamlObject &params, const std::string &name);
+	void read(const libcamera::ValueNode &params, const std::string &name);
 };
 
 class Hdr : public HdrAlgorithm
@@ -61,7 +61,7 @@  public:
 	Hdr(Controller *controller);
 	char const *name() const override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void prepare(Metadata *imageMetadata) override;
 	void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
 	int setMode(std::string const &mode) override;
diff --git a/src/ipa/rpi/controller/rpi/lux.cpp b/src/ipa/rpi/controller/rpi/lux.cpp
index 7dab27cc0a90..2047a240bad5 100644
--- a/src/ipa/rpi/controller/rpi/lux.cpp
+++ b/src/ipa/rpi/controller/rpi/lux.cpp
@@ -35,7 +35,7 @@  char const *Lux::name() const
 	return NAME;
 }
 
-int Lux::read(const libcamera::YamlObject &params)
+int Lux::read(const libcamera::ValueNode &params)
 {
 	auto value = params["reference_shutter_speed"].get<double>();
 	if (!value)
diff --git a/src/ipa/rpi/controller/rpi/lux.h b/src/ipa/rpi/controller/rpi/lux.h
index db2227e41455..c9ffe38b69db 100644
--- a/src/ipa/rpi/controller/rpi/lux.h
+++ b/src/ipa/rpi/controller/rpi/lux.h
@@ -23,7 +23,7 @@  class Lux : public Algorithm
 public:
 	Lux(Controller *controller);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
 	void prepare(Metadata *imageMetadata) override;
 	void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
diff --git a/src/ipa/rpi/controller/rpi/noise.cpp b/src/ipa/rpi/controller/rpi/noise.cpp
index 145175fb4940..d6e01c2cfe4e 100644
--- a/src/ipa/rpi/controller/rpi/noise.cpp
+++ b/src/ipa/rpi/controller/rpi/noise.cpp
@@ -41,7 +41,7 @@  void Noise::switchMode(CameraMode const &cameraMode,
 	modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
 }
 
-int Noise::read(const libcamera::YamlObject &params)
+int Noise::read(const libcamera::ValueNode &params)
 {
 	auto value = params["reference_constant"].get<double>();
 	if (!value)
diff --git a/src/ipa/rpi/controller/rpi/noise.h b/src/ipa/rpi/controller/rpi/noise.h
index 6deae1f0282e..c449fa52ffd2 100644
--- a/src/ipa/rpi/controller/rpi/noise.h
+++ b/src/ipa/rpi/controller/rpi/noise.h
@@ -19,7 +19,7 @@  public:
 	Noise(Controller *controller);
 	char const *name() const override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void prepare(Metadata *imageMetadata) override;
 
 private:
diff --git a/src/ipa/rpi/controller/rpi/saturation.cpp b/src/ipa/rpi/controller/rpi/saturation.cpp
index b83c5887c02e..5001f930a1ec 100644
--- a/src/ipa/rpi/controller/rpi/saturation.cpp
+++ b/src/ipa/rpi/controller/rpi/saturation.cpp
@@ -27,7 +27,7 @@  char const *Saturation::name() const
 	return NAME;
 }
 
-int Saturation::read(const libcamera::YamlObject &params)
+int Saturation::read(const libcamera::ValueNode &params)
 {
 	config_.shiftR = params["shift_r"].get<uint8_t>(0);
 	config_.shiftG = params["shift_g"].get<uint8_t>(0);
diff --git a/src/ipa/rpi/controller/rpi/saturation.h b/src/ipa/rpi/controller/rpi/saturation.h
index c67d496ef065..e6a22c8bb94d 100644
--- a/src/ipa/rpi/controller/rpi/saturation.h
+++ b/src/ipa/rpi/controller/rpi/saturation.h
@@ -21,7 +21,7 @@  class Saturation : public Algorithm
 public:
 	Saturation(Controller *controller = NULL);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialise() override;
 	void prepare(Metadata *imageMetadata) override;
 
diff --git a/src/ipa/rpi/controller/rpi/sdn.cpp b/src/ipa/rpi/controller/rpi/sdn.cpp
index 594ea70133ac..13dfad02068c 100644
--- a/src/ipa/rpi/controller/rpi/sdn.cpp
+++ b/src/ipa/rpi/controller/rpi/sdn.cpp
@@ -35,7 +35,7 @@  char const *Sdn::name() const
 	return NAME;
 }
 
-int Sdn::read(const libcamera::YamlObject &params)
+int Sdn::read(const libcamera::ValueNode &params)
 {
 	deviation_ = params["deviation"].get<double>(3.2);
 	strength_ = params["strength"].get<double>(0.75);
diff --git a/src/ipa/rpi/controller/rpi/sdn.h b/src/ipa/rpi/controller/rpi/sdn.h
index cb226de88c3c..20d847f0cefa 100644
--- a/src/ipa/rpi/controller/rpi/sdn.h
+++ b/src/ipa/rpi/controller/rpi/sdn.h
@@ -18,7 +18,7 @@  class Sdn : public DenoiseAlgorithm
 public:
 	Sdn(Controller *controller = NULL);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialise() override;
 	void prepare(Metadata *imageMetadata) override;
 	void setMode(DenoiseMode mode) override;
diff --git a/src/ipa/rpi/controller/rpi/sharpen.cpp b/src/ipa/rpi/controller/rpi/sharpen.cpp
index 1d143ff53287..e8723916c965 100644
--- a/src/ipa/rpi/controller/rpi/sharpen.cpp
+++ b/src/ipa/rpi/controller/rpi/sharpen.cpp
@@ -37,7 +37,7 @@  void Sharpen::switchMode(CameraMode const &cameraMode,
 	modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
 }
 
-int Sharpen::read(const libcamera::YamlObject &params)
+int Sharpen::read(const libcamera::ValueNode &params)
 {
 	threshold_ = params["threshold"].get<double>(1.0);
 	strength_ = params["strength"].get<double>(1.0);
diff --git a/src/ipa/rpi/controller/rpi/sharpen.h b/src/ipa/rpi/controller/rpi/sharpen.h
index 96ccd60934f8..2814ec85fef1 100644
--- a/src/ipa/rpi/controller/rpi/sharpen.h
+++ b/src/ipa/rpi/controller/rpi/sharpen.h
@@ -19,7 +19,7 @@  public:
 	Sharpen(Controller *controller);
 	char const *name() const override;
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void setStrength(double strength) override;
 	void prepare(Metadata *imageMetadata) override;
 
diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp
index 3422adfe7dee..3cbecf5379ac 100644
--- a/src/ipa/rpi/controller/rpi/tonemap.cpp
+++ b/src/ipa/rpi/controller/rpi/tonemap.cpp
@@ -27,7 +27,7 @@  char const *Tonemap::name() const
 	return NAME;
 }
 
-int Tonemap::read(const libcamera::YamlObject &params)
+int Tonemap::read(const libcamera::ValueNode &params)
 {
 	config_.detailConstant = params["detail_constant"].get<uint16_t>(0);
 	config_.detailSlope = params["detail_slope"].get<double>(0.1);
diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h
index 4e513b1d00da..4d486d136499 100644
--- a/src/ipa/rpi/controller/rpi/tonemap.h
+++ b/src/ipa/rpi/controller/rpi/tonemap.h
@@ -25,7 +25,7 @@  class Tonemap : public Algorithm
 public:
 	Tonemap(Controller *controller = NULL);
 	char const *name() const override;
-	int read(const libcamera::YamlObject &params) override;
+	int read(const libcamera::ValueNode &params) override;
 	void initialise() override;
 	void prepare(Metadata *imageMetadata) override;
 
diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp
index 464e43c278f3..677be56ed669 100644
--- a/src/ipa/simple/algorithms/blc.cpp
+++ b/src/ipa/simple/algorithms/blc.cpp
@@ -24,7 +24,7 @@  BlackLevel::BlackLevel()
 }
 
 int BlackLevel::init([[maybe_unused]] IPAContext &context,
-		     const YamlObject &tuningData)
+		     const ValueNode &tuningData)
 {
 	auto blackLevel = tuningData["blackLevel"].get<int16_t>();
 	if (blackLevel.has_value()) {
diff --git a/src/ipa/simple/algorithms/blc.h b/src/ipa/simple/algorithms/blc.h
index a5592d08740f..2933ff1fffe7 100644
--- a/src/ipa/simple/algorithms/blc.h
+++ b/src/ipa/simple/algorithms/blc.h
@@ -22,7 +22,7 @@  public:
 	BlackLevel();
 	~BlackLevel() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void prepare(IPAContext &context,
 		     const uint32_t frame,
diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
index 0a98406c1a3a..ec66f0193478 100644
--- a/src/ipa/simple/algorithms/ccm.cpp
+++ b/src/ipa/simple/algorithms/ccm.cpp
@@ -27,7 +27,7 @@  namespace ipa::soft::algorithms {
 
 LOG_DEFINE_CATEGORY(IPASoftCcm)
 
-int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
 {
 	int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm");
 	if (ret < 0) {
diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h
index 8279a3d5967e..3e3755165c03 100644
--- a/src/ipa/simple/algorithms/ccm.h
+++ b/src/ipa/simple/algorithms/ccm.h
@@ -25,7 +25,7 @@  public:
 	Ccm() = default;
 	~Ccm() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPAConfigInfo &configInfo) override;
 	void queueRequest(typename Module::Context &context,
diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp
index 54cb804e7822..5e09bce79ee1 100644
--- a/src/ipa/simple/algorithms/lut.cpp
+++ b/src/ipa/simple/algorithms/lut.cpp
@@ -25,7 +25,7 @@  LOG_DEFINE_CATEGORY(IPASoftLut)
 namespace ipa::soft::algorithms {
 
 int Lut::init(IPAContext &context,
-	      [[maybe_unused]] const YamlObject &tuningData)
+	      [[maybe_unused]] const ValueNode &tuningData)
 {
 	context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f);
 	return 0;
diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h
index ba8b9021b37e..a3f8fac23b19 100644
--- a/src/ipa/simple/algorithms/lut.h
+++ b/src/ipa/simple/algorithms/lut.h
@@ -19,7 +19,7 @@  public:
 	Lut() = default;
 	~Lut() = default;
 
-	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void queueRequest(typename Module::Context &context,
 			  const uint32_t frame,
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index 57836c73ccfa..6200072b9268 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -120,7 +120,7 @@  int IPASoftSimple::init(const IPASettings &settings,
 		return ret;
 	}
 
-	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
 	if (!data)
 		return -EINVAL;
 
diff --git a/src/libcamera/converter/converter_dw100.cpp b/src/libcamera/converter/converter_dw100.cpp
index 5782cd0b21b7..86b530c13f96 100644
--- a/src/libcamera/converter/converter_dw100.cpp
+++ b/src/libcamera/converter/converter_dw100.cpp
@@ -97,7 +97,7 @@  ConverterDW100Module::createModule(DeviceEnumerator *enumerator)
  * \sa Dw100VertexMap::setDewarpParams()
  * \return 0 if successful, an error code otherwise
  */
-int ConverterDW100Module::init(const YamlObject &params)
+int ConverterDW100Module::init(const ValueNode &params)
 {
 	DewarpParms dp;
 
diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp
index 621008844c74..010c1d8c35d9 100644
--- a/src/libcamera/geometry.cpp
+++ b/src/libcamera/geometry.cpp
@@ -12,7 +12,7 @@ 
 
 #include <libcamera/base/log.h>
 
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 /**
  * \file geometry.h
@@ -933,7 +933,7 @@  std::ostream &operator<<(std::ostream &out, const Rectangle &r)
  */
 template<>
 std::optional<Size>
-YamlObject::Accessor<Size>::get(const YamlObject &obj) const
+ValueNode::Accessor<Size>::get(const ValueNode &obj) const
 {
 	if (obj.type_ != Type::List)
 		return std::nullopt;
diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp
index 99d16e7c38c6..c4999d32d7c7 100644
--- a/src/libcamera/global_configuration.cpp
+++ b/src/libcamera/global_configuration.cpp
@@ -65,7 +65,7 @@  bool GlobalConfiguration::loadFile(const std::filesystem::path &fileName)
 		return true;
 	}
 
-	std::unique_ptr<YamlObject> configuration = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> configuration = YamlParser::parse(file);
 	if (!configuration) {
 		LOG(Configuration, Error)
 			<< "Failed to parse configuration file " << fileName;
@@ -146,7 +146,7 @@  GlobalConfiguration::GlobalConfiguration()
 std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
 	const std::initializer_list<std::string_view> confPath) const
 {
-	const YamlObject *c = &configuration();
+	const ValueNode *c = &configuration();
 	for (auto part : confPath) {
 		c = &(*c)[part];
 		if (!*c)
@@ -237,10 +237,10 @@  unsigned int GlobalConfiguration::version() const
  * This returns the whole configuration stored in the top-level section
  * `%configuration` of the YAML configuration file.
  *
- * The requested part of the configuration can be accessed using \a YamlObject
+ * The requested part of the configuration can be accessed using \a ValueNode
  * methods.
  *
- * \note \a YamlObject type itself shouldn't be used in type declarations to
+ * \note \a ValueNode type itself shouldn't be used in type declarations to
  * avoid trouble if we decide to change the underlying data objects in future.
  *
  * \return The whole configuration section
diff --git a/src/libcamera/matrix.cpp b/src/libcamera/matrix.cpp
index b7c07e896538..4fe210830421 100644
--- a/src/libcamera/matrix.cpp
+++ b/src/libcamera/matrix.cpp
@@ -314,7 +314,7 @@  template bool matrixInvert<double>(Span<const double> data, Span<double> dataOut
  * to the product of the number of rows and columns of the matrix (Rows x
  * Cols). The values shall be stored in row-major order.
  */
-bool matrixValidateYaml(const YamlObject &obj, unsigned int size)
+bool matrixValidateYaml(const ValueNode &obj, unsigned int size)
 {
 	if (!obj.isList())
 		return false;
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index da89aa3714c3..a24c1ec63c96 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -57,8 +57,8 @@  libcamera_internal_sources = files([
     'v4l2_request.cpp',
     'v4l2_subdevice.cpp',
     'v4l2_videodevice.cpp',
+    'value_node.cpp',
     'vector.cpp',
-    'yaml_object.cpp',
     'yaml_parser.cpp',
 ])
 
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index c7e3b185f89b..b2cfcf7395ca 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -443,7 +443,7 @@  int RkISP1CameraData::loadTuningFile(const std::string &path)
 		return ret;
 	}
 
-	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> data = YamlParser::parse(file);
 	if (!data)
 		return -EINVAL;
 
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index 684438bd5fb7..a2929bca2a03 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -1116,7 +1116,7 @@  int CameraData::loadPipelineConfiguration()
 
 	LOG(RPI, Info) << "Using configuration file '" << filename << "'";
 
-	std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> root = YamlParser::parse(file);
 	if (!root) {
 		LOG(RPI, Warning) << "Failed to parse configuration file, using defaults";
 		return 0;
@@ -1129,7 +1129,7 @@  int CameraData::loadPipelineConfiguration()
 		return 0;
 	}
 
-	const YamlObject &phConfig = (*root)["pipeline_handler"];
+	const ValueNode &phConfig = (*root)["pipeline_handler"];
 
 	if (phConfig.contains("disable_startup_frame_drops"))
 		LOG(RPI, Warning)
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h
index c69a690f580c..d959f27705ef 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.h
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h
@@ -26,7 +26,7 @@ 
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/request.h"
 #include "libcamera/internal/v4l2_videodevice.h"
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include <libcamera/ipa/raspberrypi_ipa_interface.h>
 #include <libcamera/ipa/raspberrypi_ipa_proxy.h>
@@ -96,7 +96,7 @@  public:
 	virtual V4L2VideoDevice::Formats rawFormats() const = 0;
 	virtual V4L2VideoDevice *frontendDevice() = 0;
 
-	virtual int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) = 0;
+	virtual int platformPipelineConfigure(const std::unique_ptr<ValueNode> &root) = 0;
 
 	std::unique_ptr<ipa::RPi::IPAProxyRPi> ipa_;
 
diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp
index 7bcba32b9b58..f4133de47f23 100644
--- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp
+++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp
@@ -743,7 +743,7 @@  public:
 	CameraConfiguration::Status
 	platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override;
 
-	int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override;
+	int platformPipelineConfigure(const std::unique_ptr<ValueNode> &root) override;
 
 	void platformStart() override;
 	void platformStop() override;
@@ -1333,7 +1333,7 @@  PiSPCameraData::platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const
 	return status;
 }
 
-int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root)
+int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<ValueNode> &root)
 {
 	config_ = {
 		.numCfeConfigStatsBuffers = 12,
@@ -1358,7 +1358,7 @@  int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject>
 		return -EINVAL;
 	}
 
-	const YamlObject &phConfig = (*root)["pipeline_handler"];
+	const ValueNode &phConfig = (*root)["pipeline_handler"];
 	config_.numCfeConfigStatsBuffers =
 		phConfig["num_cfe_config_stats_buffers"].get<unsigned int>(config_.numCfeConfigStatsBuffers);
 	config_.numCfeConfigQueue =
diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
index 8a80439e9082..b5d4a2f670a9 100644
--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp
+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
@@ -69,7 +69,7 @@  public:
 
 	CameraConfiguration::Status platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override;
 
-	int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override;
+	int platformPipelineConfigure(const std::unique_ptr<ValueNode> &root) override;
 
 	void platformStart() override;
 	void platformStop() override;
@@ -498,7 +498,7 @@  CameraConfiguration::Status Vc4CameraData::platformValidate(RPi::RPiCameraConfig
 	return status;
 }
 
-int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root)
+int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<ValueNode> &root)
 {
 	config_ = {
 		.minUnicamBuffers = 2,
@@ -521,7 +521,7 @@  int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &
 		return -EINVAL;
 	}
 
-	const YamlObject &phConfig = (*root)["pipeline_handler"];
+	const ValueNode &phConfig = (*root)["pipeline_handler"];
 	config_.minUnicamBuffers =
 		phConfig["min_unicam_buffers"].get<unsigned int>(config_.minUnicamBuffers);
 	config_.minTotalUnicamBuffers =
diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md
index a9f39c15172b..0a02a9ea44b3 100644
--- a/src/libcamera/pipeline/virtual/README.md
+++ b/src/libcamera/pipeline/virtual/README.md
@@ -48,7 +48,7 @@  in Virtual Pipeline Handler. `parseConfigFile()` is exposed to use in
 Virtual Pipeline Handler.
 
 This is the procedure of the Parser class:
-1. `parseConfigFile()` parses the config file to `YamlObject` using `YamlParser::parse()`.
+1. `parseConfigFile()` parses the config file to `ValueNode` using `YamlParser::parse()`.
     - Parse the top level of config file which are the camera ids and look into
       each camera properties.
 2. For each camera, `parseCameraConfigData()` returns a camera with the configuration.
diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp
index fdc729509371..5169fd39bc38 100644
--- a/src/libcamera/pipeline/virtual/config_parser.cpp
+++ b/src/libcamera/pipeline/virtual/config_parser.cpp
@@ -29,7 +29,7 @@  ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
 {
 	std::vector<std::unique_ptr<VirtualCameraData>> configurations;
 
-	std::unique_ptr<YamlObject> cameras = YamlParser::parse(file);
+	std::unique_ptr<ValueNode> cameras = YamlParser::parse(file);
 	if (!cameras) {
 		LOG(Virtual, Error) << "Failed to parse config file.";
 		return configurations;
@@ -72,7 +72,7 @@  ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
 }
 
 std::unique_ptr<VirtualCameraData>
-ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData,
+ConfigParser::parseCameraConfigData(const ValueNode &cameraConfigData,
 				    PipelineHandler *pipe)
 {
 	std::vector<VirtualCameraData::Resolution> resolutions;
@@ -94,13 +94,13 @@  ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData,
 	return data;
 }
 
-int ConfigParser::parseSupportedFormats(const YamlObject &cameraConfigData,
+int ConfigParser::parseSupportedFormats(const ValueNode &cameraConfigData,
 					std::vector<VirtualCameraData::Resolution> *resolutions)
 {
 	if (cameraConfigData.contains("supported_formats")) {
-		const YamlObject &supportedResolutions = cameraConfigData["supported_formats"];
+		const ValueNode &supportedResolutions = cameraConfigData["supported_formats"];
 
-		for (const YamlObject &supportedResolution : supportedResolutions.asList()) {
+		for (const ValueNode &supportedResolution : supportedResolutions.asList()) {
 			unsigned int width = supportedResolution["width"].get<unsigned int>(1920);
 			unsigned int height = supportedResolution["height"].get<unsigned int>(1080);
 			if (width == 0 || height == 0) {
@@ -152,7 +152,7 @@  int ConfigParser::parseSupportedFormats(const YamlObject &cameraConfigData,
 	return 0;
 }
 
-int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data)
+int ConfigParser::parseFrameGenerator(const ValueNode &cameraConfigData, VirtualCameraData *data)
 {
 	const std::string testPatternKey = "test_pattern";
 	const std::string framesKey = "frames";
@@ -178,7 +178,7 @@  int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, Virtua
 		return 0;
 	}
 
-	const YamlObject &frames = cameraConfigData[framesKey];
+	const ValueNode &frames = cameraConfigData[framesKey];
 
 	/* When there is no frames provided in the config file, use color bar test pattern */
 	if (!frames) {
@@ -231,7 +231,7 @@  int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, Virtua
 	return 0;
 }
 
-int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data)
+int ConfigParser::parseLocation(const ValueNode &cameraConfigData, VirtualCameraData *data)
 {
 	/* Default value is properties::CameraLocationFront */
 	int32_t location = properties::CameraLocationFront;
@@ -252,7 +252,7 @@  int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCamer
 	return 0;
 }
 
-int ConfigParser::parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data)
+int ConfigParser::parseModel(const ValueNode &cameraConfigData, VirtualCameraData *data)
 {
 	std::string model = cameraConfigData["model"].get<std::string>("Unknown");
 
diff --git a/src/libcamera/pipeline/virtual/config_parser.h b/src/libcamera/pipeline/virtual/config_parser.h
index f696d8862897..97d6dffa31ec 100644
--- a/src/libcamera/pipeline/virtual/config_parser.h
+++ b/src/libcamera/pipeline/virtual/config_parser.h
@@ -13,7 +13,7 @@ 
 #include <libcamera/base/file.h>
 
 #include "libcamera/internal/pipeline_handler.h"
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "virtual.h"
 
@@ -27,13 +27,13 @@  public:
 
 private:
 	std::unique_ptr<VirtualCameraData>
-	parseCameraConfigData(const YamlObject &cameraConfigData, PipelineHandler *pipe);
+	parseCameraConfigData(const ValueNode &cameraConfigData, PipelineHandler *pipe);
 
-	int parseSupportedFormats(const YamlObject &cameraConfigData,
+	int parseSupportedFormats(const ValueNode &cameraConfigData,
 				  std::vector<VirtualCameraData::Resolution> *resolutions);
-	int parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data);
-	int parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data);
-	int parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data);
+	int parseFrameGenerator(const ValueNode &cameraConfigData, VirtualCameraData *data);
+	int parseLocation(const ValueNode &cameraConfigData, VirtualCameraData *data);
+	int parseModel(const ValueNode &cameraConfigData, VirtualCameraData *data);
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
index b032db54ded4..90f2110ade8c 100644
--- a/src/libcamera/pipeline/virtual/virtual.cpp
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -36,7 +36,7 @@ 
 #include "libcamera/internal/framebuffer.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/request.h"
-#include "libcamera/internal/yaml_object.h"
+#include "libcamera/internal/value_node.h"
 
 #include "pipeline/virtual/config_parser.h"
 
diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp
new file mode 100644
index 000000000000..7374e7bdde8c
--- /dev/null
+++ b/src/libcamera/value_node.cpp
@@ -0,0 +1,484 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ * Copyright (C) 2025, Ideas on Board.
+ *
+ * Data structure to manage tree of values
+ */
+
+#include "libcamera/internal/value_node.h"
+
+#include <charconv>
+#include <errno.h>
+#include <string>
+#include <vector>
+
+#include <libcamera/base/utils.h>
+
+/**
+ * \file value_node.h
+ * \brief Data structure to manage tree of values
+ */
+
+namespace libcamera {
+
+namespace {
+
+/* Empty static ValueNode as a safe result for invalid operations */
+static const ValueNode empty;
+
+} /* namespace */
+
+/**
+ * \class ValueNode
+ * \brief A class representing a tree structure of values
+ *
+ * The ValueNode class is designed to model a tree of values. Each node in the
+ * tree is represented by a ValueNode instance. Intermediate nodes store
+ * children either as an ordered list (sequence) or a string-indexed dictionary
+ * (mapping). Leaf nodes can be empty or store a string value.
+ */
+
+ValueNode::ValueNode()
+	: type_(Type::Empty)
+{
+}
+
+ValueNode::~ValueNode() = default;
+
+/**
+ * \fn ValueNode::isValue()
+ * \brief Return whether the ValueNode is a value
+ *
+ * \return True if the ValueNode is a value, false otherwise
+ */
+
+/**
+ * \fn ValueNode::isList()
+ * \brief Return whether the ValueNode is a list
+ *
+ * \return True if the ValueNode is a list, false otherwise
+ */
+
+/**
+ * \fn ValueNode::isDictionary()
+ * \brief Return whether the ValueNode is a dictionary
+ *
+ * \return True if the ValueNode is a dictionary, false otherwise
+ */
+
+/**
+ * \fn ValueNode::isEmpty()
+ * \brief Return whether the ValueNode is an empty
+ *
+ * \return True if the ValueNode is empty, false otherwise
+ */
+
+/**
+ * \fn ValueNode::operator bool()
+ * \brief Return whether the ValueNode is a non-empty
+ *
+ * \return False if the ValueNode is empty, true otherwise
+ */
+
+/**
+ * \fn ValueNode::size()
+ * \brief Retrieve the number of elements in a dictionary or list ValueNode
+ *
+ * This function retrieves the size of the ValueNode, defined as the number of
+ * child elements it contains. Only ValueNode instances of Dictionary or List
+ * types have a size, calling this function on other types of instances is
+ * invalid and results in undefined behaviour.
+ *
+ * \return The size of the ValueNode
+ */
+std::size_t ValueNode::size() const
+{
+	switch (type_) {
+	case Type::Dictionary:
+	case Type::List:
+		return list_.size();
+	default:
+		return 0;
+	}
+}
+
+/**
+ * \fn template<typename T> ValueNode::get<T>() const
+ * \brief Parse the ValueNode as a \a T value
+ * \tparam T Type of the value
+ *
+ * This function parses the value of the ValueNode as a \a T object, and
+ * returns the value. If parsing fails (usually because the ValueNode doesn't
+ * store a \a T value), std::nullopt is returned.
+ *
+ * If the type \a T is an std::vector, the ValueNode will be parsed as a list
+ * of values.
+ *
+ * \return The ValueNode value, or std::nullopt if parsing failed
+ */
+
+/**
+ * \fn template<typename T, typename U> ValueNode::get<T>(U &&defaultValue) const
+ * \brief Parse the ValueNode as a \a T value
+ * \tparam T Type of the value
+ * \tparam U Type of the default value
+ * \param[in] defaultValue The default value when failing to parse
+ *
+ * This function parses the value of the ValueNode as a \a T object, and
+ * returns the value. If parsing fails (usually because the ValueNode doesn't
+ * store a \a T value), the \a defaultValue is returned. Type \a U must be
+ * convertible to type \a T.
+ *
+ * Unlike the get() function, this overload does not support std::vector for the
+ * type \a T.
+ *
+ * \return The ValueNode value, or \a defaultValue if parsing failed
+ */
+
+/**
+ * \fn template<typename T> ValueNode::set<T>(T &&value)
+ * \brief Set the value of a ValueNode
+ * \tparam T Type of the value
+ * \param[in] value The value
+ *
+ * This function sets the value stored in a ValueNode to \a value. The value is
+ * converted to a string in an implementation-specific way that guarantees that
+ * subsequent calls to get<T>() will return the same value.
+ *
+ * Values can only be set on ValueNode of Type::Value type or empty ValueNode.
+ * Attempting to set a value on a node of type Type::Dict or Type::List does not
+ * modify the ValueNode.
+ */
+
+#ifndef __DOXYGEN__
+
+template<>
+std::optional<bool>
+ValueNode::Accessor<bool>::get(const ValueNode &obj) const
+{
+	if (obj.type_ != Type::Value)
+		return std::nullopt;
+
+	if (obj.value_ == "true")
+		return true;
+	else if (obj.value_ == "false")
+		return false;
+
+	return std::nullopt;
+}
+
+template<>
+void ValueNode::Accessor<bool>::set(ValueNode &obj, bool value)
+{
+	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
+		return;
+
+	obj.type_ = Type::Value;
+	obj.value_ = value ? "true" : "false";
+}
+
+template<typename T>
+struct ValueNode::Accessor<T, std::enable_if_t<
+	std::is_same_v<int8_t, T> ||
+	std::is_same_v<uint8_t, T> ||
+	std::is_same_v<int16_t, T> ||
+	std::is_same_v<uint16_t, T> ||
+	std::is_same_v<int32_t, T> ||
+	std::is_same_v<uint32_t, T>>>
+{
+	std::optional<T> get(const ValueNode &obj) const
+	{
+		if (obj.type_ != Type::Value)
+			return std::nullopt;
+
+		const std::string &str = obj.value_;
+		T value;
+
+		auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(),
+						 value);
+		if (ptr != str.data() + str.size() || ec != std::errc())
+			return std::nullopt;
+
+		return value;
+	}
+
+	void set(ValueNode &obj, T value)
+	{
+		if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
+			return;
+
+		obj.type_ = Type::Value;
+		obj.value_ = std::to_string(value);
+	}
+};
+
+template struct ValueNode::Accessor<int8_t>;
+template struct ValueNode::Accessor<uint8_t>;
+template struct ValueNode::Accessor<int16_t>;
+template struct ValueNode::Accessor<uint16_t>;
+template struct ValueNode::Accessor<int32_t>;
+template struct ValueNode::Accessor<uint32_t>;
+
+template<>
+std::optional<float>
+ValueNode::Accessor<float>::get(const ValueNode &obj) const
+{
+	return obj.get<double>();
+}
+
+template<>
+void ValueNode::Accessor<float>::set(ValueNode &obj, float value)
+{
+	obj.set<double>(std::forward<float>(value));
+}
+
+template<>
+std::optional<double>
+ValueNode::Accessor<double>::get(const ValueNode &obj) const
+{
+	if (obj.type_ != Type::Value)
+		return std::nullopt;
+
+	if (obj.value_.empty())
+		return std::nullopt;
+
+	char *end;
+
+	errno = 0;
+	double value = utils::strtod(obj.value_.c_str(), &end);
+
+	if ('\0' != *end || errno == ERANGE)
+		return std::nullopt;
+
+	return value;
+}
+
+template<>
+void ValueNode::Accessor<double>::set(ValueNode &obj, double value)
+{
+	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
+		return;
+
+	obj.type_ = Type::Value;
+	obj.value_ = std::to_string(value);
+}
+
+template<>
+std::optional<std::string>
+ValueNode::Accessor<std::string>::get(const ValueNode &obj) const
+{
+	if (obj.type_ != Type::Value)
+		return std::nullopt;
+
+	return obj.value_;
+}
+
+template<>
+void ValueNode::Accessor<std::string>::set(ValueNode &obj, std::string value)
+{
+	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
+		return;
+
+	obj.type_ = Type::Value;
+	obj.value_ = std::move(value);
+}
+
+template<typename T>
+struct ValueNode::Accessor<std::vector<T>, std::enable_if_t<
+	std::is_same_v<bool, T> ||
+	std::is_same_v<float, T> ||
+	std::is_same_v<double, T> ||
+	std::is_same_v<int8_t, T> ||
+	std::is_same_v<uint8_t, T> ||
+	std::is_same_v<int16_t, T> ||
+	std::is_same_v<uint16_t, T> ||
+	std::is_same_v<int32_t, T> ||
+	std::is_same_v<uint32_t, T> ||
+	std::is_same_v<std::string, T>>>
+{
+	std::optional<std::vector<T>> get(const ValueNode &obj) const
+	{
+		if (obj.type_ != Type::List)
+			return std::nullopt;
+
+		std::vector<T> values;
+		values.reserve(obj.list_.size());
+
+		for (const ValueNode &entry : obj.asList()) {
+			const auto value = entry.get<T>();
+			if (!value)
+				return std::nullopt;
+			values.emplace_back(*value);
+		}
+
+		return values;
+	}
+};
+
+template struct ValueNode::Accessor<std::vector<bool>>;
+template struct ValueNode::Accessor<std::vector<float>>;
+template struct ValueNode::Accessor<std::vector<double>>;
+template struct ValueNode::Accessor<std::vector<int8_t>>;
+template struct ValueNode::Accessor<std::vector<uint8_t>>;
+template struct ValueNode::Accessor<std::vector<int16_t>>;
+template struct ValueNode::Accessor<std::vector<uint16_t>>;
+template struct ValueNode::Accessor<std::vector<int32_t>>;
+template struct ValueNode::Accessor<std::vector<uint32_t>>;
+template struct ValueNode::Accessor<std::vector<std::string>>;
+#endif /* __DOXYGEN__ */
+
+/**
+ * \fn ValueNode::asDict() const
+ * \brief Wrap a dictionary ValueNode in an adapter that exposes iterators
+ *
+ * The ValueNode class doesn't directly implement iterators, as the iterator
+ * type depends on whether the node is a Dictionary or List. This function wraps
+ * a ValueNode of Dictionary type into an adapter that exposes iterators, as
+ * well as begin() and end() functions, allowing usage of range-based for loops
+ * with ValueNode. As mappings are not ordered, the iteration order is not
+ * specified.
+ *
+ * The iterator's value_type is a
+ * <em>std::pair<const std::string &, const \ref ValueNode &></em>.
+ *
+ * If the ValueNode is not of Dictionary type, the returned adapter operates
+ * as an empty container.
+ *
+ * \return An adapter of unspecified type compatible with range-based for loops
+ */
+
+/**
+ * \fn ValueNode::asList() const
+ * \brief Wrap a list ValueNode in an adapter that exposes iterators
+ *
+ * The ValueNode class doesn't directly implement iterators, as the iterator
+ * type depends on whether the node is a Dictionary or List. This function wraps
+ * a ValueNode of List type into an adapter that exposes iterators, as well as
+ * begin() and end() functions, allowing usage of range-based for loops with
+ * ValueNode. As lists are ordered, the iteration order is matches the order in
+ * which child nodes have been added.
+ *
+ * The iterator's value_type is a <em>const ValueNode &</em>.
+ *
+ * If the ValueNode is not of List type, the returned adapter operates as an
+ * empty container.
+ *
+ * \return An adapter of unspecified type compatible with range-based for loops
+ */
+
+/**
+ * \brief Retrieve the element from list ValueNode by index
+ * \param[in] index The element index
+ *
+ * This function retrieves an element of the ValueNode. Only ValueNode
+ * instances of List type associate elements with index, calling this function
+ * on other types of instances or with an invalid index results in an empty
+ * node.
+ *
+ * \return The ValueNode as an element of the list
+ */
+const ValueNode &ValueNode::operator[](std::size_t index) const
+{
+	if (type_ != Type::List || index >= size())
+		return empty;
+
+	return *list_[index].value;
+}
+
+/**
+ * \brief Check if an element of a dictionary exists
+ * \param[in] key The element key
+ *
+ * This function check if the ValueNode contains an element for the given \a
+ * key. Only ValueNode instances of Dictionary type associate elements with
+ * keys, calling this function on other types of instances is invalid and
+ * results in undefined behaviour.
+ *
+ * \return True if an element exists, false otherwise
+ */
+bool ValueNode::contains(std::string_view key) const
+{
+	return dictionary_.find(key) != dictionary_.end();
+}
+
+/**
+ * \brief Retrieve a member by key from the dictionary
+ * \param[in] key The element key
+ *
+ * This function retrieves a member of a ValueNode by \a key. Only ValueNode
+ * instances of Dictionary type associate elements with keys, calling this
+ * function on other types of instances or with a nonexistent key results in an
+ * empty node.
+ *
+ * \return The ValueNode corresponding to the \a key member
+ */
+const ValueNode &ValueNode::operator[](std::string_view key) const
+{
+	if (type_ != Type::Dictionary)
+		return empty;
+
+	auto iter = dictionary_.find(key);
+	if (iter == dictionary_.end())
+		return empty;
+
+	return *iter->second;
+}
+
+/**
+ * \brief Add a child node to a list
+ * \param[in] child The child node
+ *
+ * Append the \a child node as the last element of this node's children list.
+ * This node must be empty, in which case it is converted to the Type::List
+ * type, or be a list. Otherwise, the \a child is discarded and the function
+ * returns a nullptr.
+ *
+ * \return A pointer to the \a child node if successfully added, nullptr
+ * otherwise
+ */
+ValueNode *ValueNode::add(std::unique_ptr<ValueNode> child)
+{
+	if (type_ == Type::Empty)
+		type_ = Type::List;
+
+	if (type_ != Type::List)
+		return nullptr;
+
+	Value &elem = list_.emplace_back(std::string{}, std::move(child));
+	return elem.value.get();
+}
+
+/**
+ * \brief Add a child node to a dictionary
+ * \param[in] key The dictionary key
+ * \param[in] child The child node
+ *
+ * Add the \a child node with the given \a key to this node's children. This
+ * node must be empty, in which case it is converted to the Type::Dictionary
+ * type, or be a dictionary. Otherwise, the \a child is discarded and the
+ * function returns a nullptr.
+ *
+ * Keys are unique. If a child with the same \a key already exist, the \a child
+ * is discarded and the function returns a nullptr.
+ *
+ * \return A pointer to the \a child node if successfully added, nullptr
+ * otherwise
+ */
+ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)
+{
+	if (type_ == Type::Empty)
+		type_ = Type::Dictionary;
+
+	if (type_ != Type::Dictionary)
+		return nullptr;
+
+	if (dictionary_.find(key) != dictionary_.end())
+		return nullptr;
+
+	Value &elem = list_.emplace_back(std::move(key), std::move(child));
+	dictionary_.emplace(elem.key, elem.value.get());
+	return elem.value.get();
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp
index 4dad1b9001c5..86b9f9bb6d5e 100644
--- a/src/libcamera/vector.cpp
+++ b/src/libcamera/vector.cpp
@@ -337,7 +337,7 @@  LOG_DEFINE_CATEGORY(Vector)
  */
 
 #ifndef __DOXYGEN__
-bool vectorValidateYaml(const YamlObject &obj, unsigned int size)
+bool vectorValidateYaml(const ValueNode &obj, unsigned int size)
 {
 	if (!obj.isList())
 		return false;
diff --git a/src/libcamera/yaml_object.cpp b/src/libcamera/yaml_object.cpp
deleted file mode 100644
index 4328ccbff8f1..000000000000
--- a/src/libcamera/yaml_object.cpp
+++ /dev/null
@@ -1,480 +0,0 @@ 
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2022, Google Inc.
- * Copyright (C) 2025, Ideas on Board.
- *
- * libcamera YAML object
- */
-
-#include "libcamera/internal/yaml_object.h"
-
-#include <charconv>
-#include <errno.h>
-#include <string>
-#include <vector>
-
-#include <libcamera/base/utils.h>
-
-/**
- * \file yaml_object.h
- * \brief YAML objects
- */
-
-namespace libcamera {
-
-namespace {
-
-/* Empty static YamlObject as a safe result for invalid operations */
-static const YamlObject empty;
-
-} /* namespace */
-
-/**
- * \class YamlObject
- * \brief A class representing the tree structure of the YAML content
- *
- * The YamlObject class represents the tree structure of YAML content. A
- * YamlObject can be empty, a dictionary or list of YamlObjects, or a value if a
- * tree leaf.
- */
-
-YamlObject::YamlObject()
-	: type_(Type::Empty)
-{
-}
-
-YamlObject::~YamlObject() = default;
-
-/**
- * \fn YamlObject::isValue()
- * \brief Return whether the YamlObject is a value
- *
- * \return True if the YamlObject is a value, false otherwise
- */
-
-/**
- * \fn YamlObject::isList()
- * \brief Return whether the YamlObject is a list
- *
- * \return True if the YamlObject is a list, false otherwise
- */
-
-/**
- * \fn YamlObject::isDictionary()
- * \brief Return whether the YamlObject is a dictionary
- *
- * \return True if the YamlObject is a dictionary, false otherwise
- */
-
-/**
- * \fn YamlObject::isEmpty()
- * \brief Return whether the YamlObject is an empty
- *
- * \return True if the YamlObject is empty, false otherwise
- */
-
-/**
- * \fn YamlObject::operator bool()
- * \brief Return whether the YamlObject is a non-empty
- *
- * \return False if the YamlObject is empty, true otherwise
- */
-
-/**
- * \brief Retrieve the number of elements in a dictionary or list YamlObject
- *
- * This function retrieves the size of the YamlObject, defined as the number of
- * child elements it contains. Only YamlObject instances of Dictionary or List
- * types have a size, calling this function on other types of instances is
- * invalid and results in undefined behaviour.
- *
- * \return The size of the YamlObject
- */
-std::size_t YamlObject::size() const
-{
-	switch (type_) {
-	case Type::Dictionary:
-	case Type::List:
-		return list_.size();
-	default:
-		return 0;
-	}
-}
-
-/**
- * \fn template<typename T> YamlObject::get<T>() const
- * \brief Parse the YamlObject as a \a T value
- * \tparam T Type of the value
- *
- * This function parses the value of the YamlObject as a \a T object, and
- * returns the value. If parsing fails (usually because the YamlObject doesn't
- * store a \a T value), std::nullopt is returned.
- *
- * If the type \a T is an std::vector, the YamlObject will be parsed as a list
- * of values.
- *
- * \return The YamlObject value, or std::nullopt if parsing failed
- */
-
-/**
- * \fn template<typename T, typename U> YamlObject::get<T>(U &&defaultValue) const
- * \brief Parse the YamlObject as a \a T value
- * \tparam T Type of the value
- * \tparam U Type of the default value
- * \param[in] defaultValue The default value when failing to parse
- *
- * This function parses the value of the YamlObject as a \a T object, and
- * returns the value. If parsing fails (usually because the YamlObject doesn't
- * store a \a T value), the \a defaultValue is returned. Type \a U must be
- * convertible to type \a T.
- *
- * Unlike the get() function, this overload does not support std::vector for the
- * type \a T.
- *
- * \return The YamlObject value, or \a defaultValue if parsing failed
- */
-
-/**
- * \fn template<typename T> YamlObject::set<T>(T &&value)
- * \brief Set the value of a YamlObject
- * \tparam T Type of the value
- * \param[in] value The value
- *
- * This function sets the value stored in a YamlObject to \a value. The value is
- * converted to a string in an implementation-specific way that guarantees that
- * subsequent calls to get<T>() will return the same value.
- *
- * Values can only be set on YamlObject of Type::Value type or empty YamlObject.
- * Attempting to set a value on an object of type Type::Dict or Type::List does
- * not modify the YamlObject.
- */
-
-#ifndef __DOXYGEN__
-
-template<>
-std::optional<bool>
-YamlObject::Accessor<bool>::get(const YamlObject &obj) const
-{
-	if (obj.type_ != Type::Value)
-		return std::nullopt;
-
-	if (obj.value_ == "true")
-		return true;
-	else if (obj.value_ == "false")
-		return false;
-
-	return std::nullopt;
-}
-
-template<>
-void YamlObject::Accessor<bool>::set(YamlObject &obj, bool value)
-{
-	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
-		return;
-
-	obj.type_ = Type::Value;
-	obj.value_ = value ? "true" : "false";
-}
-
-template<typename T>
-struct YamlObject::Accessor<T, std::enable_if_t<
-	std::is_same_v<int8_t, T> ||
-	std::is_same_v<uint8_t, T> ||
-	std::is_same_v<int16_t, T> ||
-	std::is_same_v<uint16_t, T> ||
-	std::is_same_v<int32_t, T> ||
-	std::is_same_v<uint32_t, T>>>
-{
-	std::optional<T> get(const YamlObject &obj) const
-	{
-		if (obj.type_ != Type::Value)
-			return std::nullopt;
-
-		const std::string &str = obj.value_;
-		T value;
-
-		auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(),
-						 value);
-		if (ptr != str.data() + str.size() || ec != std::errc())
-			return std::nullopt;
-
-		return value;
-	}
-
-	void set(YamlObject &obj, T value)
-	{
-		if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
-			return;
-
-		obj.type_ = Type::Value;
-		obj.value_ = std::to_string(value);
-	}
-};
-
-template struct YamlObject::Accessor<int8_t>;
-template struct YamlObject::Accessor<uint8_t>;
-template struct YamlObject::Accessor<int16_t>;
-template struct YamlObject::Accessor<uint16_t>;
-template struct YamlObject::Accessor<int32_t>;
-template struct YamlObject::Accessor<uint32_t>;
-
-template<>
-std::optional<float>
-YamlObject::Accessor<float>::get(const YamlObject &obj) const
-{
-	return obj.get<double>();
-}
-
-template<>
-void YamlObject::Accessor<float>::set(YamlObject &obj, float value)
-{
-	obj.set<double>(std::forward<float>(value));
-}
-
-template<>
-std::optional<double>
-YamlObject::Accessor<double>::get(const YamlObject &obj) const
-{
-	if (obj.type_ != Type::Value)
-		return std::nullopt;
-
-	if (obj.value_.empty())
-		return std::nullopt;
-
-	char *end;
-
-	errno = 0;
-	double value = utils::strtod(obj.value_.c_str(), &end);
-
-	if ('\0' != *end || errno == ERANGE)
-		return std::nullopt;
-
-	return value;
-}
-
-template<>
-void YamlObject::Accessor<double>::set(YamlObject &obj, double value)
-{
-	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
-		return;
-
-	obj.type_ = Type::Value;
-	obj.value_ = std::to_string(value);
-}
-
-template<>
-std::optional<std::string>
-YamlObject::Accessor<std::string>::get(const YamlObject &obj) const
-{
-	if (obj.type_ != Type::Value)
-		return std::nullopt;
-
-	return obj.value_;
-}
-
-template<>
-void YamlObject::Accessor<std::string>::set(YamlObject &obj, std::string value)
-{
-	if (obj.type_ != Type::Empty && obj.type_ != Type::Value)
-		return;
-
-	obj.type_ = Type::Value;
-	obj.value_ = std::move(value);
-}
-
-template<typename T>
-struct YamlObject::Accessor<std::vector<T>, std::enable_if_t<
-	std::is_same_v<bool, T> ||
-	std::is_same_v<float, T> ||
-	std::is_same_v<double, T> ||
-	std::is_same_v<int8_t, T> ||
-	std::is_same_v<uint8_t, T> ||
-	std::is_same_v<int16_t, T> ||
-	std::is_same_v<uint16_t, T> ||
-	std::is_same_v<int32_t, T> ||
-	std::is_same_v<uint32_t, T> ||
-	std::is_same_v<std::string, T>>>
-{
-	std::optional<std::vector<T>> get(const YamlObject &obj) const
-	{
-		if (obj.type_ != Type::List)
-			return std::nullopt;
-
-		std::vector<T> values;
-		values.reserve(obj.list_.size());
-
-		for (const YamlObject &entry : obj.asList()) {
-			const auto value = entry.get<T>();
-			if (!value)
-				return std::nullopt;
-			values.emplace_back(*value);
-		}
-
-		return values;
-	}
-};
-
-template struct YamlObject::Accessor<std::vector<bool>>;
-template struct YamlObject::Accessor<std::vector<float>>;
-template struct YamlObject::Accessor<std::vector<double>>;
-template struct YamlObject::Accessor<std::vector<int8_t>>;
-template struct YamlObject::Accessor<std::vector<uint8_t>>;
-template struct YamlObject::Accessor<std::vector<int16_t>>;
-template struct YamlObject::Accessor<std::vector<uint16_t>>;
-template struct YamlObject::Accessor<std::vector<int32_t>>;
-template struct YamlObject::Accessor<std::vector<uint32_t>>;
-template struct YamlObject::Accessor<std::vector<std::string>>;
-#endif /* __DOXYGEN__ */
-
-/**
- * \fn YamlObject::asDict() const
- * \brief Wrap a dictionary YamlObject in an adapter that exposes iterators
- *
- * The YamlObject class doesn't directly implement iterators, as the iterator
- * type depends on whether the object is a Dictionary or List. This function
- * wraps a YamlObject of Dictionary type into an adapter that exposes
- * iterators, as well as begin() and end() functions, allowing usage of
- * range-based for loops with YamlObject. As YAML mappings are not ordered, the
- * iteration order is not specified.
- *
- * The iterator's value_type is a
- * <em>std::pair<const std::string &, const \ref YamlObject &></em>.
- *
- * If the YamlObject is not of Dictionary type, the returned adapter operates
- * as an empty container.
- *
- * \return An adapter of unspecified type compatible with range-based for loops
- */
-
-/**
- * \fn YamlObject::asList() const
- * \brief Wrap a list YamlObject in an adapter that exposes iterators
- *
- * The YamlObject class doesn't directly implement iterators, as the iterator
- * type depends on whether the object is a Dictionary or List. This function
- * wraps a YamlObject of List type into an adapter that exposes iterators, as
- * well as begin() and end() functions, allowing usage of range-based for loops
- * with YamlObject. As YAML lists are ordered, the iteration order is identical
- * to the list order in the YAML data.
- *
- * The iterator's value_type is a <em>const YamlObject &</em>.
- *
- * If the YamlObject is not of List type, the returned adapter operates as an
- * empty container.
- *
- * \return An adapter of unspecified type compatible with range-based for loops
- */
-
-/**
- * \brief Retrieve the element from list YamlObject by index
- * \param[in] index The element index
- *
- * This function retrieves an element of the YamlObject. Only YamlObject
- * instances of List type associate elements with index, calling this function
- * on other types of instances or with an invalid index results in an empty
- * object.
- *
- * \return The YamlObject as an element of the list
- */
-const YamlObject &YamlObject::operator[](std::size_t index) const
-{
-	if (type_ != Type::List || index >= size())
-		return empty;
-
-	return *list_[index].value;
-}
-
-/**
- * \brief Check if an element of a dictionary exists
- * \param[in] key The element key
- *
- * This function check if the YamlObject contains an element for the given \a
- * key. Only YamlObject instances of Dictionary type associate elements with
- * keys, calling this function on other types of instances is invalid and
- * results in undefined behaviour.
- *
- * \return True if an element exists, false otherwise
- */
-bool YamlObject::contains(std::string_view key) const
-{
-	return dictionary_.find(key) != dictionary_.end();
-}
-
-/**
- * \brief Retrieve a member by key from the dictionary
- * \param[in] key The element key
- *
- * This function retrieves a member of a YamlObject by \a key. Only YamlObject
- * instances of Dictionary type associate elements with keys, calling this
- * function on other types of instances or with a nonexistent key results in an
- * empty object.
- *
- * \return The YamlObject corresponding to the \a key member
- */
-const YamlObject &YamlObject::operator[](std::string_view key) const
-{
-	if (type_ != Type::Dictionary)
-		return empty;
-
-	auto iter = dictionary_.find(key);
-	if (iter == dictionary_.end())
-		return empty;
-
-	return *iter->second;
-}
-
-/**
- * \brief Add a child object to a list
- * \param[in] child The child object
- *
- * Append the \a child node as the last element of this node's children list.
- * This node must be empty, in which case it is converted to the Type::List
- * type, or be a list. Otherwise, the \a child is discarded and the function
- * returns a nullptr.
- *
- * \return The child object if successfully added, nullptr otherwise
- */
-YamlObject *YamlObject::add(std::unique_ptr<YamlObject> child)
-{
-	if (type_ == Type::Empty)
-		type_ = Type::List;
-
-	if (type_ != Type::List)
-		return nullptr;
-
-	Value &elem = list_.emplace_back(std::string{}, std::move(child));
-	return elem.value.get();
-}
-
-/**
- * \brief Add a child object to a dictionary
- * \param[in] key The dictionary key
- * \param[in] child The child object
- *
- * Add the \a child node with the given \a key to this node's children. This
- * node must be empty, in which case it is converted to the Type::Dictionary
- * type, or be a dictionary. Otherwise, the \a child is discarded and the
- * function returns a nullptr.
- *
- * Keys are unique. If a child with the same \a key already exist, the \a child
- * is discarded and the function returns a nullptr.
- *
- * \return The child object if successfully added, nullptr otherwise
- */
-YamlObject *YamlObject::add(std::string key, std::unique_ptr<YamlObject> child)
-{
-	if (type_ == Type::Empty)
-		type_ = Type::Dictionary;
-
-	if (type_ != Type::Dictionary)
-		return nullptr;
-
-	if (dictionary_.find(key) != dictionary_.end())
-		return nullptr;
-
-	Value &elem = list_.emplace_back(std::move(key), std::move(child));
-	dictionary_.emplace(elem.key, elem.value.get());
-	return elem.value.get();
-}
-
-} /* namespace libcamera */
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index d83df0fb9597..b44818319845 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -35,7 +35,7 @@  public:
 	~YamlParserContext();
 
 	int init(File &file);
-	int parseContent(YamlObject &yamlObject);
+	int parseContent(ValueNode &valueNode);
 
 private:
 	struct EventDeleter {
@@ -55,7 +55,7 @@  private:
 	std::string readValue(EventPtr event);
 	int parseDictionaryOrList(yaml_event_type_t endEventType,
 				  const std::function<int(EventPtr event)> &parseItem);
-	int parseNextYamlObject(YamlObject &yamlObject, EventPtr event);
+	int parseNextNode(ValueNode &valueNode, EventPtr event);
 
 	bool parserValid_;
 	yaml_parser_t parser_;
@@ -153,15 +153,15 @@  YamlParserContext::EventPtr YamlParserContext::nextEvent()
 
 /**
  * \brief Parse the content of a YAML document
- * \param[in] yamlObject The result of YamlObject
+ * \param[in] valueNode The result of ValueNode
  *
  * Check YAML start and end events of a YAML document, and parse the root object
- * of the YAML document into a YamlObject.
+ * of the YAML document into a ValueNode.
  *
  * \return 0 on success or a negative error code otherwise
  * \retval -EINVAL The parser has failed to validate end of a YAML file
  */
-int YamlParserContext::parseContent(YamlObject &yamlObject)
+int YamlParserContext::parseContent(ValueNode &valueNode)
 {
 	/* Check start of the YAML file. */
 	EventPtr event = nextEvent();
@@ -174,7 +174,7 @@  int YamlParserContext::parseContent(YamlObject &yamlObject)
 
 	/* Parse the root object. */
 	event = nextEvent();
-	if (parseNextYamlObject(yamlObject, std::move(event)))
+	if (parseNextNode(valueNode, std::move(event)))
 		return -EINVAL;
 
 	/* Check end of the YAML file. */
@@ -247,8 +247,8 @@  int YamlParserContext::parseDictionaryOrList(yaml_event_type_t endEventType,
 }
 
 /**
- * \brief Parse next YAML event and read it as a YamlObject
- * \param[in] yamlObject The result of YamlObject
+ * \brief Parse next YAML event and read it as a ValueNode
+ * \param[in] valueNode The result of ValueNode
  * \param[in] event The leading event of the object
  *
  * Parse next YAML object separately as a value, list or dictionary.
@@ -256,26 +256,26 @@  int YamlParserContext::parseDictionaryOrList(yaml_event_type_t endEventType,
  * \return 0 on success or a negative error code otherwise
  * \retval -EINVAL Fail to parse the YAML file.
  */
-int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr event)
+int YamlParserContext::parseNextNode(ValueNode &valueNode, EventPtr event)
 {
 	if (!event)
 		return -EINVAL;
 
 	switch (event->type) {
 	case YAML_SCALAR_EVENT:
-		yamlObject.set(readValue(std::move(event)));
+		valueNode.set(readValue(std::move(event)));
 		return 0;
 
 	case YAML_SEQUENCE_START_EVENT: {
-		auto handler = [this, &yamlObject](EventPtr evt) {
-			YamlObject *child = yamlObject.add(std::make_unique<YamlObject>());
-			return parseNextYamlObject(*child, std::move(evt));
+		auto handler = [this, &valueNode](EventPtr evt) {
+			ValueNode *child = valueNode.add(std::make_unique<ValueNode>());
+			return parseNextNode(*child, std::move(evt));
 		};
 		return parseDictionaryOrList(YAML_SEQUENCE_END_EVENT, handler);
 	}
 
 	case YAML_MAPPING_START_EVENT: {
-		auto handler = [this, &yamlObject](EventPtr evtKey) {
+		auto handler = [this, &valueNode](EventPtr evtKey) {
 			/* Parse key */
 			if (evtKey->type != YAML_SCALAR_EVENT) {
 				LOG(YamlParser, Error) << "Expect key at line: "
@@ -292,9 +292,9 @@  int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
 			if (!evtValue)
 				return -EINVAL;
 
-			YamlObject *child = yamlObject.add(std::move(key),
-							   std::make_unique<YamlObject>());
-			return parseNextYamlObject(*child, std::move(evtValue));
+			ValueNode *child = valueNode.add(std::move(key),
+							 std::make_unique<ValueNode>());
+			return parseNextNode(*child, std::move(evtValue));
 		};
 		int ret = parseDictionaryOrList(YAML_MAPPING_END_EVENT, handler);
 		if (ret)
@@ -316,7 +316,7 @@  int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
  * \brief A helper class for parsing a YAML file
  *
  * The YamlParser class provides an easy interface to parse the contents of a
- * YAML file into a tree of YamlObject instances.
+ * YAML file into a tree of ValueNode instances.
  *
  * Example usage:
  *
@@ -334,17 +334,17 @@  int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
  *
  * \code{.cpp}
  *
- * std::unique_ptr<YamlObject> root = YamlParser::parse(fh);
+ * std::unique_ptr<ValueNode> root = YamlParser::parse(fh);
  * if (!root)
  *   return;
  *
  * if (!root->isDictionary())
  *   return;
  *
- * const YamlObject &name = (*root)["name"];
+ * const ValueNode &name = (*root)["name"];
  * std::cout << name.get<std::string>("") << std::endl;
  *
- * const YamlObject &numbers = (*root)["numbers"];
+ * const ValueNode &numbers = (*root)["numbers"];
  * if (!numbers.isList())
  *   return;
  *
@@ -354,7 +354,7 @@  int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
  * \endcode
  *
  * The YamlParser::parse() function takes an open FILE, parses its contents, and
- * returns a pointer to a YamlObject corresponding to the root node of the YAML
+ * returns a pointer to a ValueNode corresponding to the root node of the YAML
  * document.
  *
  * The parser preserves the order of items in the YAML file, for both lists and
@@ -362,23 +362,23 @@  int YamlParserContext::parseNextYamlObject(YamlObject &yamlObject, EventPtr even
  */
 
 /**
- * \brief Parse a YAML file as a YamlObject
+ * \brief Parse a YAML file as a ValueNode
  * \param[in] file The YAML file to parse
  *
  * The YamlParser::parse() function takes a file, parses its contents, and
- * returns a pointer to a YamlObject corresponding to the root node of the YAML
+ * returns a pointer to a ValueNode corresponding to the root node of the YAML
  * document.
  *
- * \return Pointer to result YamlObject on success or nullptr otherwise
+ * \return Pointer to result ValueNode on success or nullptr otherwise
  */
-std::unique_ptr<YamlObject> YamlParser::parse(File &file)
+std::unique_ptr<ValueNode> YamlParser::parse(File &file)
 {
 	YamlParserContext context;
 
 	if (context.init(file))
 		return nullptr;
 
-	std::unique_ptr<YamlObject> root = std::make_unique<YamlObject>();
+	std::unique_ptr<ValueNode> root = std::make_unique<ValueNode>();
 
 	if (context.parseContent(*root)) {
 		LOG(YamlParser, Error)
diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
index 01e734d23059..8c5826f4885b 100644
--- a/test/yaml-parser.cpp
+++ b/test/yaml-parser.cpp
@@ -94,7 +94,7 @@  protected:
 		Dictionary,
 	};
 
-	int testObjectType(const YamlObject &obj, const char *name, Type type)
+	int testObjectType(const ValueNode &obj, const char *name, Type type)
 	{
 		bool isList = type == Type::List || type == Type::Size;
 		bool isScalar = !isList && type != Type::Dictionary;
@@ -194,7 +194,7 @@  protected:
 		return TestPass;
 	}
 
-	int testIntegerObject(const YamlObject &obj, const char *name, Type type,
+	int testIntegerObject(const ValueNode &obj, const char *name, Type type,
 			      int64_t value)
 	{
 		uint64_t unsignedValue = static_cast<uint64_t>(value);
@@ -292,7 +292,7 @@  protected:
 			return TestFail;
 		}
 
-		std::unique_ptr<YamlObject> root = YamlParser::parse(file);
+		std::unique_ptr<ValueNode> root = YamlParser::parse(file);
 		if (root) {
 			cerr << "Invalid YAML file parse successfully" << std::endl;
 			return TestFail;