[v3,11/37] libcamera: yaml_parser: Split YamlObject from YamlParser
diff mbox series

Message ID 20260423230059.3180987-12-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • libcamera: Global configuration file improvements
Related show

Commit Message

Laurent Pinchart April 23, 2026, 11 p.m. UTC
The YamlObject class was designed to represent data parsed from YAML
files. It is outgrowing the initial needs. Move it to a separate file to
prepare for a rename.

Most files that include yaml_parser.h only need access to a YamlObject.
Switch them to yaml_object.h. pipeline_base.cpp was including
yaml_parser.h indirectly through pipeline_base.h, include it directly
now.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>
---
 .../libcamera/internal/global_configuration.h |   2 +-
 include/libcamera/internal/matrix.h           |   2 +-
 include/libcamera/internal/meson.build        |   1 +
 include/libcamera/internal/vector.h           |   2 +-
 include/libcamera/internal/yaml_object.h      | 227 +++++++++
 include/libcamera/internal/yaml_parser.h      | 213 +-------
 src/ipa/libipa/agc_mean_luminance.h           |   2 +-
 src/ipa/libipa/awb.h                          |   2 +-
 src/ipa/libipa/awb_bayes.h                    |   2 +-
 src/ipa/libipa/interpolator.h                 |   2 +-
 src/ipa/libipa/lsc_polynomial.h               |   2 +-
 src/ipa/libipa/lux.cpp                        |   2 +-
 src/ipa/libipa/module.h                       |   2 +-
 src/ipa/mali-c55/algorithms/blc.cpp           |   2 +-
 src/ipa/mali-c55/algorithms/lsc.cpp           |   2 +-
 src/ipa/rkisp1/algorithms/agc.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/blc.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/ccm.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/dpcc.cpp            |   2 +-
 src/ipa/rkisp1/algorithms/goc.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/gsl.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/lsc.cpp             |   2 +-
 src/ipa/rkisp1/algorithms/wdr.cpp             |   2 +-
 src/ipa/rpi/controller/algorithm.h            |   2 +-
 src/ipa/rpi/controller/controller.h           |   2 +-
 src/libcamera/geometry.cpp                    |   2 +-
 src/libcamera/meson.build                     |   1 +
 .../pipeline/rpi/common/pipeline_base.cpp     |   1 +
 .../pipeline/rpi/common/pipeline_base.h       |   2 +-
 .../pipeline/virtual/config_parser.h          |   2 +-
 src/libcamera/pipeline/virtual/virtual.cpp    |   2 +-
 src/libcamera/yaml_object.cpp                 | 467 ++++++++++++++++++
 src/libcamera/yaml_parser.cpp                 | 446 +----------------
 33 files changed, 727 insertions(+), 681 deletions(-)
 create mode 100644 include/libcamera/internal/yaml_object.h
 create mode 100644 src/libcamera/yaml_object.cpp

Patch
diff mbox series

diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
index 8d09517edca1..16c6a21f2a8a 100644
--- a/include/libcamera/internal/global_configuration.h
+++ b/include/libcamera/internal/global_configuration.h
@@ -14,7 +14,7 @@ 
 
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/yaml_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/include/libcamera/internal/matrix.h b/include/libcamera/internal/matrix.h
index 67761cb6037d..11fccd27547e 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 4d2a09bd7041..21ce8b391ba2 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -49,6 +49,7 @@  libcamera_internal_headers = files([
     'v4l2_subdevice.h',
     'v4l2_videodevice.h',
     'vector.h',
+    'yaml_object.h',
     'yaml_parser.h',
 ])
 
diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
index cfd8882ce0e6..af24485f3bb1 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/include/libcamera/internal/yaml_object.h b/include/libcamera/internal/yaml_object.h
new file mode 100644
index 000000000000..804f4a5b407c
--- /dev/null
+++ b/include/libcamera/internal/yaml_object.h
@@ -0,0 +1,227 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ * Copyright (C) 2026, Ideas on Board
+ *
+ * libcamera YAML object
+ */
+
+#pragma once
+
+#include <iterator>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <libcamera/base/class.h>
+
+namespace libcamera {
+
+class YamlObject
+{
+private:
+	struct Value {
+		Value(std::string k, std::unique_ptr<YamlObject> &&v)
+			: key(std::move(k)), value(std::move(v))
+		{
+		}
+		std::string key;
+		std::unique_ptr<YamlObject> value;
+	};
+
+	using ValueContainer = std::vector<Value>;
+
+public:
+#ifndef __DOXYGEN__
+	template<typename Derived>
+	class Iterator
+	{
+	public:
+		using difference_type = std::ptrdiff_t;
+		using iterator_category = std::forward_iterator_tag;
+
+		Iterator(typename ValueContainer::const_iterator it)
+			: it_(it)
+		{
+		}
+
+		Derived &operator++()
+		{
+			++it_;
+			return *static_cast<Derived *>(this);
+		}
+
+		Derived operator++(int)
+		{
+			Derived it = *static_cast<Derived *>(this);
+			it_++;
+			return it;
+		}
+
+		friend bool operator==(const Iterator &a, const Iterator &b)
+		{
+			return a.it_ == b.it_;
+		}
+
+		friend bool operator!=(const Iterator &a, const Iterator &b)
+		{
+			return a.it_ != b.it_;
+		}
+
+	protected:
+		ValueContainer::const_iterator it_;
+	};
+
+	template<typename Iterator>
+	class Adapter
+	{
+	public:
+		Adapter(const ValueContainer &container)
+			: container_(container)
+		{
+		}
+
+		Iterator begin() const
+		{
+			return Iterator{ container_.begin() };
+		}
+
+		Iterator end() const
+		{
+			return Iterator{ container_.end() };
+		}
+
+	protected:
+		const ValueContainer &container_;
+	};
+
+	class ListIterator : public Iterator<ListIterator>
+	{
+	public:
+		using value_type = const YamlObject &;
+		using pointer = const YamlObject *;
+		using reference = value_type;
+
+		value_type operator*() const
+		{
+			return *it_->value.get();
+		}
+
+		pointer operator->() const
+		{
+			return it_->value.get();
+		}
+	};
+
+	class DictIterator : public Iterator<DictIterator>
+	{
+	public:
+		using value_type = std::pair<const std::string &, const YamlObject &>;
+		using pointer = value_type *;
+		using reference = value_type &;
+
+		value_type operator*() const
+		{
+			return { it_->key, *it_->value.get() };
+		}
+	};
+
+	class DictAdapter : public Adapter<DictIterator>
+	{
+	public:
+		using key_type = std::string;
+	};
+
+	class ListAdapter : public Adapter<ListIterator>
+	{
+	};
+#endif /* __DOXYGEN__ */
+
+	YamlObject();
+	~YamlObject();
+
+	bool isValue() const
+	{
+		return type_ == Type::Value;
+	}
+	bool isList() const
+	{
+		return type_ == Type::List;
+	}
+	bool isDictionary() const
+	{
+		return type_ == Type::Dictionary;
+	}
+	bool isEmpty() const
+	{
+		return type_ == Type::Empty;
+	}
+	explicit operator bool() const
+	{
+		return type_ != Type::Empty;
+	}
+
+	std::size_t size() const;
+
+	template<typename T>
+	std::optional<T> get() const
+	{
+		return Accessor<T>{}.get(*this);
+	}
+
+	template<typename T, typename U>
+	T get(U &&defaultValue) const
+	{
+		return get<T>().value_or(std::forward<U>(defaultValue));
+	}
+
+	template<typename T>
+	void set(T &&value)
+	{
+		return Accessor<std::remove_cv_t<std::remove_reference_t<T>>>{}
+			.set(*this, std::forward<T>(value));
+	}
+
+	DictAdapter asDict() const { return DictAdapter{ list_ }; }
+	ListAdapter asList() const { return ListAdapter{ list_ }; }
+
+	const YamlObject &operator[](std::size_t index) const;
+
+	bool contains(std::string_view key) const;
+	const YamlObject &operator[](std::string_view key) const;
+
+	YamlObject *add(std::unique_ptr<YamlObject> &&child);
+	YamlObject *add(std::string key, std::unique_ptr<YamlObject> &&child);
+
+private:
+	LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject)
+
+	template<typename T>
+	friend struct Accessor;
+
+	enum class Type {
+		Dictionary,
+		List,
+		Value,
+		Empty,
+	};
+
+	template<typename T, typename Enable = void>
+	struct Accessor {
+		std::optional<T> get(const YamlObject &obj) const;
+		void set(YamlObject &obj, T value);
+	};
+
+	Type type_;
+
+	std::string value_;
+	ValueContainer list_;
+	std::map<std::string, YamlObject *, std::less<>> dictionary_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
index 7707e7469aca..e503e83a80da 100644
--- a/include/libcamera/internal/yaml_parser.h
+++ b/include/libcamera/internal/yaml_parser.h
@@ -7,222 +7,13 @@ 
 
 #pragma once
 
-#include <iterator>
-#include <map>
-#include <optional>
-#include <stdint.h>
-#include <string>
-#include <string_view>
-#include <vector>
+#include <memory>
 
-#include <libcamera/base/class.h>
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
 class File;
-class YamlParserContext;
-
-class YamlObject
-{
-private:
-	struct Value {
-		Value(std::string k, std::unique_ptr<YamlObject> &&v)
-			: key(std::move(k)), value(std::move(v))
-		{
-		}
-		std::string key;
-		std::unique_ptr<YamlObject> value;
-	};
-
-	using ValueContainer = std::vector<Value>;
-
-public:
-#ifndef __DOXYGEN__
-	template<typename Derived>
-	class Iterator
-	{
-	public:
-		using difference_type = std::ptrdiff_t;
-		using iterator_category = std::forward_iterator_tag;
-
-		Iterator(typename ValueContainer::const_iterator it)
-			: it_(it)
-		{
-		}
-
-		Derived &operator++()
-		{
-			++it_;
-			return *static_cast<Derived *>(this);
-		}
-
-		Derived operator++(int)
-		{
-			Derived it = *static_cast<Derived *>(this);
-			it_++;
-			return it;
-		}
-
-		friend bool operator==(const Iterator &a, const Iterator &b)
-		{
-			return a.it_ == b.it_;
-		}
-
-		friend bool operator!=(const Iterator &a, const Iterator &b)
-		{
-			return a.it_ != b.it_;
-		}
-
-	protected:
-		ValueContainer::const_iterator it_;
-	};
-
-	template<typename Iterator>
-	class Adapter
-	{
-	public:
-		Adapter(const ValueContainer &container)
-			: container_(container)
-		{
-		}
-
-		Iterator begin() const
-		{
-			return Iterator{ container_.begin() };
-		}
-
-		Iterator end() const
-		{
-			return Iterator{ container_.end() };
-		}
-
-	protected:
-		const ValueContainer &container_;
-	};
-
-	class ListIterator : public Iterator<ListIterator>
-	{
-	public:
-		using value_type = const YamlObject &;
-		using pointer = const YamlObject *;
-		using reference = value_type;
-
-		value_type operator*() const
-		{
-			return *it_->value.get();
-		}
-
-		pointer operator->() const
-		{
-			return it_->value.get();
-		}
-	};
-
-	class DictIterator : public Iterator<DictIterator>
-	{
-	public:
-		using value_type = std::pair<const std::string &, const YamlObject &>;
-		using pointer = value_type *;
-		using reference = value_type &;
-
-		value_type operator*() const
-		{
-			return { it_->key, *it_->value.get() };
-		}
-	};
-
-	class DictAdapter : public Adapter<DictIterator>
-	{
-	public:
-		using key_type = std::string;
-	};
-
-	class ListAdapter : public Adapter<ListIterator>
-	{
-	};
-#endif /* __DOXYGEN__ */
-
-	YamlObject();
-	~YamlObject();
-
-	bool isValue() const
-	{
-		return type_ == Type::Value;
-	}
-	bool isList() const
-	{
-		return type_ == Type::List;
-	}
-	bool isDictionary() const
-	{
-		return type_ == Type::Dictionary;
-	}
-	bool isEmpty() const
-	{
-		return type_ == Type::Empty;
-	}
-	explicit operator bool() const
-	{
-		return type_ != Type::Empty;
-	}
-
-	std::size_t size() const;
-
-	template<typename T>
-	std::optional<T> get() const
-	{
-		return Accessor<T>{}.get(*this);
-	}
-
-	template<typename T, typename U>
-	T get(U &&defaultValue) const
-	{
-		return get<T>().value_or(std::forward<U>(defaultValue));
-	}
-
-	template<typename T>
-	void set(T &&value)
-	{
-		return Accessor<std::remove_cv_t<std::remove_reference_t<T>>>{}
-			.set(*this, std::forward<T>(value));
-	}
-
-	DictAdapter asDict() const { return DictAdapter{ list_ }; }
-	ListAdapter asList() const { return ListAdapter{ list_ }; }
-
-	const YamlObject &operator[](std::size_t index) const;
-
-	bool contains(std::string_view key) const;
-	const YamlObject &operator[](std::string_view key) const;
-
-	YamlObject *add(std::unique_ptr<YamlObject> &&child);
-	YamlObject *add(std::string key, std::unique_ptr<YamlObject> &&child);
-
-private:
-	LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject)
-
-	template<typename T>
-	friend struct Accessor;
-
-	enum class Type {
-		Dictionary,
-		List,
-		Value,
-		Empty,
-	};
-
-	template<typename T, typename Enable = void>
-	struct Accessor {
-		std::optional<T> get(const YamlObject &obj) const;
-		void set(YamlObject &obj, T value);
-	};
-
-	Type type_;
-
-	std::string value_;
-	ValueContainer list_;
-	std::map<std::string, YamlObject *, std::less<>> dictionary_;
-};
 
 class YamlParser final
 {
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index 0df97b27332d..dfe1ccbe948b 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "exposure_mode_helper.h"
 #include "histogram.h"
diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
index f4a86038635f..3f25d13feaa8 100644
--- a/src/ipa/libipa/awb.h
+++ b/src/ipa/libipa/awb.h
@@ -14,7 +14,7 @@ 
 #include <libcamera/controls.h>
 
 #include "libcamera/internal/vector.h"
-#include "libcamera/internal/yaml_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
index 47ef3cce4d58..79334ad3e7a3 100644
--- a/src/ipa/libipa/awb_bayes.h
+++ b/src/ipa/libipa/awb_bayes.h
@@ -10,7 +10,7 @@ 
 #include <libcamera/controls.h>
 
 #include "libcamera/internal/vector.h"
-#include "libcamera/internal/yaml_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "awb.h"
 #include "interpolator.h"
diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h
index fb2c611d186e..af35fc556a9b 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
index c71f215de3fc..19da46860849 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/src/ipa/libipa/lux.cpp b/src/ipa/libipa/lux.cpp
index 899e88248f04..d79b116a3339 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "histogram.h"
 
diff --git a/src/ipa/libipa/module.h b/src/ipa/libipa/module.h
index c27af7718feb..8e6c658a6b63 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "algorithm.h"
 
diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp
index d099219c3e43..3bdf19141e1d 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 /**
  * \file blc.h
diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
index f75b9cd7b7b8..e32f5a83485e 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 namespace libcamera {
 
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 7cc06f91ac2b..ef16a3775056 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "libipa/histogram.h"
 
diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index 32fc44ffff92..19c262fffa73 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 /**
  * \file blc.h
diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
index 466d7a116eea..567891d115be 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "libipa/fixedpoint.h"
 #include "libipa/interpolator.h"
diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp
index 7894628144f3..c195334750e1 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "linux/rkisp1-config.h"
 
diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp
index a0e7030fe5db..6c07bd8c71e5 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "linux/rkisp1-config.h"
 
diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp
index 7ac5dc215850..292d0e80dc57 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "linux/rkisp1-config.h"
 
diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 3f456210a2da..7302eb58570b 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "libipa/lsc_polynomial.h"
 #include "linux/rkisp1-config.h"
diff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp
index ed81628c032c..9e2688a05179 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include <libipa/agc_mean_luminance.h>
 #include <libipa/histogram.h>
diff --git a/src/ipa/rpi/controller/algorithm.h b/src/ipa/rpi/controller/algorithm.h
index 1971bfdcc8ad..8839daa90916 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "controller.h"
 
diff --git a/src/ipa/rpi/controller/controller.h b/src/ipa/rpi/controller/controller.h
index fdb46557de9c..573942886bc0 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "camera_mode.h"
 #include "device_status.h"
diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp
index 2e8e9e33ea78..621008844c74 100644
--- a/src/libcamera/geometry.cpp
+++ b/src/libcamera/geometry.cpp
@@ -12,7 +12,7 @@ 
 
 #include <libcamera/base/log.h>
 
-#include "libcamera/internal/yaml_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 /**
  * \file geometry.h
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 575408b2c733..1aafc84b8802 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -58,6 +58,7 @@  libcamera_internal_sources = files([
     'v4l2_subdevice.cpp',
     'v4l2_videodevice.cpp',
     'vector.cpp',
+    'yaml_object.cpp',
     'yaml_parser.cpp',
 ])
 
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index 6d2072e84da5..08a2b32dc30f 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -21,6 +21,7 @@ 
 
 #include "libcamera/internal/camera_lens.h"
 #include "libcamera/internal/v4l2_subdevice.h"
+#include "libcamera/internal/yaml_parser.h"
 
 using namespace std::chrono_literals;
 
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h
index 597eb5878dbf..8b5d78d2d2dc 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include <libcamera/ipa/raspberrypi_ipa_interface.h>
 #include <libcamera/ipa/raspberrypi_ipa_proxy.h>
diff --git a/src/libcamera/pipeline/virtual/config_parser.h b/src/libcamera/pipeline/virtual/config_parser.h
index d2000de9c12f..f696d8862897 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_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "virtual.h"
 
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
index e8ef7e524ccf..3e8fdf496788 100644
--- a/src/libcamera/pipeline/virtual/virtual.cpp
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -37,7 +37,7 @@ 
 #include "libcamera/internal/framebuffer.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/request.h"
-#include "libcamera/internal/yaml_parser.h"
+#include "libcamera/internal/yaml_object.h"
 
 #include "pipeline/virtual/config_parser.h"
 
diff --git a/src/libcamera/yaml_object.cpp b/src/libcamera/yaml_object.cpp
new file mode 100644
index 000000000000..94f99374f415
--- /dev/null
+++ b/src/libcamera/yaml_object.cpp
@@ -0,0 +1,467 @@ 
+/* 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
+ */
+
+/**
+ * \fn YamlObject::size()
+ * \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
+ * \tparam T Type of the value
+ * \brief Parse the YamlObject as a \a T 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
+ * \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.
+ *
+ * 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
+ * \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>(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::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()) {
+			auto value = entry.get<T>();
+			if (!value)
+				return std::nullopt;
+			values.emplace_back(std::move(*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
+ */
+
+/**
+ * \fn YamlObject::operator[](std::size_t index) const
+ * \brief Retrieve the element from list YamlObject by 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;
+}
+
+/**
+ * \fn YamlObject::contains()
+ * \brief Check if an element of a dictionary exists
+ *
+ * This function check if the YamlObject contains an element. Only YamlObject
+ * instances of Dictionary type associate elements with names, 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();
+}
+
+/**
+ * \fn YamlObject::operator[](std::string_view key) const
+ * \brief Retrieve a member by name from the dictionary
+ *
+ * This function retrieve a member of a YamlObject by name. Only YamlObject
+ * instances of Dictionary type associate elements with names, 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 object as the last element of this object's children
+ * list. This object must be empty, in which case it is converted to the
+ * Type::List type, or be a list. Otherwise, the function returns a nullptr and
+ * the \a child is not modified.
+ *
+ * \return A pointer to 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 object with the given \a key to this object's children. This
+ * object must be empty, in which case it is converted to the Type::Dictionary
+ * type, or be a dictionary. Otherwise, the function returns a nullptr and the
+ * \a child is not modified.
+ *
+ * Keys are unique. If a child with the same \a key already exists, the function
+ * returns a nullptr and the \a child is not modified.
+ *
+ * \return A pointer to 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;
+
+	auto [it, inserted] = dictionary_.try_emplace(std::move(key), child.get());
+	if (!inserted)
+		return nullptr;
+
+	return list_.emplace_back(it->first, std::move(child)).value.get();
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index 347875769a44..91c8f3e5b84f 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -7,11 +7,10 @@ 
 
 #include "libcamera/internal/yaml_parser.h"
 
-#include <charconv>
 #include <errno.h>
 #include <functional>
-#include <limits>
-#include <stdlib.h>
+#include <memory>
+#include <string>
 
 #include <libcamera/base/file.h>
 #include <libcamera/base/log.h>
@@ -27,447 +26,6 @@  namespace libcamera {
 
 LOG_DEFINE_CATEGORY(YamlParser)
 
-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
- * \tparam T Type of the value
- * \brief Parse the YamlObject as a \a T 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
- * \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.
- *
- * 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
- * \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>(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::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()) {
-			auto value = entry.get<T>();
-			if (!value)
-				return std::nullopt;
-			values.emplace_back(std::move(*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
- */
-
-/**
- * \fn YamlObject::operator[](std::size_t index) const
- * \brief Retrieve the element from list YamlObject by 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;
-}
-
-/**
- * \fn YamlObject::contains()
- * \brief Check if an element of a dictionary exists
- *
- * This function check if the YamlObject contains an element. Only YamlObject
- * instances of Dictionary type associate elements with names, 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();
-}
-
-/**
- * \fn YamlObject::operator[](std::string_view key) const
- * \brief Retrieve a member by name from the dictionary
- *
- * This function retrieve a member of a YamlObject by name. Only YamlObject
- * instances of Dictionary type associate elements with names, 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 object as the last element of this object's children
- * list. This object must be empty, in which case it is converted to the
- * Type::List type, or be a list. Otherwise, the function returns a nullptr and
- * the \a child is not modified.
- *
- * \return A pointer to 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 object with the given \a key to this object's children. This
- * object must be empty, in which case it is converted to the Type::Dictionary
- * type, or be a dictionary. Otherwise, the function returns a nullptr and the
- * \a child is not modified.
- *
- * Keys are unique. If a child with the same \a key already exists, the function
- * returns a nullptr and the \a child is not modified.
- *
- * \return A pointer to 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;
-
-	auto [it, inserted] = dictionary_.try_emplace(std::move(key), child.get());
-	if (!inserted)
-		return nullptr;
-
-	return list_.emplace_back(it->first, std::move(child)).value.get();
-}
-
 #ifndef __DOXYGEN__
 
 class YamlParserContext