diff --git a/CHANGELOG.md b/CHANGELOG.md
index a87b13c08..596c81131 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,7 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- `Deref` and `DerefMut` implementations to `QList` for `QStringList`, `QPolygon`, and `QPolygonF`.
+- `QDate::format`, `QDateTime::format`, and `QTime::format` to create a `QString` with a specific `DateFormat`.
 - `QDateTime::from_string` to parse `QDateTime` from a `QString`.
+- `QSet::reserve` to reserve capacity up-front.
 - Support for further types: `QUuid`
 - New example: Basic greeter app
 - Support for further types: `qreal`, `qint64`, `qintptr`, `qsizetype`, `quint64`, `quintptr`
@@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Add pure virtual function specified through the `#[cxx_pure]` attribute
 - Add wrappers for up and down casting, for all types which inherit from QObject, available for &T, &mut T and Pin<&mut T>
 - Support for `QMessageLogContext` and sending log messages to the Qt message handler.
+- Serde support for further types: `QByteArray`, `QColor`, `QDate`, `QDateTime`, `QFont`, `QLine`, `QLineF`, `QList`, `QMargins`, `QMarginsF`, `QPoint`, `QPointF`, `QPolygon`, `QPolygonF`, `QRect`, `QRectF`, `QSet`, `QSize`, `QSizeF`, `QStringList`, `QVector`, `QVector2D`, `QVector3D`, `QVector4D`, `QTime`, `QUrl`, `QUuid`
 
 ### Removed
 
@@ -144,7 +148,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Do not use -bundle otherwise CMake builds are missing qt-static-initalizers (note this is broken in rustc 1.69)
 - Do not import `Pin` in hidden module as invokables are outside now, resolving IDE integration
-- Rust always links against a non-debug Windows runtime with *-msvc targets, so we need to link to MultiThreadedDLL
+- Rust always links against a non-debug Windows runtime with \*-msvc targets, so we need to link to MultiThreadedDLL
 
 ### Removed
 
diff --git a/Cargo.lock b/Cargo.lock
index f2761bb9f..5545b4a24 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -422,6 +422,7 @@ dependencies = [
  "qt-build-utils",
  "rgb",
  "serde",
+ "serde_json",
  "time",
  "url",
  "uuid",
diff --git a/crates/cxx-qt-lib/Cargo.toml b/crates/cxx-qt-lib/Cargo.toml
index 65b606618..3797f503a 100644
--- a/crates/cxx-qt-lib/Cargo.toml
+++ b/crates/cxx-qt-lib/Cargo.toml
@@ -42,6 +42,9 @@ image-v0-25 = { version = "0.25", optional = true, package = "image", default-fe
 cxx-qt-build.workspace = true
 qt-build-utils.workspace = true
 
+[dev-dependencies]
+serde_json = "1.0.135"
+
 [features]
 full = ["qt_full", "serde", "url", "uuid", "time", "rgb", "http", "chrono", "bytes", "image-v0-24", "image-v0-25"]
 default = []
diff --git a/crates/cxx-qt-lib/include/core/qset/qset_private.h b/crates/cxx-qt-lib/include/core/qset/qset_private.h
index 3c7c3232c..651c6a38f 100644
--- a/crates/cxx-qt-lib/include/core/qset/qset_private.h
+++ b/crates/cxx-qt-lib/include/core/qset/qset_private.h
@@ -58,6 +58,19 @@ qsetLen(const QSet<T>& s) noexcept
   return static_cast<::rust::isize>(s.size());
 }
 
+template<typename T>
+void
+qsetReserve(QSet<T>& s, ::rust::isize size) noexcept
+{
+  Q_ASSERT(size >= 0);
+  // Qt 5 has an int Qt 6 has a qsizetype
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+  s.reserve(static_cast<qsizetype>(size));
+#else
+  s.reserve(static_cast<int>(size));
+#endif
+}
+
 }
 }
 }
\ No newline at end of file
diff --git a/crates/cxx-qt-lib/include/core/quuid.h b/crates/cxx-qt-lib/include/core/quuid.h
index c4ff8b724..7a39223fd 100644
--- a/crates/cxx-qt-lib/include/core/quuid.h
+++ b/crates/cxx-qt-lib/include/core/quuid.h
@@ -10,6 +10,7 @@
 
 #include "rust/cxx.h"
 
+using QUuidStringFormat = QUuid::StringFormat;
 using QUuidVariant = QUuid::Variant;
 using QUuidVersion = QUuid::Version;
 
@@ -24,9 +25,6 @@ quuidCreateUuid();
 QUuid
 quuidCreateUuidV5(const QUuid& ns, ::rust::Slice<const ::std::uint8_t> slice);
 
-QString
-quuidToString(const QUuid& uuid);
-
 QUuid
 quuidFromString(const QString& string);
 
diff --git a/crates/cxx-qt-lib/src/core/qbytearray.rs b/crates/cxx-qt-lib/src/core/qbytearray.rs
index 6be2cd9b5..e8cab0c95 100644
--- a/crates/cxx-qt-lib/src/core/qbytearray.rs
+++ b/crates/cxx-qt-lib/src/core/qbytearray.rs
@@ -321,6 +321,54 @@ impl QByteArray {
     }
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for QByteArray {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_bytes(self.as_slice())
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QByteArray {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        use serde::de::{Error as DeError, SeqAccess, Visitor};
+
+        struct BytesVisitor;
+
+        impl<'de> Visitor<'de> for BytesVisitor {
+            type Value = QByteArray;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                formatter.write_str("an array of bytes")
+            }
+
+            fn visit_bytes<E: DeError>(self, v: &[u8]) -> Result<Self::Value, E> {
+                Ok(Self::Value::from(v))
+            }
+
+            fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
+                Ok(Self::Value::from(v))
+            }
+
+            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
+                let mut values = Self::Value::default();
+                if let Some(size_hint) = seq.size_hint() {
+                    if size_hint != 0 && size_hint <= isize::MAX as usize {
+                        values.reserve(size_hint as isize);
+                    }
+                }
+                while let Some(value) = seq.next_element()? {
+                    values.append(value);
+                }
+                Ok(values)
+            }
+        }
+
+        let visitor = BytesVisitor;
+        deserializer.deserialize_byte_buf(visitor)
+    }
+}
+
 // Safety:
 //
 // Static checks on the C++ side to ensure the size is the same.
@@ -334,6 +382,13 @@ mod tests {
     #[cfg(feature = "bytes")]
     use super::*;
 
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qbytearray_serde() {
+        let qbytearray = QByteArray::from("KDAB");
+        assert_eq!(crate::serde_impl::roundtrip(&qbytearray), qbytearray)
+    }
+
     #[cfg(feature = "bytes")]
     #[test]
     fn test_bytes() {
diff --git a/crates/cxx-qt-lib/src/core/qdate.rs b/crates/cxx-qt-lib/src/core/qdate.rs
index e1460dc62..beff45b61 100644
--- a/crates/cxx-qt-lib/src/core/qdate.rs
+++ b/crates/cxx-qt-lib/src/core/qdate.rs
@@ -176,7 +176,7 @@ impl QDate {
         Self { jd }
     }
 
-    /// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed.
+    /// Returns the QDate represented by the string, using the format given, or None if the string cannot be parsed.
     pub fn from_string(string: &ffi::QString, format: &ffi::QString) -> Option<Self> {
         let date = ffi::qdate_from_string(string, format);
         if date.is_valid() {
@@ -186,7 +186,7 @@ impl QDate {
         }
     }
 
-    /// Returns the time represented in the string as a QTime using the format given, or None if this is not possible.
+    /// Returns the time represented in the string as a QDate using the format given, or None if this is not possible.
     pub fn from_string_enum(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> {
         let date = ffi::qdate_from_string_enum(string, format);
         if date.is_valid() {
@@ -290,6 +290,13 @@ mod test {
         assert_eq!(QDate::from(naive), qdate);
     }
 
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qdate_serde() {
+        let qdate = QDate::new(2023, 1, 1);
+        assert_eq!(crate::serde_impl::roundtrip(&qdate), qdate);
+    }
+
     #[cfg(feature = "chrono")]
     #[test]
     fn qdate_to_chrono_naive() {
diff --git a/crates/cxx-qt-lib/src/core/qdatetime.rs b/crates/cxx-qt-lib/src/core/qdatetime.rs
index 231f388d9..1b2193f9d 100644
--- a/crates/cxx-qt-lib/src/core/qdatetime.rs
+++ b/crates/cxx-qt-lib/src/core/qdatetime.rs
@@ -195,6 +195,7 @@ mod ffi {
         fn qdatetimeTimeZone(datetime: &QDateTime) -> UniquePtr<QTimeZone>;
         #[rust_name = "qdatetime_settimezone"]
         fn qdatetimeSetTimeZone(datetime: &mut QDateTime, time_zone: &QTimeZone);
+        #[doc(hidden)]
         #[rust_name = "qdatetime_from_string"]
         fn qdatetimeFromQString(string: &QString, format: DateFormat) -> QDateTime;
     }
@@ -514,6 +515,17 @@ mod test {
         assert_eq!(qdatetime_b.cmp(&qdatetime_a), Ordering::Greater);
         assert_eq!(qdatetime_a.cmp(&qdatetime_a), Ordering::Equal);
     }
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qdatetime_serde() {
+        let qdatetime = QDateTime::from_date_and_time_time_zone(
+            &QDate::new(2023, 1, 1),
+            &QTime::new(1, 1, 1, 1),
+            &ffi::QTimeZone::utc(),
+        );
+        assert_eq!(crate::serde_impl::roundtrip(&qdatetime), qdatetime);
+    }
 }
 
 #[cfg(test)]
diff --git a/crates/cxx-qt-lib/src/core/qline.rs b/crates/cxx-qt-lib/src/core/qline.rs
index 0bf67c7f7..aef459160 100644
--- a/crates/cxx-qt-lib/src/core/qline.rs
+++ b/crates/cxx-qt-lib/src/core/qline.rs
@@ -98,8 +98,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QLine class provides a two-dimensional vector using integer precision
 #[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QLine {
     pt1: QPoint,
diff --git a/crates/cxx-qt-lib/src/core/qlinef.rs b/crates/cxx-qt-lib/src/core/qlinef.rs
index 8c80837d1..83d4e2acd 100644
--- a/crates/cxx-qt-lib/src/core/qlinef.rs
+++ b/crates/cxx-qt-lib/src/core/qlinef.rs
@@ -131,8 +131,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QLineF class provides a two-dimensional vector using floating point precision.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QLineF {
     pt1: QPointF,
diff --git a/crates/cxx-qt-lib/src/core/qlist/mod.rs b/crates/cxx-qt-lib/src/core/qlist/mod.rs
index de82d4dff..abe6b3073 100644
--- a/crates/cxx-qt-lib/src/core/qlist/mod.rs
+++ b/crates/cxx-qt-lib/src/core/qlist/mod.rs
@@ -400,4 +400,11 @@ mod test {
         let qlist = QList::<u8>::from(array);
         assert_eq!(Vec::from(&qlist), array);
     }
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qlist_serde() {
+        let qlist = QList::<u8>::from([0, 1, 2]);
+        assert_eq!(crate::serde_impl::roundtrip(&qlist), qlist);
+    }
 }
diff --git a/crates/cxx-qt-lib/src/core/qmargins.rs b/crates/cxx-qt-lib/src/core/qmargins.rs
index 5da187b8c..430682118 100644
--- a/crates/cxx-qt-lib/src/core/qmargins.rs
+++ b/crates/cxx-qt-lib/src/core/qmargins.rs
@@ -96,8 +96,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QMargins class defines the four margins of a rectangle.
 #[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QMargins {
     left: i32,
diff --git a/crates/cxx-qt-lib/src/core/qmarginsf.rs b/crates/cxx-qt-lib/src/core/qmarginsf.rs
index 8fcbcf800..93e5397fd 100644
--- a/crates/cxx-qt-lib/src/core/qmarginsf.rs
+++ b/crates/cxx-qt-lib/src/core/qmarginsf.rs
@@ -92,8 +92,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QMarginsF class defines the four margins of a rectangle.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QMarginsF {
     left: f64,
diff --git a/crates/cxx-qt-lib/src/core/qpoint.rs b/crates/cxx-qt-lib/src/core/qpoint.rs
index 5c4dbc342..c7aa3f8a4 100644
--- a/crates/cxx-qt-lib/src/core/qpoint.rs
+++ b/crates/cxx-qt-lib/src/core/qpoint.rs
@@ -93,8 +93,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QPoint struct defines a point in the plane using integer precision.
 #[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QPoint {
     x: i32,
diff --git a/crates/cxx-qt-lib/src/core/qpointf.rs b/crates/cxx-qt-lib/src/core/qpointf.rs
index 3a12b91c2..1b53fd1d1 100644
--- a/crates/cxx-qt-lib/src/core/qpointf.rs
+++ b/crates/cxx-qt-lib/src/core/qpointf.rs
@@ -86,9 +86,12 @@ mod ffi {
         fn operatorDiv(a: f64, b: &QPointF) -> QPointF;
     }
 }
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
 
 /// The QPointF struct defines a point in the plane using floating point precision.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QPointF {
     x: f64,
diff --git a/crates/cxx-qt-lib/src/core/qrect.rs b/crates/cxx-qt-lib/src/core/qrect.rs
index c25abe19e..3a1f80e16 100644
--- a/crates/cxx-qt-lib/src/core/qrect.rs
+++ b/crates/cxx-qt-lib/src/core/qrect.rs
@@ -268,8 +268,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QRect struct defines a rectangle in the plane using integer precision.
 #[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QRect {
     // Note that Qt stores QRect as two points rather than a point and size (which QRectF is)
diff --git a/crates/cxx-qt-lib/src/core/qrectf.rs b/crates/cxx-qt-lib/src/core/qrectf.rs
index 3ba51b236..f4a51b6af 100644
--- a/crates/cxx-qt-lib/src/core/qrectf.rs
+++ b/crates/cxx-qt-lib/src/core/qrectf.rs
@@ -269,9 +269,12 @@ mod ffi {
         fn operatorMinus(a: &QRectF, b: &QMarginsF) -> QRectF;
     }
 }
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
 
 /// The QRectF struct defines a rectangle in the plane using floating point precision.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QRectF {
     xp: f64,
diff --git a/crates/cxx-qt-lib/src/core/qset/generate.sh b/crates/cxx-qt-lib/src/core/qset/generate.sh
index 953177b2a..18fad9759 100755
--- a/crates/cxx-qt-lib/src/core/qset/generate.sh
+++ b/crates/cxx-qt-lib/src/core/qset/generate.sh
@@ -72,6 +72,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_$1, _: &$1);
         #[rust_name = "len_$1"]
         fn qsetLen(_: &QSet_$1) -> isize;
+        #[rust_name = "reserve_$1"]
+        fn qsetReserve(_: &mut QSet_$1, size: isize);
     }
 }
 
@@ -98,6 +100,10 @@ pub(crate) fn insert(s: &mut ffi::QSet_$1, value: &$1) {
 pub(crate) fn len(s: &ffi::QSet_$1) -> isize {
     ffi::len_$1(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_$1, size: isize) {
+  ffi::reserve_$1(s, size);
+}
 EOF
     rustfmt "$SCRIPTPATH/qset_$1.rs"
 }
@@ -148,6 +154,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_$1, _: &$1);
         #[rust_name = "len_$1"]
         fn qsetLen(_: &QSet_$1) -> isize;
+        #[rust_name = "reserve_$1"]
+        fn qsetReserve(_: &mut QSet_$1, size: isize);
     }
 }
 
@@ -174,6 +182,10 @@ pub(crate) fn insert(s: &mut ffi::QSet_$1, value: &ffi::$1) {
 pub(crate) fn len(s: &ffi::QSet_$1) -> isize {
     ffi::len_$1(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_$1, size: isize) {
+  ffi::reserve_$1(s, size);
+}
 EOF
     rustfmt "$SCRIPTPATH/qset_$2.rs"
 }
diff --git a/crates/cxx-qt-lib/src/core/qset/mod.rs b/crates/cxx-qt-lib/src/core/qset/mod.rs
index 82888a404..c5f8b9a6f 100644
--- a/crates/cxx-qt-lib/src/core/qset/mod.rs
+++ b/crates/cxx-qt-lib/src/core/qset/mod.rs
@@ -121,6 +121,12 @@ where
     pub fn remove(&mut self, value: &T) -> bool {
         T::remove(self, value)
     }
+
+    /// Reserve the specified capacity to prevent repeated allocations
+    /// when the maximum size is known.
+    pub fn reserve(&mut self, size: isize) {
+        T::reserve(self, size);
+    }
 }
 
 impl<T> QSet<T>
@@ -201,6 +207,7 @@ pub trait QSetElement: Sized {
     fn insert_clone(set: &mut QSet<Self>, value: &Self);
     fn len(set: &QSet<Self>) -> isize;
     fn remove(set: &mut QSet<Self>, value: &Self) -> bool;
+    fn reserve(set: &mut QSet<Self>, size: isize);
 }
 
 macro_rules! impl_qset_element {
@@ -249,6 +256,10 @@ macro_rules! impl_qset_element {
             fn remove(set: &mut QSet<Self>, value: &Self) -> bool {
                 set.cxx_remove(value)
             }
+
+            fn reserve(set: &mut QSet<Self>, size: isize) {
+                $module::reserve(set, size);
+            }
         }
     };
 }
@@ -277,3 +288,18 @@ impl_qset_element!(u8, qset_u8, "QSet_u8");
 impl_qset_element!(u16, qset_u16, "QSet_u16");
 impl_qset_element!(u32, qset_u32, "QSet_u32");
 impl_qset_element!(u64, qset_u64, "QSet_u64");
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qset_serde() {
+        let mut set = QSet::default();
+        set.insert(0);
+        set.insert(1);
+        set.insert(2);
+        assert_eq!(crate::serde_impl::roundtrip(&set), set)
+    }
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_bool.rs b/crates/cxx-qt-lib/src/core/qset/qset_bool.rs
index f5353efcc..be0ca0d31 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_bool.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_bool.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_bool, _: &bool);
         #[rust_name = "len_bool"]
         fn qsetLen(_: &QSet_bool) -> isize;
+        #[rust_name = "reserve_bool"]
+        fn qsetReserve(_: &mut QSet_bool, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_bool, value: &bool) {
 pub(crate) fn len(s: &ffi::QSet_bool) -> isize {
     ffi::len_bool(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_bool, size: isize) {
+    ffi::reserve_bool(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_f32.rs b/crates/cxx-qt-lib/src/core/qset/qset_f32.rs
index 0c9e32ab0..fe7458c30 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_f32.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_f32.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_f32, _: &f32);
         #[rust_name = "len_f32"]
         fn qsetLen(_: &QSet_f32) -> isize;
+        #[rust_name = "reserve_f32"]
+        fn qsetReserve(_: &mut QSet_f32, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_f32, value: &f32) {
 pub(crate) fn len(s: &ffi::QSet_f32) -> isize {
     ffi::len_f32(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_f32, size: isize) {
+    ffi::reserve_f32(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_f64.rs b/crates/cxx-qt-lib/src/core/qset/qset_f64.rs
index 9fe50615c..d670a8de3 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_f64.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_f64.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_f64, _: &f64);
         #[rust_name = "len_f64"]
         fn qsetLen(_: &QSet_f64) -> isize;
+        #[rust_name = "reserve_f64"]
+        fn qsetReserve(_: &mut QSet_f64, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_f64, value: &f64) {
 pub(crate) fn len(s: &ffi::QSet_f64) -> isize {
     ffi::len_f64(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_f64, size: isize) {
+    ffi::reserve_f64(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i16.rs b/crates/cxx-qt-lib/src/core/qset/qset_i16.rs
index f0981c15c..9b9d1edc0 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_i16.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_i16.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_i16, _: &i16);
         #[rust_name = "len_i16"]
         fn qsetLen(_: &QSet_i16) -> isize;
+        #[rust_name = "reserve_i16"]
+        fn qsetReserve(_: &mut QSet_i16, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i16, value: &i16) {
 pub(crate) fn len(s: &ffi::QSet_i16) -> isize {
     ffi::len_i16(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_i16, size: isize) {
+    ffi::reserve_i16(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i32.rs b/crates/cxx-qt-lib/src/core/qset/qset_i32.rs
index dd703fa28..b1cbc0a0b 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_i32.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_i32.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_i32, _: &i32);
         #[rust_name = "len_i32"]
         fn qsetLen(_: &QSet_i32) -> isize;
+        #[rust_name = "reserve_i32"]
+        fn qsetReserve(_: &mut QSet_i32, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i32, value: &i32) {
 pub(crate) fn len(s: &ffi::QSet_i32) -> isize {
     ffi::len_i32(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_i32, size: isize) {
+    ffi::reserve_i32(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i64.rs b/crates/cxx-qt-lib/src/core/qset/qset_i64.rs
index edd74528b..267859b00 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_i64.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_i64.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_i64, _: &i64);
         #[rust_name = "len_i64"]
         fn qsetLen(_: &QSet_i64) -> isize;
+        #[rust_name = "reserve_i64"]
+        fn qsetReserve(_: &mut QSet_i64, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i64, value: &i64) {
 pub(crate) fn len(s: &ffi::QSet_i64) -> isize {
     ffi::len_i64(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_i64, size: isize) {
+    ffi::reserve_i64(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i8.rs b/crates/cxx-qt-lib/src/core/qset/qset_i8.rs
index 8ff1e68b1..b1ef22887 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_i8.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_i8.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_i8, _: &i8);
         #[rust_name = "len_i8"]
         fn qsetLen(_: &QSet_i8) -> isize;
+        #[rust_name = "reserve_i8"]
+        fn qsetReserve(_: &mut QSet_i8, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i8, value: &i8) {
 pub(crate) fn len(s: &ffi::QSet_i8) -> isize {
     ffi::len_i8(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_i8, size: isize) {
+    ffi::reserve_i8(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs b/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs
index 3ffd1f82b..67c19b58a 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QByteArray, _: &QByteArray);
         #[rust_name = "len_QByteArray"]
         fn qsetLen(_: &QSet_QByteArray) -> isize;
+        #[rust_name = "reserve_QByteArray"]
+        fn qsetReserve(_: &mut QSet_QByteArray, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QByteArray, value: &ffi::QByteArray) {
 pub(crate) fn len(s: &ffi::QSet_QByteArray) -> isize {
     ffi::len_QByteArray(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QByteArray, size: isize) {
+    ffi::reserve_QByteArray(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs b/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs
index 889b77691..e4ee199ae 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QDate, _: &QDate);
         #[rust_name = "len_QDate"]
         fn qsetLen(_: &QSet_QDate) -> isize;
+        #[rust_name = "reserve_QDate"]
+        fn qsetReserve(_: &mut QSet_QDate, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QDate, value: &ffi::QDate) {
 pub(crate) fn len(s: &ffi::QSet_QDate) -> isize {
     ffi::len_QDate(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QDate, size: isize) {
+    ffi::reserve_QDate(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs b/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs
index 37bd4adb9..b88bb8b2e 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QDateTime, _: &QDateTime);
         #[rust_name = "len_QDateTime"]
         fn qsetLen(_: &QSet_QDateTime) -> isize;
+        #[rust_name = "reserve_QDateTime"]
+        fn qsetReserve(_: &mut QSet_QDateTime, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QDateTime, value: &ffi::QDateTime) {
 pub(crate) fn len(s: &ffi::QSet_QDateTime) -> isize {
     ffi::len_QDateTime(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QDateTime, size: isize) {
+    ffi::reserve_QDateTime(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs b/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs
index 72d8c8bcd..34c6cc2ea 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs
@@ -45,6 +45,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QPersistentModelIndex, _: &QPersistentModelIndex);
         #[rust_name = "len_QPersistentModelIndex"]
         fn qsetLen(_: &QSet_QPersistentModelIndex) -> isize;
+        #[rust_name = "reserve_QPersistentModelIndex"]
+        fn qsetReserve(_: &mut QSet_QPersistentModelIndex, size: isize);
     }
 }
 
@@ -74,3 +76,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QPersistentModelIndex, value: &ffi::QPers
 pub(crate) fn len(s: &ffi::QSet_QPersistentModelIndex) -> isize {
     ffi::len_QPersistentModelIndex(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QPersistentModelIndex, size: isize) {
+    ffi::reserve_QPersistentModelIndex(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs b/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs
index d6960813b..7c81e3d16 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QString, _: &QString);
         #[rust_name = "len_QString"]
         fn qsetLen(_: &QSet_QString) -> isize;
+        #[rust_name = "reserve_QString"]
+        fn qsetReserve(_: &mut QSet_QString, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QString, value: &ffi::QString) {
 pub(crate) fn len(s: &ffi::QSet_QString) -> isize {
     ffi::len_QString(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QString, size: isize) {
+    ffi::reserve_QString(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs b/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs
index 2784888c4..36e3982a0 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QTime, _: &QTime);
         #[rust_name = "len_QTime"]
         fn qsetLen(_: &QSet_QTime) -> isize;
+        #[rust_name = "reserve_QTime"]
+        fn qsetReserve(_: &mut QSet_QTime, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QTime, value: &ffi::QTime) {
 pub(crate) fn len(s: &ffi::QSet_QTime) -> isize {
     ffi::len_QTime(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QTime, size: isize) {
+    ffi::reserve_QTime(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs b/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs
index c3e0b6708..a40793fd2 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QUrl, _: &QUrl);
         #[rust_name = "len_QUrl"]
         fn qsetLen(_: &QSet_QUrl) -> isize;
+        #[rust_name = "reserve_QUrl"]
+        fn qsetReserve(_: &mut QSet_QUrl, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QUrl, value: &ffi::QUrl) {
 pub(crate) fn len(s: &ffi::QSet_QUrl) -> isize {
     ffi::len_QUrl(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QUrl, size: isize) {
+    ffi::reserve_QUrl(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs b/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs
index 8229df3db..937dd4806 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs
@@ -42,6 +42,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_QUuid, _: &QUuid);
         #[rust_name = "len_QUuid"]
         fn qsetLen(_: &QSet_QUuid) -> isize;
+        #[rust_name = "reserve_QUuid"]
+        fn qsetReserve(_: &mut QSet_QUuid, size: isize);
     }
 }
 
@@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QUuid, value: &ffi::QUuid) {
 pub(crate) fn len(s: &ffi::QSet_QUuid) -> isize {
     ffi::len_QUuid(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_QUuid, size: isize) {
+    ffi::reserve_QUuid(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u16.rs b/crates/cxx-qt-lib/src/core/qset/qset_u16.rs
index f0cc404fe..954d927ad 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_u16.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_u16.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_u16, _: &u16);
         #[rust_name = "len_u16"]
         fn qsetLen(_: &QSet_u16) -> isize;
+        #[rust_name = "reserve_u16"]
+        fn qsetReserve(_: &mut QSet_u16, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u16, value: &u16) {
 pub(crate) fn len(s: &ffi::QSet_u16) -> isize {
     ffi::len_u16(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_u16, size: isize) {
+    ffi::reserve_u16(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u32.rs b/crates/cxx-qt-lib/src/core/qset/qset_u32.rs
index accb74b25..bebacae24 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_u32.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_u32.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_u32, _: &u32);
         #[rust_name = "len_u32"]
         fn qsetLen(_: &QSet_u32) -> isize;
+        #[rust_name = "reserve_u32"]
+        fn qsetReserve(_: &mut QSet_u32, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u32, value: &u32) {
 pub(crate) fn len(s: &ffi::QSet_u32) -> isize {
     ffi::len_u32(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_u32, size: isize) {
+    ffi::reserve_u32(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u64.rs b/crates/cxx-qt-lib/src/core/qset/qset_u64.rs
index 0eb9821d3..00cebaaca 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_u64.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_u64.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_u64, _: &u64);
         #[rust_name = "len_u64"]
         fn qsetLen(_: &QSet_u64) -> isize;
+        #[rust_name = "reserve_u64"]
+        fn qsetReserve(_: &mut QSet_u64, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u64, value: &u64) {
 pub(crate) fn len(s: &ffi::QSet_u64) -> isize {
     ffi::len_u64(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_u64, size: isize) {
+    ffi::reserve_u64(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u8.rs b/crates/cxx-qt-lib/src/core/qset/qset_u8.rs
index 8392079c6..898b071df 100644
--- a/crates/cxx-qt-lib/src/core/qset/qset_u8.rs
+++ b/crates/cxx-qt-lib/src/core/qset/qset_u8.rs
@@ -40,6 +40,8 @@ pub mod ffi {
         fn qsetInsert(_: &mut QSet_u8, _: &u8);
         #[rust_name = "len_u8"]
         fn qsetLen(_: &QSet_u8) -> isize;
+        #[rust_name = "reserve_u8"]
+        fn qsetReserve(_: &mut QSet_u8, size: isize);
     }
 }
 
@@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u8, value: &u8) {
 pub(crate) fn len(s: &ffi::QSet_u8) -> isize {
     ffi::len_u8(s)
 }
+
+pub(crate) fn reserve(s: &mut ffi::QSet_u8, size: isize) {
+    ffi::reserve_u8(s, size);
+}
diff --git a/crates/cxx-qt-lib/src/core/qsize.rs b/crates/cxx-qt-lib/src/core/qsize.rs
index 41266e132..c51a0078a 100644
--- a/crates/cxx-qt-lib/src/core/qsize.rs
+++ b/crates/cxx-qt-lib/src/core/qsize.rs
@@ -113,9 +113,12 @@ mod ffi {
         fn operatorDiv(a: f64, b: &QSize) -> QSize;
     }
 }
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
 
 /// The QSize struct defines the size of a two-dimensional object using integer point precision.
 #[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QSize {
     width: i32,
diff --git a/crates/cxx-qt-lib/src/core/qsizef.rs b/crates/cxx-qt-lib/src/core/qsizef.rs
index 9a3ec13b3..02dd8556e 100644
--- a/crates/cxx-qt-lib/src/core/qsizef.rs
+++ b/crates/cxx-qt-lib/src/core/qsizef.rs
@@ -116,9 +116,12 @@ mod ffi {
         fn operatorDiv(a: f64, b: &QSizeF) -> QSizeF;
     }
 }
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
 
 /// The QSizeF class defines the size of a two-dimensional object using floating point precision.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QSizeF {
     width: f64,
diff --git a/crates/cxx-qt-lib/src/core/qstring.rs b/crates/cxx-qt-lib/src/core/qstring.rs
index b27b95867..9bcff0100 100644
--- a/crates/cxx-qt-lib/src/core/qstring.rs
+++ b/crates/cxx-qt-lib/src/core/qstring.rs
@@ -191,15 +191,10 @@ mod ffi {
     }
 }
 
-#[cfg(feature = "serde")]
-use serde::{Deserialize, Serialize};
-
 /// The QString class provides a Unicode character string.
 ///
 /// Note that QString is a UTF-16 whereas Rust strings are a UTF-8
 #[repr(C)]
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[cfg_attr(feature = "serde", serde(from = "String", into = "String"))]
 pub struct QString {
     /// The layout has changed between Qt 5 and Qt 6
     ///
@@ -425,10 +420,55 @@ unsafe impl ExternType for QString {
     type Kind = cxx::kind::Trivial;
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for QString {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_str(&String::from(self))
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QString {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        use serde::de::{Error as DeError, Unexpected, Visitor};
+
+        struct StringVisitor;
+
+        impl<'de> Visitor<'de> for StringVisitor {
+            type Value = QString;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                formatter.write_str("a string")
+            }
+
+            fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
+                Ok(Self::Value::from(v))
+            }
+
+            fn visit_bytes<E: DeError>(self, v: &[u8]) -> Result<Self::Value, E> {
+                match std::str::from_utf8(v) {
+                    Ok(s) => Ok(Self::Value::from(s)),
+                    Err(_) => Err(E::invalid_value(Unexpected::Bytes(v), &self)),
+                }
+            }
+        }
+
+        let visitor = StringVisitor;
+        deserializer.deserialize_string(visitor)
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
 
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qstring_serde() {
+        let qstring = QString::from("KDAB");
+        assert_eq!(crate::serde_impl::roundtrip(&qstring), qstring);
+    }
+
     #[test]
     fn test_ordering() {
         let qstring_a = QString::from("a");
diff --git a/crates/cxx-qt-lib/src/core/qstringlist.rs b/crates/cxx-qt-lib/src/core/qstringlist.rs
index 8294b766d..1f07b9593 100644
--- a/crates/cxx-qt-lib/src/core/qstringlist.rs
+++ b/crates/cxx-qt-lib/src/core/qstringlist.rs
@@ -244,4 +244,13 @@ mod test {
             Some("element".to_owned())
         );
     }
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qstringlist_serde() {
+        let mut qstringlist = QStringList::default();
+        qstringlist.append(QString::from("element 1"));
+        qstringlist.append(QString::from("element 2"));
+        assert_eq!(crate::serde_impl::roundtrip(&qstringlist), qstringlist)
+    }
 }
diff --git a/crates/cxx-qt-lib/src/core/qtime.rs b/crates/cxx-qt-lib/src/core/qtime.rs
index bb95975d5..b3047c75c 100644
--- a/crates/cxx-qt-lib/src/core/qtime.rs
+++ b/crates/cxx-qt-lib/src/core/qtime.rs
@@ -140,11 +140,31 @@ impl QTime {
         ffi::qtime_from_string(string, format)
     }
 
+    /// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed.
+    pub fn from_string_opt(string: &ffi::QString, format: &ffi::QString) -> Option<Self> {
+        let time = ffi::qtime_from_string(string, format);
+        if time.is_valid() {
+            Some(time)
+        } else {
+            None
+        }
+    }
+
     /// Returns the time represented in the string as a QTime using the format given, or an invalid time if this is not possible.
     pub fn from_string_enum(string: &ffi::QString, format: ffi::DateFormat) -> Self {
         ffi::qtime_from_string_enum(string, format)
     }
 
+    /// Returns the time represented in the string as a QTime using the format given, or None if this is not possible.
+    pub fn from_string_enum_opt(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> {
+        let time = ffi::qtime_from_string_enum(string, format);
+        if time.is_valid() {
+            Some(time)
+        } else {
+            None
+        }
+    }
+
     /// Returns the number of milliseconds from this time to t.
     /// If t is earlier than this time, the number of milliseconds returned is negative.
     pub fn msecs_to(&self, t: Self) -> i32 {
@@ -266,6 +286,18 @@ unsafe impl ExternType for QTime {
     type Kind = cxx::kind::Trivial;
 }
 
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qtime_serde() {
+        let qtime = QTime::new(1, 2, 3, 4);
+        assert_eq!(crate::serde_impl::roundtrip(&qtime), qtime);
+    }
+}
+
 #[cfg(test)]
 #[cfg(feature = "chrono")]
 mod test_chrono {
diff --git a/crates/cxx-qt-lib/src/core/qurl.rs b/crates/cxx-qt-lib/src/core/qurl.rs
index a009bfcae..b5501a4d9 100644
--- a/crates/cxx-qt-lib/src/core/qurl.rs
+++ b/crates/cxx-qt-lib/src/core/qurl.rs
@@ -501,6 +501,21 @@ impl TryFrom<&QUrl> for url::Url {
     }
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for QUrl {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        ffi::qurl_to_qstring(self).serialize(serializer)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QUrl {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        let string = ffi::QString::deserialize(deserializer)?;
+        Ok(Self::from(&string))
+    }
+}
+
 // Safety:
 //
 // Static checks on the C++ side to ensure the size is the same.
@@ -511,9 +526,15 @@ unsafe impl ExternType for QUrl {
 
 #[cfg(test)]
 mod tests {
-    #[cfg(any(feature = "http", feature = "url"))]
     use super::*;
 
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qurl_serde() {
+        let qurl = QUrl::from("https://github.com/kdab/cxx-qt");
+        assert_eq!(crate::serde_impl::roundtrip(&qurl), qurl);
+    }
+
     #[cfg(feature = "http")]
     #[test]
     fn test_http() {
diff --git a/crates/cxx-qt-lib/src/core/quuid.cpp b/crates/cxx-qt-lib/src/core/quuid.cpp
index e96d5ed17..7aa5f7399 100644
--- a/crates/cxx-qt-lib/src/core/quuid.cpp
+++ b/crates/cxx-qt-lib/src/core/quuid.cpp
@@ -52,12 +52,6 @@ quuidCreateUuidV5(const QUuid& ns, ::rust::Slice<const ::std::uint8_t> slice)
   return QUuid::createUuidV5(ns, byteView(slice));
 }
 
-QString
-quuidToString(const QUuid& uuid)
-{
-  return uuid.toString();
-}
-
 QUuid
 quuidFromString(const QString& string)
 {
diff --git a/crates/cxx-qt-lib/src/core/quuid.rs b/crates/cxx-qt-lib/src/core/quuid.rs
index 2b789935e..8116579ee 100644
--- a/crates/cxx-qt-lib/src/core/quuid.rs
+++ b/crates/cxx-qt-lib/src/core/quuid.rs
@@ -10,6 +10,21 @@ use uuid::Uuid;
 
 #[cxx::bridge]
 mod ffi {
+    #[repr(i32)]
+    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+    enum QUuidStringFormat {
+        /// Five hex fields, separated by dashes and surrounded by braces.
+        /// Example: {00000000-0000-0000-0000-000000000000}.
+        WithBraces = 0,
+        /// Only the five dash-separated fields, without the braces.
+        /// Example: 00000000-0000-0000-0000-000000000000.
+        WithoutBraces = 1,
+        /// Only the hex digits, without braces or dashes.
+        /// Example: 00000000000000000000000000000000.
+        /// Note that QUuid cannot parse this back again as input.
+        Id128 = 3,
+    }
+
     #[repr(i32)]
     #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
     enum QUuidVariant {
@@ -55,9 +70,15 @@ mod ffi {
     unsafe extern "C++" {
         include!("cxx-qt-lib/quuid.h");
         type QUuid = super::QUuid;
+        type QUuidStringFormat;
         type QUuidVariant;
         type QUuidVersion;
 
+        /// Returns the string representation of this QUuid, with the formatting controlled by the
+        /// `mode` parameter.
+        #[rust_name = "format"]
+        fn toString(self: &QUuid, mode: QUuidStringFormat) -> QString;
+
         /// Returns the value in the variant field of the UUID. If the return value is
         /// `QUuidVariant::DCE`, call `version()` to see which layout it uses. The null UUID is
         /// considered to be of an unknown variant.
@@ -85,9 +106,6 @@ mod ffi {
         #[rust_name = "quuid_create_uuid_v5"]
         fn quuidCreateUuidV5(ns: &QUuid, data: &[u8]) -> QUuid;
         #[doc(hidden)]
-        #[rust_name = "quuid_to_string"]
-        fn quuidToString(uuid: &QUuid) -> QString;
-        #[doc(hidden)]
         #[rust_name = "quuid_from_string"]
         fn quuidFromString(string: &QString) -> QUuid;
         #[doc(hidden)]
@@ -99,7 +117,7 @@ mod ffi {
     }
 }
 
-pub use ffi::{QUuidVariant, QUuidVersion};
+pub use ffi::{QUuidStringFormat, QUuidVariant, QUuidVersion};
 
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(C)]
@@ -120,7 +138,7 @@ impl Default for QUuid {
 
 impl fmt::Display for QUuid {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", ffi::quuid_to_string(self))
+        write!(f, "{}", self.format(QUuidStringFormat::WithBraces))
     }
 }
 
@@ -250,7 +268,7 @@ unsafe impl ExternType for QUuid {
 
 impl From<QUuid> for QString {
     fn from(value: QUuid) -> Self {
-        ffi::quuid_to_string(&value)
+        value.format(QUuidStringFormat::WithBraces)
     }
 }
 
@@ -333,6 +351,29 @@ impl From<QUuid> for Uuid {
     }
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for QUuid {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        if serializer.is_human_readable() {
+            self.format(QUuidStringFormat::WithoutBraces)
+                .serialize(serializer)
+        } else {
+            self.to_bytes().serialize(serializer)
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QUuid {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        if deserializer.is_human_readable() {
+            QString::deserialize(deserializer).map(|s| Self::from(&s))
+        } else {
+            <[u8; 16]>::deserialize(deserializer).map(Self::from_bytes)
+        }
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -457,4 +498,12 @@ mod test {
         let roundtrip = QUuid::from_u128(uuid.to_u128());
         assert_eq!(uuid, roundtrip)
     }
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn quuid_serde() {
+        let uuid = QUuid::create_uuid();
+        let roundtrip = crate::serde_impl::roundtrip(&uuid);
+        assert_eq!(roundtrip, uuid)
+    }
 }
diff --git a/crates/cxx-qt-lib/src/core/qvector/mod.rs b/crates/cxx-qt-lib/src/core/qvector/mod.rs
index aef9a6c1a..f1d94752f 100644
--- a/crates/cxx-qt-lib/src/core/qvector/mod.rs
+++ b/crates/cxx-qt-lib/src/core/qvector/mod.rs
@@ -400,4 +400,11 @@ mod test {
         let qvec = QVector::<u8>::from(array);
         assert_eq!(Vec::from(&qvec), array);
     }
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qvec_serde() {
+        let qvec = QVector::<u8>::from([0, 1, 2]);
+        assert_eq!(crate::serde_impl::roundtrip(&qvec), qvec);
+    }
 }
diff --git a/crates/cxx-qt-lib/src/gui/qcolor.rs b/crates/cxx-qt-lib/src/gui/qcolor.rs
index 415868dad..561f65c89 100644
--- a/crates/cxx-qt-lib/src/gui/qcolor.rs
+++ b/crates/cxx-qt-lib/src/gui/qcolor.rs
@@ -619,6 +619,29 @@ impl From<&QColor> for rgb::RGBA8 {
     }
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for QColor {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let format = if self.alpha() == 255 {
+            ffi::QColorNameFormat::HexRgb
+        } else {
+            ffi::QColorNameFormat::HexArgb
+        };
+        self.name(format).serialize(serializer)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QColor {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        let string = ffi::QString::deserialize(deserializer)?;
+        Self::try_from(&string).map_err(|_| {
+            use serde::de::{Error as _, Unexpected};
+            D::Error::invalid_value(Unexpected::Str(&String::from(&string)), &"hex color code")
+        })
+    }
+}
+
 // Safety:
 //
 // Static checks on the C++ side to ensure the size is the same.
@@ -629,9 +652,15 @@ unsafe impl ExternType for QColor {
 
 #[cfg(test)]
 mod tests {
-    #[cfg(feature = "rgb")]
     use super::*;
 
+    #[cfg(feature = "serde")]
+    #[test]
+    fn serde_qcolor() {
+        let qcolor = QColor::from_rgba(10, 20, 30, 40);
+        assert_eq!(crate::serde_impl::roundtrip(&qcolor), qcolor);
+    }
+
     #[cfg(feature = "rgb")]
     #[test]
     fn test_rgb() {
diff --git a/crates/cxx-qt-lib/src/gui/qfont.rs b/crates/cxx-qt-lib/src/gui/qfont.rs
index e9287974a..8ea5becf9 100644
--- a/crates/cxx-qt-lib/src/gui/qfont.rs
+++ b/crates/cxx-qt-lib/src/gui/qfont.rs
@@ -159,6 +159,10 @@ mod ffi {
         #[rust_name = "default_family"]
         fn defaultFamily(self: &QFont) -> QString;
 
+        /// Returns a description of the font. The description is a comma-separated list of the attributes.
+        #[rust_name = "description"]
+        fn toString(self: &QFont) -> QString;
+
         /// Returns true if a window system font exactly matching
         /// the settings of this font is available.
         #[rust_name = "exact_match"]
@@ -177,8 +181,8 @@ mod ffi {
         #[rust_name = "fixed_pitch"]
         fn fixedPitch(self: &QFont) -> bool;
 
-        /// Sets this font to match the description descrip. The description is a comma-separated
-        /// list of the font attributes, as returned by toString().
+        /// Sets this font to match the description *descrip*. The description is a comma-separated
+        /// list of the font attributes, as returned by [`QFont::description`].
         #[rust_name = "from_string"]
         fn fromString(self: &mut QFont, descrip: &QString) -> bool;
 
@@ -423,6 +427,30 @@ impl QFont {
     }
 }
 
+#[cfg(feature = "serde")]
+impl serde::Serialize for QFont {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        self.description().serialize(serializer)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for QFont {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        use serde::de::{Error as _, Unexpected};
+
+        let qstring = ffi::QString::deserialize(deserializer)?;
+        let mut font = QFont::default();
+        if !font.from_string(&qstring) {
+            return Err(D::Error::invalid_value(
+                Unexpected::Str(&String::from(&qstring)),
+                &"QFont description",
+            ));
+        }
+        Ok(font)
+    }
+}
+
 // Safety:
 //
 // Static checks on the C++ side to ensure the size is the same.
@@ -430,3 +458,29 @@ unsafe impl ExternType for QFont {
     type Id = type_id!("QFont");
     type Kind = cxx::kind::Trivial;
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qfont_serde() {
+        let mut qfont = QFont::default();
+        qfont.set_family(&ffi::QString::from("Arial"));
+        qfont.set_bold(true);
+        qfont.set_pixel_size(10);
+        qfont.set_style_hint(QFontStyleHint::Courier, QFontStyleStrategy::PreferAntialias);
+        qfont.set_strikeout(true);
+        qfont.set_capitalization(QFontCapitalization::AllLowercase);
+        qfont.set_letter_spacing(QFontSpacingType::AbsoluteSpacing, 10.0);
+        qfont.set_word_spacing(1.5);
+        qfont.set_stretch(2);
+        qfont.set_style(QFontStyle::StyleItalic);
+
+        assert_eq!(
+            crate::serde_impl::roundtrip(&qfont).description(),
+            qfont.description()
+        );
+    }
+}
diff --git a/crates/cxx-qt-lib/src/gui/qpolygon.rs b/crates/cxx-qt-lib/src/gui/qpolygon.rs
index 314372639..b506d361e 100644
--- a/crates/cxx-qt-lib/src/gui/qpolygon.rs
+++ b/crates/cxx-qt-lib/src/gui/qpolygon.rs
@@ -195,3 +195,17 @@ unsafe impl ExternType for QPolygon {
     type Id = type_id!("QPolygon");
     type Kind = cxx::kind::Trivial;
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qpolygon_serde() {
+        let mut polygon = QPolygon::default();
+        polygon.append(QPoint::new(1, 2));
+        polygon.append(QPoint::new(3, 4));
+        assert_eq!(crate::serde_impl::roundtrip(&polygon), polygon);
+    }
+}
diff --git a/crates/cxx-qt-lib/src/gui/qpolygonf.rs b/crates/cxx-qt-lib/src/gui/qpolygonf.rs
index 7b843df4d..a4b94494e 100644
--- a/crates/cxx-qt-lib/src/gui/qpolygonf.rs
+++ b/crates/cxx-qt-lib/src/gui/qpolygonf.rs
@@ -177,3 +177,17 @@ unsafe impl ExternType for QPolygonF {
     type Id = type_id!("QPolygonF");
     type Kind = cxx::kind::Trivial;
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[cfg(feature = "serde")]
+    #[test]
+    fn qpolygonf_serde() {
+        let mut polygonf = QPolygonF::default();
+        polygonf.append(QPointF::new(1.0, 2.0));
+        polygonf.append(QPointF::new(3.0, 4.0));
+        assert_eq!(crate::serde_impl::roundtrip(&polygonf), polygonf);
+    }
+}
diff --git a/crates/cxx-qt-lib/src/gui/qvector2d.rs b/crates/cxx-qt-lib/src/gui/qvector2d.rs
index 7349319e6..6c269c0b4 100644
--- a/crates/cxx-qt-lib/src/gui/qvector2d.rs
+++ b/crates/cxx-qt-lib/src/gui/qvector2d.rs
@@ -127,7 +127,11 @@ mod ffi {
 }
 
 /// The QVector2D class represents a vector or vertex in 2D space.
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QVector2D {
     v: [f32; 2],
diff --git a/crates/cxx-qt-lib/src/gui/qvector3d.rs b/crates/cxx-qt-lib/src/gui/qvector3d.rs
index c8b76046b..4cd8f85c4 100644
--- a/crates/cxx-qt-lib/src/gui/qvector3d.rs
+++ b/crates/cxx-qt-lib/src/gui/qvector3d.rs
@@ -142,8 +142,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QVector3D class represents a vector or vertex in 3D space.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QVector3D {
     v: [f32; 3],
diff --git a/crates/cxx-qt-lib/src/gui/qvector4d.rs b/crates/cxx-qt-lib/src/gui/qvector4d.rs
index 944f74b70..980399c18 100644
--- a/crates/cxx-qt-lib/src/gui/qvector4d.rs
+++ b/crates/cxx-qt-lib/src/gui/qvector4d.rs
@@ -133,8 +133,12 @@ mod ffi {
     }
 }
 
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
 /// The QVector4D class represents a vector or vertex in 4D space.
 #[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[repr(C)]
 pub struct QVector4D {
     v: [f32; 4],
diff --git a/crates/cxx-qt-lib/src/lib.rs b/crates/cxx-qt-lib/src/lib.rs
index 21e9d1f9a..006890a2c 100644
--- a/crates/cxx-qt-lib/src/lib.rs
+++ b/crates/cxx-qt-lib/src/lib.rs
@@ -9,6 +9,9 @@ compile_error!("cxxqt_qt_version_major must be either \"5\" or \"6\"");
 
 mod core;
 
+#[cfg(feature = "serde")]
+mod serde_impl;
+
 pub use crate::core::*;
 
 #[cfg(feature = "qt_gui")]
diff --git a/crates/cxx-qt-lib/src/serde_impl.rs b/crates/cxx-qt-lib/src/serde_impl.rs
new file mode 100644
index 000000000..aa85a6a3f
--- /dev/null
+++ b/crates/cxx-qt-lib/src/serde_impl.rs
@@ -0,0 +1,190 @@
+// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
+//
+// SPDX-License-Identifier: MIT OR Apache-2.0
+use crate::{
+    DateFormat, QDate, QList, QListElement, QSet, QSetElement, QString, QStringList, QTime,
+    QVector, QVectorElement,
+};
+use cxx::ExternType;
+use serde::de::{Error as _, SeqAccess, Unexpected, Visitor};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use std::fmt::{self, Formatter};
+use std::marker::PhantomData;
+use std::num::NonZeroIsize;
+
+/// Serializes and deserializes a time-like value using an ISO-8601 string as the intermediary.
+macro_rules! datetime_impl {
+    ($t:ty, $construct:expr, $f:expr, $expected:literal) => {
+        impl Serialize for $t {
+            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+                self.format_enum($f).serialize(serializer)
+            }
+        }
+
+        impl<'de> Deserialize<'de> for $t {
+            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+                let string = QString::deserialize(deserializer)?;
+                $construct(&string, $f).ok_or_else(|| {
+                    D::Error::invalid_value(Unexpected::Str(&String::from(&string)), &$expected)
+                })
+            }
+        }
+    };
+}
+
+datetime_impl!(
+    QDate,
+    QDate::from_string_enum,
+    DateFormat::ISODate,
+    "ISO-8601 date"
+);
+datetime_impl!(
+    QTime,
+    QTime::from_string_enum_opt,
+    DateFormat::ISODateWithMs,
+    "ISO-8601 time"
+);
+#[cfg(not(target_os = "emscripten"))]
+datetime_impl!(
+    crate::QDateTime,
+    crate::QDateTime::from_string,
+    DateFormat::ISODateWithMs,
+    "ISO-8601 datetime"
+);
+
+/// Serde deserializers provide an `Option<usize>` size hint, but Qt containers use signed types
+/// for size. This helper function converts between the two.
+/// It also returns `None` if the size hint is 0, because there's no need to reserve capacity of 0.
+const fn get_size_hint(size_hint: Option<usize>) -> Option<NonZeroIsize> {
+    match size_hint {
+        Some(n) if n <= isize::MAX as usize => NonZeroIsize::new(n as isize),
+        _ => None,
+    }
+}
+
+/// Serializes and deserializes a list-like container by iterating over values.
+macro_rules! seq_impl {
+    ($t:ident, $element:ident, $insert:expr) => {
+        impl<T> Serialize for $t<T>
+        where
+            T: $element + Serialize,
+        {
+            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+                serializer.collect_seq(self.iter())
+            }
+        }
+
+        impl<'de, T> Deserialize<'de> for $t<T>
+        where
+            T: $element + Deserialize<'de> + ExternType<Kind = cxx::kind::Trivial>,
+        {
+            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+                struct SeqVisitor<T: $element> {
+                    marker: PhantomData<$t<T>>,
+                }
+
+                impl<'de, T> Visitor<'de> for SeqVisitor<T>
+                where
+                    T: $element + Deserialize<'de> + ExternType<Kind = cxx::kind::Trivial>,
+                {
+                    type Value = $t<T>;
+
+                    fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+                        formatter.write_str("a sequence")
+                    }
+
+                    #[inline]
+                    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+                    where
+                        A: SeqAccess<'de>,
+                    {
+                        let mut values = Self::Value::default();
+                        if let Some(size_hint) = get_size_hint(seq.size_hint()) {
+                            values.reserve(size_hint.get());
+                        }
+                        while let Some(value) = seq.next_element()? {
+                            $insert(&mut values, value);
+                        }
+                        Ok(values)
+                    }
+                }
+
+                let visitor = SeqVisitor {
+                    marker: PhantomData,
+                };
+                deserializer.deserialize_seq(visitor)
+            }
+        }
+    };
+}
+
+seq_impl!(QList, QListElement, QList::append);
+seq_impl!(QSet, QSetElement, QSet::insert);
+seq_impl!(QVector, QVectorElement, QVector::append);
+
+/// Like seq_impl, but for Qt classes that dereference to a container.
+macro_rules! deref_impl {
+    ($t:ty) => {
+        impl Serialize for $t {
+            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+                (**self).serialize(serializer)
+            }
+        }
+
+        impl<'de> Deserialize<'de> for $t {
+            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+                struct SeqVisitor;
+
+                impl<'de> Visitor<'de> for SeqVisitor {
+                    type Value = $t;
+
+                    fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+                        formatter.write_str("a sequence")
+                    }
+
+                    #[inline]
+                    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+                    where
+                        A: SeqAccess<'de>,
+                    {
+                        let mut list = Self::Value::default();
+                        let values = &mut *list;
+                        if let Some(size_hint) = get_size_hint(seq.size_hint()) {
+                            values.reserve(size_hint.get());
+                        }
+                        while let Some(value) = seq.next_element()? {
+                            values.append(value);
+                        }
+                        Ok(list)
+                    }
+                }
+
+                let visitor = SeqVisitor;
+                deserializer.deserialize_seq(visitor)
+            }
+        }
+    };
+}
+
+deref_impl!(QStringList);
+
+#[cfg(feature = "qt_gui")]
+deref_impl!(crate::QPolygon);
+#[cfg(feature = "qt_gui")]
+deref_impl!(crate::QPolygonF);
+
+#[cfg(test)]
+pub fn roundtrip<T>(value: &T) -> T
+where
+    T: Serialize + serde::de::DeserializeOwned,
+{
+    let serialized = serde_json::to_value(value).expect("error serializing value");
+    match serde_json::from_value(serialized) {
+        Ok(deserialized) => deserialized,
+        Err(e) => panic!(
+            "error deserializing {}: {e}",
+            serde_json::to_value(value).unwrap()
+        ),
+    }
+}