Repository: jonasbb/serde_with
Pull request #863 (2025-06-27 22:55:37)

Add support for `schemars` v1
* Copy existing `schemars_0_9` code to `schemars_1` and update feature
  flags and names
* Add extra bounds for BTreeMap and HashMap keys
* Update snapshot files for v0.9->v1 differences
* Bumped syn in lockfile as schemars_derive uses types introduced in syn
    2.0.81


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e2b10b1..f5f6052 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -789,9 +789,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.81"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
 dependencies = [
  "unicode-ident",
 ]
@@ -960,6 +960,19 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "schemars"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5f657913eee4bddb062d3aa42193b9285e0e333f852885909a27d867579f2a"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "schemars_derive 1.0.2",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "schemars_derive"
 version = "0.8.17"
@@ -984,6 +997,18 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "schemars_derive"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d367383cbc8d37ce3aa513ca69daf21053d8e96c999fec305c2b402d790e66c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.2.0"
@@ -1030,9 +1055,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive_internals"
-version = "0.29.0"
+version = "0.29.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1094,6 +1119,7 @@ dependencies = [
  "rustversion",
  "schemars 0.8.17",
  "schemars 0.9.0",
+ "schemars 1.0.2",
  "serde",
  "serde-xml-rs",
  "serde_derive",
@@ -1165,9 +1191,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
 name = "syn"
-version = "2.0.60"
+version = "2.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/serde_with/CHANGELOG.md b/serde_with/CHANGELOG.md
index 42aff99..8626203 100644
--- a/serde_with/CHANGELOG.md
+++ b/serde_with/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 * Add support for `Range`, `RangeFrom`, `RangeTo`, `RangeInclusive` (#851)
     `RangeToInclusive` is currently unsupported by serde.
 * Add `schemars` implementations for `Bound`, `Range`, `RangeFrom`, `RangeTo`, `RangeInclusive`.
+* Added support for `schemars` v1 under the `schemars_1` feature flag
 
 ## [3.13.0] - 2025-06-14
 
diff --git a/serde_with/Cargo.toml b/serde_with/Cargo.toml
index 4ab71cf..dabef9f 100644
--- a/serde_with/Cargo.toml
+++ b/serde_with/Cargo.toml
@@ -41,7 +41,7 @@ alloc = ["serde/alloc", "base64?/alloc", "chrono_0_4?/alloc", "hex?/alloc", "ser
 ## Enables support for various types from the std library.
 ## This will enable `std` support in all dependencies too.
 ## The feature enabled by default and also enables `alloc`.
-std = ["alloc", "serde/std", "chrono_0_4?/clock", "chrono_0_4?/std", "indexmap_1?/std", "indexmap_2?/std", "time_0_3?/serde-well-known", "time_0_3?/std", "schemars_0_9?/std"]
+std = ["alloc", "serde/std", "chrono_0_4?/clock", "chrono_0_4?/std", "indexmap_1?/std", "indexmap_2?/std", "time_0_3?/serde-well-known", "time_0_3?/std", "schemars_0_9?/std", "schemars_1?/std"]
 
 #! # Documentation
 #!
@@ -113,20 +113,27 @@ json = ["dep:serde_json", "alloc"]
 ##
 ## This pulls in [`serde_with_macros`] as a dependency.
 macros = ["dep:serde_with_macros"]
-## This feature enables integration with `schemars` 0.8.
+## This feature enables integration with `schemars` v0.8.
 ## This makes `#[derive(JsonSchema)]` pick up the correct schema for the type
 ## used within `#[serde_as(as = ...)]`.
 ##
 ## This pulls in [`schemars` v0.8](::schemars_0_8) as a dependency. It will also implicitly enable
 ## the `std` feature as `schemars` is not `#[no_std]`.
 schemars_0_8 = ["dep:schemars_0_8", "std", "serde_with_macros?/schemars_0_8"]
-## This feature enables integration with `schemars` 0.9
+## This feature enables integration with `schemars` v0.9
 ## This makes `#[derive(JsonSchema)]` pick up the correct schema for the type
 ## used within `#[serde_as(as = ...)]`.
 ##
 ## This pulls in [`schemars` v0.9](::schemars_0_9) as a dependency. It will also implicitly enable
 ## the `alloc` feature.
 schemars_0_9 = ["dep:schemars_0_9", "alloc", "serde_with_macros?/schemars_0_9", "dep:serde_json"]
+## This feature enables integration with `schemars` v1
+## This makes `#[derive(JsonSchema)]` pick up the correct schema for the type
+## used within `#[serde_as(as = ...)]`.
+##
+## This pulls in [`schemars` v1](::schemars_1) as a dependency. It will also implicitly enable
+## the `alloc` feature.
+schemars_1 = ["dep:schemars_1", "alloc", "serde_with_macros?/schemars_1", "dep:serde_json"]
 ## The feature enables integration of `time` v0.3 specific conversions.
 ## This includes support for the timestamp and duration types.
 ##
@@ -146,6 +153,7 @@ indexmap_1 = { package = "indexmap", version = "1.8", optional = true, default-f
 indexmap_2 = { package = "indexmap", version = "2.0", optional = true, default-features = false, features = ["serde"] }
 schemars_0_8 = { package = "schemars", version = "0.8.16", optional = true, default-features = false }
 schemars_0_9 = { package = "schemars", version = "0.9.0", optional = true, default-features = false }
+schemars_1 = { package = "schemars", version = "1.0.2", optional = true, default-features = false }
 serde = { version = "1.0.152", default-features = false }
 serde_derive = "1.0.152"
 serde_json = { version = "1.0.45", optional = true, default-features = false }
@@ -165,6 +173,7 @@ ron = "0.10"
 rustversion = "1.0.0"
 schemars_0_8 = { package = "schemars", version = "0.8.16" }
 schemars_0_9 = { package = "schemars", version = "0.9.0" }
+schemars_1 = { package = "schemars", version = "1.0.2" }
 serde = { version = "1.0.152", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.25", features = ["preserve_order"] }
 serde_test = "1.0.124"
@@ -247,6 +256,11 @@ name = "schemars_0_9"
 path = "tests/schemars_0_9/main.rs"
 required-features = ["schemars_0_9", "std"]
 
+[[test]]
+name = "schemars_1"
+path = "tests/schemars_1/main.rs"
+required-features = ["schemars_1", "std"]
+
 [package.metadata.docs.rs]
 all-features = true
 rustdoc-args = [
diff --git a/serde_with/src/lib.rs b/serde_with/src/lib.rs
index 6595a18..b18e04a 100644
--- a/serde_with/src/lib.rs
+++ b/serde_with/src/lib.rs
@@ -312,6 +312,9 @@ pub mod schemars_0_8;
 #[cfg(feature = "schemars_0_9")]
 #[cfg_attr(docsrs, doc(cfg(feature = "schemars_0_9")))]
 pub mod schemars_0_9;
+#[cfg(feature = "schemars_1")]
+#[cfg_attr(docsrs, doc(cfg(feature = "schemars_1")))]
+pub mod schemars_1;
 pub mod ser;
 mod serde_conv;
 #[cfg(feature = "time_0_3")]
@@ -2594,6 +2597,10 @@ pub struct SetLastValueWins<T>(PhantomData<T>);
 /// It is added implicitly by the [`#[serde_as]`](crate::serde_as) macro when any `schemars`
 /// feature is enabled.
 ///
-/// [`JsonSchema`]: ::schemars_0_8::JsonSchema
-#[cfg(any(feature = "schemars_0_8", feature = "schemars_0_9"))]
+/// [`JsonSchema`]: ::schemars_1::JsonSchema
+#[cfg(any(
+    feature = "schemars_0_8",
+    feature = "schemars_0_9",
+    feature = "schemars_1"
+))]
 pub struct Schema<T: ?Sized, TA>(PhantomData<T>, PhantomData<TA>);
diff --git a/serde_with/src/schemars_1.rs b/serde_with/src/schemars_1.rs
new file mode 100644
index 0000000..6d8b470
--- /dev/null
+++ b/serde_with/src/schemars_1.rs
@@ -0,0 +1,1369 @@
+//! Integration with [schemars v1](schemars_1).
+//!
+//! This module is only available if using the `schemars_1` feature of the crate.
+//!
+//! If you would like to add support for schemars to your own `serde_with` helpers
+//! see [`JsonSchemaAs`].
+
+use crate::{
+    formats::{Flexible, Format, PreferMany, PreferOne, Separator, Strict},
+    prelude::{Schema as WrapSchema, *},
+};
+use ::schemars_1::{json_schema, JsonSchema, Schema, SchemaGenerator};
+use alloc::{
+    borrow::Cow,
+    collections::{BTreeMap, BTreeSet},
+    format,
+    rc::Rc,
+    vec::Vec,
+};
+use core::{
+    mem::ManuallyDrop,
+    ops::{Deref, DerefMut},
+};
+use serde_json::Value;
+
+//===================================================================
+// Trait Definition
+
+/// A type which can be described as a JSON schema document.
+///
+/// This trait is as [`SerializeAs`] is to [`Serialize`] but for [`JsonSchema`].
+/// You can use it to make your custom [`SerializeAs`] and [`DeserializeAs`]
+/// types also support being described via JSON schemas.
+///
+/// It is used by the [`Schema`][1] type in order to implement [`JsonSchema`]
+/// for the relevant types. [`Schema`][1] is used implicitly by the [`serde_as`]
+/// macro to instruct `schemars` on how to generate JSON schemas for fields
+/// annotated with `#[serde_as(as = "...")]` attributes.
+///
+/// # Examples
+/// Suppose we have our very own `PositiveInt` type. Then we could add support
+/// for generating a schema from it like this
+///
+/// ```
+/// # extern crate schemars_1 as schemars;
+/// # use serde::{Serialize, Serializer, Deserialize, Deserializer};
+/// # use serde_with::{SerializeAs, DeserializeAs};
+/// use serde_with::schemars_1::JsonSchemaAs;
+/// use schemars::{json_schema, SchemaGenerator, Schema};
+/// use std::borrow::Cow;
+///
+/// # #[allow(dead_code)]
+/// struct PositiveInt;
+///
+/// impl SerializeAs<i32> for PositiveInt {
+///     // ...
+///     # fn serialize_as<S>(&value: &i32, ser: S) -> Result<S::Ok, S::Error>
+///     # where
+///     #    S: Serializer
+///     # {
+///     #    if value < 0 {
+///     #        return Err(serde::ser::Error::custom(
+///     #            "expected a positive integer value, got a negative one"
+///     #        ));
+///     #    }
+///     #
+///     #    value.serialize(ser)
+///     # }
+/// }
+///
+/// impl<'de> DeserializeAs<'de, i32> for PositiveInt {
+///     // ...
+///     # fn deserialize_as<D>(de: D) -> Result<i32, D::Error>
+///     # where
+///     #     D: Deserializer<'de>,
+///     # {
+///     #     match i32::deserialize(de) {
+///     #         Ok(value) if value < 0 => Err(serde::de::Error::custom(
+///     #             "expected a positive integer value, got a negative one"
+///     #         )),
+///     #         value => value
+///     #     }
+///     # }
+/// }
+///
+/// impl JsonSchemaAs<i32> for PositiveInt {
+///     fn schema_name() -> Cow<'static, str> {
+///         "PositiveInt".into()
+///     }
+///
+///     fn json_schema(_: &mut SchemaGenerator) -> Schema {
+///         json_schema!({
+///             "type": "integer",
+///             "minimum": 0
+///         })
+///     }
+/// }
+/// ```
+///
+/// [0]: crate::serde_as
+/// [1]: crate::Schema
+pub trait JsonSchemaAs<T: ?Sized> {
+    /// Whether JSON schemas generated for this type should be included directly
+    /// in arent schemas, rather than being re-used where possible using the `$ref`
+    /// keyword.
+    ///
+    /// For trivial types (such as primitives), this should return `true`. For
+    /// more complex types, it should return `false`. For recursive types, this
+    /// **must** return `false` to prevent infinite cycles when generating schemas.
+    ///
+    /// By default, this returns `false`.
+    fn inline_schema() -> bool {
+        false
+    }
+
+    /// The name of the generated JSON Schema.
+    ///
+    /// This is used as the title for root schemas, and the key within the root's `definitions` property for sub-schemas.
+    ///
+    /// As the schema name is used as as part of `$ref` it has to be a valid URI path segment according to
+    /// [RFC 3986 Section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
+    fn schema_name() -> Cow<'static, str>;
+
+    /// Returns a string that uniquely identifies the schema produced by this type.
+    ///
+    /// This does not have to be a human-readable string, and the value will not itself be included in generated schemas.
+    /// If two types produce different schemas, then they **must** have different `schema_id()`s,
+    /// but two types that produce identical schemas should *ideally* have the same `schema_id()`.
+    ///
+    /// The default implementation returns the same value as `schema_name()`.
+    fn schema_id() -> Cow<'static, str> {
+        Self::schema_name()
+    }
+
+    /// Generates a JSON Schema for this type.
+    ///
+    /// If the returned schema depends on any [inlineable](JsonSchema::inline_schema) schemas, then this method will
+    /// add them to the [`SchemaGenerator`]'s schema definitions.
+    ///
+    /// This should not return a `$ref` schema.
+    fn json_schema(generator: &mut SchemaGenerator) -> Schema;
+}
+
+impl<T, TA> JsonSchema for WrapSchema<T, TA>
+where
+    T: ?Sized,
+    TA: JsonSchemaAs<T>,
+{
+    fn schema_name() -> Cow<'static, str> {
+        TA::schema_name()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        TA::schema_id()
+    }
+
+    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
+        TA::json_schema(generator)
+    }
+
+    fn inline_schema() -> bool {
+        TA::inline_schema()
+    }
+}
+
+//===================================================================
+// Macro helpers
+
+macro_rules! forward_schema {
+    ($fwd:ty) => {
+        fn schema_name() -> Cow<'static, str> {
+            <$fwd as JsonSchema>::schema_name()
+        }
+
+        fn schema_id() -> Cow<'static, str> {
+            <$fwd as JsonSchema>::schema_id()
+        }
+
+        fn json_schema(gen: &mut SchemaGenerator) -> Schema {
+            <$fwd as JsonSchema>::json_schema(gen)
+        }
+
+        fn inline_schema() -> bool {
+            <$fwd as JsonSchema>::inline_schema()
+        }
+    };
+}
+
+//===================================================================
+// Common definitions for various std types
+
+impl<'a, T: 'a, TA: 'a> JsonSchemaAs<&'a T> for &'a TA
+where
+    T: ?Sized,
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(&'a WrapSchema<T, TA>);
+}
+
+impl<'a, T: 'a, TA: 'a> JsonSchemaAs<&'a mut T> for &'a mut TA
+where
+    T: ?Sized,
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(&'a mut WrapSchema<T, TA>);
+}
+
+impl<T, TA> JsonSchemaAs<Option<T>> for Option<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Option<WrapSchema<T, TA>>);
+}
+
+impl<T, TA> JsonSchemaAs<Box<T>> for Box<TA>
+where
+    T: ?Sized,
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Box<WrapSchema<T, TA>>);
+}
+
+impl<T, TA> JsonSchemaAs<Rc<T>> for Rc<TA>
+where
+    T: ?Sized,
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Rc<WrapSchema<T, TA>>);
+}
+
+impl<T, TA> JsonSchemaAs<Arc<T>> for Arc<TA>
+where
+    T: ?Sized,
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Arc<WrapSchema<T, TA>>);
+}
+
+impl<T, TA> JsonSchemaAs<Vec<T>> for Vec<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Vec<WrapSchema<T, TA>>);
+}
+
+impl<T, TA> JsonSchemaAs<VecDeque<T>> for VecDeque<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(VecDeque<WrapSchema<T, TA>>);
+}
+
+// schemars only requires that V implement JsonSchema for BTreeMap<K, V>
+impl<K, V, KA, VA> JsonSchemaAs<BTreeMap<K, V>> for BTreeMap<KA, VA>
+where
+    KA: JsonSchemaAs<K>,
+    VA: JsonSchemaAs<V>,
+{
+    forward_schema!(BTreeMap<WrapSchema<K, KA>, WrapSchema<V, VA>>);
+}
+
+// schemars only requires that V implement JsonSchema for HashMap<K, V>
+#[cfg(feature = "std")]
+impl<K, V, S, KA, VA> JsonSchemaAs<HashMap<K, V, S>> for HashMap<KA, VA, S>
+where
+    KA: JsonSchemaAs<K>,
+    VA: JsonSchemaAs<V>,
+{
+    forward_schema!(HashMap<WrapSchema<K, KA>, WrapSchema<V, VA>, S>);
+}
+
+impl<T, TA> JsonSchemaAs<BTreeSet<T>> for BTreeSet<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(BTreeSet<WrapSchema<T, TA>>);
+}
+
+#[cfg(feature = "std")]
+impl<T, TA, S> JsonSchemaAs<T> for HashSet<TA, S>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(HashSet<WrapSchema<T, TA>, S>);
+}
+
+impl<T, TA> JsonSchemaAs<Bound<T>> for Bound<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Bound<WrapSchema<T, TA>>);
+}
+
+impl<T, TA> JsonSchemaAs<Range<T>> for Range<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Range<WrapSchema<T, TA>>);
+}
+
+// Note: Not included in `schemars`
+// impl<T, TA> JsonSchemaAs<RangeFrom<T>> for RangeFrom<TA>
+// where
+//     TA: JsonSchemaAs<T>,
+// {
+//     forward_schema!(RangeFrom<WrapSchema<T, TA>>);
+// }
+
+// impl<T, TA> JsonSchemaAs<RangeTo<T>> for RangeTo<TA>
+// where
+//     TA: JsonSchemaAs<T>,
+// {
+//     forward_schema!(RangeTo<WrapSchema<T, TA>>);
+// }
+
+impl<T, TA> JsonSchemaAs<RangeInclusive<T>> for RangeInclusive<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(RangeInclusive<WrapSchema<T, TA>>);
+}
+
+impl<T, TA, const N: usize> JsonSchemaAs<[T; N]> for [TA; N]
+where
+    TA: JsonSchemaAs<T>,
+{
+    fn schema_name() -> Cow<'static, str> {
+        format!("[{}; {}]", <WrapSchema<T, TA>>::schema_name(), N).into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        format!("[{}; {}]", <WrapSchema<T, TA>>::schema_id(), N).into()
+    }
+
+    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
+        let (max, min) = match N.try_into() {
+            Ok(len) => (Some(len), Some(len)),
+            Err(_) => (None, Some(u32::MAX)),
+        };
+
+        json_schema!({
+            "type": "array",
+            "items": generator.subschema_for::<WrapSchema<T, TA>>(),
+            "maxItems": max,
+            "minItems": min
+        })
+    }
+
+    fn inline_schema() -> bool {
+        true
+    }
+}
+
+macro_rules! schema_for_tuple {
+    (
+        ( $( $ts:ident )+ )
+        ( $( $as:ident )+ )
+    ) => {
+        impl<$($ts,)+ $($as,)+> JsonSchemaAs<($($ts,)+)> for ($($as,)+)
+        where
+            $( $as: JsonSchemaAs<$ts>, )+
+        {
+            forward_schema!(( $( WrapSchema<$ts, $as>, )+ ));
+        }
+    }
+}
+
+impl JsonSchemaAs<()> for () {
+    forward_schema!(());
+}
+
+// schemars only implements JsonSchema for tuples up to 15 elements so we do
+// the same here.
+schema_for_tuple!((T0)(A0));
+schema_for_tuple!((T0 T1) (A0 A1));
+schema_for_tuple!((T0 T1 T2) (A0 A1 A2));
+schema_for_tuple!((T0 T1 T2 T3) (A0 A1 A2 A3));
+schema_for_tuple!((T0 T1 T2 T3 T4) (A0 A1 A2 A3 A4));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5) (A0 A1 A2 A3 A4 A5));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6) (A0 A1 A2 A3 A4 A5 A6));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7) (A0 A1 A2 A3 A4 A5 A6 A7));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8) (A0 A1 A2 A3 A4 A5 A6 A7 A8));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8 T9) (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10));
+schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11) (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11));
+schema_for_tuple!(
+    (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12)
+    (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12)
+);
+schema_for_tuple!(
+    (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13)
+    (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13)
+);
+schema_for_tuple!(
+    (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
+    (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14)
+);
+schema_for_tuple!(
+    (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
+    (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15)
+);
+
+//===================================================================
+// Impls for serde_with types.
+
+impl<T: JsonSchema> JsonSchemaAs<T> for Same {
+    forward_schema!(T);
+}
+
+impl<T> JsonSchemaAs<T> for DisplayFromStr {
+    forward_schema!(String);
+}
+
+impl JsonSchemaAs<bool> for BoolFromInt<Strict> {
+    fn schema_name() -> Cow<'static, str> {
+        "BoolFromInt<Strict>".into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        "serde_with::BoolFromInt<Strict>".into()
+    }
+
+    fn json_schema(_: &mut SchemaGenerator) -> Schema {
+        json_schema!({
+            "type": "integer",
+            "minimum": 0.0,
+            "maximum": 1.0
+        })
+    }
+
+    fn inline_schema() -> bool {
+        true
+    }
+}
+
+impl JsonSchemaAs<bool> for BoolFromInt<Flexible> {
+    fn schema_name() -> Cow<'static, str> {
+        "BoolFromInt<Flexible>".into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        "serde_with::BoolFromInt<Flexible>".into()
+    }
+
+    fn json_schema(_: &mut SchemaGenerator) -> Schema {
+        json_schema!({
+            "type": "integer",
+        })
+    }
+
+    fn inline_schema() -> bool {
+        true
+    }
+}
+
+impl<'a, T: 'a> JsonSchemaAs<Cow<'a, T>> for BorrowCow
+where
+    T: ?Sized + ToOwned,
+    Cow<'a, T>: JsonSchema,
+{
+    forward_schema!(Cow<'a, T>);
+}
+
+impl<T> JsonSchemaAs<T> for Bytes {
+    forward_schema!(Vec<u8>);
+}
+
+impl JsonSchemaAs<Vec<u8>> for BytesOrString {
+    fn schema_name() -> Cow<'static, str> {
+        "BytesOrString".into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        "serde_with::BytesOrString".into()
+    }
+
+    fn json_schema(g: &mut SchemaGenerator) -> Schema {
+        json_schema!({
+            "anyOf": [
+                g.subschema_for::<Vec<u8>>(),
+                {
+                    "type": "string",
+                    "writeOnly": true
+                }
+            ]
+        })
+    }
+
+    fn inline_schema() -> bool {
+        true
+    }
+}
+
+impl<T, TA> JsonSchemaAs<T> for DefaultOnError<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(WrapSchema<T, TA>);
+}
+
+impl<T, TA> JsonSchemaAs<T> for DefaultOnNull<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Option<WrapSchema<T, TA>>);
+}
+
+impl<O, T: JsonSchema> JsonSchemaAs<O> for FromInto<T> {
+    forward_schema!(T);
+}
+
+impl<O, T: JsonSchema> JsonSchemaAs<O> for FromIntoRef<T> {
+    forward_schema!(T);
+}
+
+impl<T, U: JsonSchema> JsonSchemaAs<T> for TryFromInto<U> {
+    forward_schema!(U);
+}
+
+impl<T, U: JsonSchema> JsonSchemaAs<T> for TryFromIntoRef<U> {
+    forward_schema!(U);
+}
+
+impl<T, TA, FA> JsonSchemaAs<T> for IfIsHumanReadable<TA, FA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    // serde_json always has `is_human_readable` set to true so we just use the
+    // schema for the human readable variant.
+    forward_schema!(WrapSchema<T, TA>);
+}
+
+macro_rules! schema_for_map {
+    ($type:ty) => {
+        impl<K, V, KA, VA> JsonSchemaAs<$type> for Map<KA, VA>
+        where
+            KA: JsonSchemaAs<K>,
+            VA: JsonSchemaAs<V>,
+        {
+            forward_schema!(WrapSchema<BTreeMap<K, V>, BTreeMap<KA, VA>>);
+        }
+    };
+}
+
+schema_for_map!([(K, V)]);
+schema_for_map!(BTreeSet<(K, V)>);
+schema_for_map!(BinaryHeap<(K, V)>);
+schema_for_map!(Box<[(K, V)]>);
+schema_for_map!(LinkedList<(K, V)>);
+schema_for_map!(Vec<(K, V)>);
+schema_for_map!(VecDeque<(K, V)>);
+
+#[cfg(feature = "std")]
+impl<K, V, S, KA, VA> JsonSchemaAs<HashSet<(K, V), S>> for Map<KA, VA>
+where
+    KA: JsonSchemaAs<K>,
+    VA: JsonSchemaAs<V>,
+{
+    forward_schema!(WrapSchema<BTreeMap<K, V>, BTreeMap<KA, VA>>);
+}
+
+impl<T> JsonSchemaAs<Vec<T>> for EnumMap
+where
+    T: JsonSchema,
+{
+    fn schema_name() -> Cow<'static, str> {
+        format!("EnumMap({})", T::schema_name()).into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        format!("serde_with::EnumMap({})", T::schema_id()).into()
+    }
+
+    // We generate the schema here by going through all the variants of the
+    // enum (the oneOf property) and sticking all their properties onto an
+    // object.
+    //
+    // This will be wrong if the object is not an externally tagged enum but in
+    // that case serialization and deserialization will fail so it is probably
+    // OK.
+    fn json_schema(g: &mut SchemaGenerator) -> Schema {
+        let mut inner_schema = T::json_schema(g);
+        let inner = inner_schema.ensure_object();
+
+        let one_of = match inner.get_mut("oneOf") {
+            Some(Value::Array(one_of)) => one_of,
+            _ => return inner_schema,
+        };
+
+        let mut properties = serde_json::Map::new();
+        for schema in one_of {
+            let schema = match schema {
+                Value::Object(schema) => schema,
+                _ => continue,
+            };
+
+            if let Some(Value::Object(props)) = schema.get_mut("properties") {
+                properties.extend(core::mem::take(props));
+            }
+        }
+
+        json_schema!({
+            "type": "object",
+            "properties": properties,
+            "additionalProperties": false
+        })
+    }
+
+    fn inline_schema() -> bool {
+        false
+    }
+}
+
+impl<T, TA> WrapSchema<Vec<T>, KeyValueMap<TA>>
+where
+    TA: JsonSchemaAs<T>,
+{
+    /// Transform a schema from the entry type of a `KeyValueMap<T>` to the
+    /// resulting field type.
+    ///
+    /// This usually means doing one of two things:
+    /// 1. removing the `$key$` property from an object, or,
+    /// 2. removing the first item from an array.
+    ///
+    /// We also need to adjust any fields that control the number of items or
+    /// properties allowed such as `(max|min)_properties` or `(max|min)_items`.
+    ///
+    /// This is mostly straightforward. Where things get hairy is when dealing
+    /// with subschemas. JSON schemas allow you to build the schema for an
+    /// object by combining multiple subschemas:
+    /// - You can match exactly one of a set of subschemas (`one_of`).
+    /// - You can match any of a set of subschemas (`any_of`).
+    /// - You can match all of a set of subschemas (`all_of`).
+    ///
+    /// Unfortunately for us, we need to handle all of these options by recursing
+    /// into the subschemas and applying the same transformations as above.
+    fn kvmap_transform_schema_1(g: &mut SchemaGenerator, schema: &mut Schema) {
+        let mut parents = Vec::new();
+
+        let mut value = if let Some(object) = schema.as_object_mut() {
+            Value::Object(core::mem::take(object))
+        } else if let Some(value) = schema.as_bool() {
+            Value::Bool(value)
+        } else {
+            unreachable!()
+        };
+
+        Self::kvmap_transform_schema_impl_1(g, &mut value, &mut parents, 0);
+        *schema = Schema::try_from(value).expect("modified value was not an object or boolean");
+    }
+
+    fn kvmap_transform_schema_impl_1(
+        g: &mut SchemaGenerator,
+        schema: &mut Value,
+        parents: &mut Vec<String>,
+        depth: u32,
+    ) {
+        if depth > 8 {
+            return;
+        }
+
+        let mut done = false;
+        let schema = match schema.as_object_mut() {
+            Some(schema) => schema,
+            _ => return,
+        };
+
+        // The schema is a reference to a schema defined elsewhere.
+        //
+        // If possible we replace it with its definition but if that is not
+        // available then we give up and leave it as-is.
+        let mut parents = if let Some(reference) = &schema.get("$ref") {
+            let reference = match reference {
+                Value::String(reference) => &**reference,
+                // $ref is invalid, skip
+                _ => return,
+            };
+
+            let name = match Self::resolve_reference_1(g, reference) {
+                Some(name) => name,
+                // Reference is defined elsewhere, nothing we can do.
+                None => return,
+            };
+
+            // We are in a recursive reference loop. No point in continuing.
+            if parents.iter().any(|parent| parent == name) {
+                return;
+            }
+
+            let name = name.to_owned();
+            *schema = match g.definitions().get(&name) {
+                Some(Value::Object(schema)) => schema.clone(),
+                _ => return,
+            };
+
+            parents.push(name);
+            DropGuard::new(parents, |parents| drop(parents.pop()))
+        } else {
+            DropGuard::unguarded(parents)
+        };
+
+        // We do comparisons here to avoid lifetime conflicts below
+        let ty = match schema.get("type") {
+            Some(Value::String(ty)) if ty == "object" => Some("object"),
+            Some(Value::String(ty)) if ty == "array" => Some("array"),
+            _ => None,
+        };
+
+        if ty == Some("object") {
+            // For objects KeyValueMap uses the $key$ property so we need to remove it from
+            // the inner schema.
+
+            if let Some(Value::Object(properties)) = schema.get_mut("properties") {
+                done |= properties.remove("$key$").is_some();
+            }
+
+            if let Some(Value::Array(required)) = schema.get_mut("required") {
+                required.retain(|req| match req {
+                    Value::String(key) if key == "$key$" => {
+                        done = true;
+                        false
+                    }
+                    _ => true,
+                });
+            }
+
+            if let Some(Value::Number(max)) = schema.get_mut("maxProperties") {
+                *max = max.saturating_sub(1);
+            }
+
+            if let Some(Value::Number(min)) = schema.get_mut("minProperties") {
+                *min = min.saturating_sub(1);
+            }
+        }
+
+        if ty == Some("array") {
+            // For arrays KeyValueMap uses the first array element so we need to remove it
+            // from the inner schema.
+
+            if let Some(Value::Array(items)) = schema.get_mut("prefixItems") {
+                // If the array is empty then the leading element may be following the
+                // additionalItem schema. In that case we do nothing.
+                if !items.is_empty() {
+                    items.remove(0);
+                    done = true;
+                }
+            }
+
+            if let Some(Value::Array(items)) = schema.get_mut("items") {
+                // If the array is empty then the leading element may be following the
+                // additionalItem schema. In that case we do nothing.
+                if !items.is_empty() {
+                    items.remove(0);
+                    done = true;
+                }
+            }
+
+            if let Some(Value::Number(max)) = schema.get_mut("maxItems") {
+                *max = max.saturating_sub(1);
+            }
+
+            if let Some(Value::Number(min)) = schema.get_mut("minItems") {
+                *min = min.saturating_sub(1);
+            }
+        }
+
+        // We've already modified the schema so there's no need to do more work.
+        if done {
+            return;
+        }
+
+        if let Some(Value::Array(one_of)) = schema.get_mut("oneOf") {
+            for subschema in one_of {
+                Self::kvmap_transform_schema_impl_1(g, subschema, &mut parents, depth + 1);
+            }
+        }
+
+        if let Some(Value::Array(any_of)) = schema.get_mut("anyOf") {
+            for subschema in any_of {
+                Self::kvmap_transform_schema_impl_1(g, subschema, &mut parents, depth + 1);
+            }
+        }
+
+        if let Some(Value::Array(all_of)) = schema.get_mut("allOf") {
+            for subschema in all_of {
+                Self::kvmap_transform_schema_impl_1(g, subschema, &mut parents, depth + 1);
+            }
+        }
+    }
+
+    fn resolve_reference_1<'a>(g: &mut SchemaGenerator, reference: &'a str) -> Option<&'a str> {
+        // We can only resolve references that are contained within the current
+        // schema.
+        let reference = reference.strip_prefix('#')?;
+
+        let defpath: &str = &g.settings().definitions_path;
+        let defpath = defpath.strip_prefix("#").unwrap_or(defpath);
+
+        let mut reference = reference.strip_prefix(defpath)?;
+        if !defpath.ends_with('/') {
+            reference = reference.strip_prefix('/').unwrap_or(reference);
+        }
+
+        Some(reference)
+    }
+}
+
+impl<T, TA> JsonSchemaAs<Vec<T>> for KeyValueMap<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    fn schema_name() -> Cow<'static, str> {
+        format!("KeyValueMap({})", <WrapSchema<T, TA>>::schema_name()).into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        format!(
+            "serde_with::KeyValueMap({})",
+            <WrapSchema<T, TA>>::schema_id()
+        )
+        .into()
+    }
+
+    fn json_schema(g: &mut SchemaGenerator) -> Schema {
+        let mut value = <WrapSchema<T, TA>>::json_schema(g);
+        <WrapSchema<Vec<T>, KeyValueMap<TA>>>::kvmap_transform_schema_1(g, &mut value);
+
+        json_schema!({
+            "type": "object",
+            "additionalProperties": value
+        })
+    }
+}
+
+impl<K, V, KA, VA, const N: usize> JsonSchemaAs<[(K, V); N]> for Map<KA, VA>
+where
+    KA: JsonSchemaAs<K>,
+    VA: JsonSchemaAs<V>,
+{
+    forward_schema!(WrapSchema<BTreeMap<K, V>, BTreeMap<KA, VA>>);
+}
+
+macro_rules! map_first_last_wins_schema {
+    ($(=> $extra:ident)? $type:ty) => {
+        impl<K, V, $($extra,)? KA, VA> JsonSchemaAs<$type> for MapFirstKeyWins<KA, VA>
+        where
+            KA: JsonSchemaAs<K>,
+            VA: JsonSchemaAs<V>,
+        {
+            forward_schema!(BTreeMap<WrapSchema<K, KA>, WrapSchema<V, VA>>);
+        }
+
+        impl<K, V, $($extra,)? KA, VA> JsonSchemaAs<$type> for MapPreventDuplicates<KA, VA>
+        where
+            KA: JsonSchemaAs<K>,
+            VA: JsonSchemaAs<V>,
+        {
+            forward_schema!(BTreeMap<WrapSchema<K, KA>, WrapSchema<V, VA>>);
+        }
+    }
+}
+
+map_first_last_wins_schema!(BTreeMap<K, V>);
+#[cfg(feature = "std")]
+map_first_last_wins_schema!(=> S HashMap<K, V, S>);
+#[cfg(feature = "hashbrown_0_14")]
+map_first_last_wins_schema!(=> S hashbrown_0_14::HashMap<K, V, S>);
+#[cfg(feature = "hashbrown_0_15")]
+map_first_last_wins_schema!(=> S hashbrown_0_15::HashMap<K, V, S>);
+#[cfg(feature = "indexmap_1")]
+map_first_last_wins_schema!(=> S indexmap_1::IndexMap<K, V, S>);
+#[cfg(feature = "indexmap_2")]
+map_first_last_wins_schema!(=> S indexmap_2::IndexMap<K, V, S>);
+
+impl<T, TA> JsonSchemaAs<Vec<T>> for OneOrMany<TA, PreferOne>
+where
+    TA: JsonSchemaAs<T>,
+{
+    fn schema_name() -> Cow<'static, str> {
+        format!(
+            "OneOrMany({},PreferOne)",
+            <WrapSchema<T, TA>>::schema_name()
+        )
+        .into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        format!(
+            "serde_with::OneOrMany({},PreferOne)",
+            <WrapSchema<T, TA>>::schema_id()
+        )
+        .into()
+    }
+
+    fn json_schema(g: &mut SchemaGenerator) -> Schema {
+        let single = g.subschema_for::<WrapSchema<T, TA>>();
+
+        json_schema!({
+            "anyOf": [
+                single,
+                {
+                    "type": "array",
+                    "items": single
+                }
+            ]
+        })
+    }
+}
+
+impl<T, TA> JsonSchemaAs<Vec<T>> for OneOrMany<TA, PreferMany>
+where
+    TA: JsonSchemaAs<T>,
+{
+    fn schema_name() -> Cow<'static, str> {
+        format!(
+            "OneOrMany<{}, PreferMany>",
+            <WrapSchema<T, TA>>::schema_name()
+        )
+        .into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        format!(
+            "serde_with::OneOrMany<{}, PreferMany>",
+            <WrapSchema<T, TA>>::schema_id()
+        )
+        .into()
+    }
+
+    fn json_schema(g: &mut SchemaGenerator) -> Schema {
+        let inner = g.subschema_for::<WrapSchema<T, TA>>();
+
+        json_schema!({
+            "anyOf": [
+                {
+                    "writeOnly": true,
+                    "allOf": [
+                        inner
+                    ],
+                },
+                {
+                    "type": "array",
+                    "items": inner
+                }
+            ]
+        })
+    }
+}
+
+macro_rules! schema_for_pickfirst {
+    ($( $param:ident )+) => {
+        impl<T, $($param,)+> JsonSchemaAs<T> for PickFirst<($( $param, )+)>
+        where
+            $( $param: JsonSchemaAs<T>, )+
+        {
+            fn schema_name() -> Cow<'static, str> {
+                format!(
+                    concat!(
+                        "PickFirst(",
+                        $( "{", stringify!($param), "}", )+
+                        ")"
+                    ),
+                    $( $param = <WrapSchema<T, $param>>::schema_name(), )+
+                )
+                .into()
+            }
+
+            fn schema_id() -> Cow<'static, str> {
+                format!(
+                    concat!(
+                        "serde_with::PickFirst(",
+                        $( "{", stringify!($param), "}", )+
+                        ")"
+                    ),
+                    $( $param = <WrapSchema<T, $param>>::schema_id(), )+
+                )
+                .into()
+            }
+
+            fn json_schema(g: &mut SchemaGenerator) -> Schema {
+                let mut first = true;
+                let subschemas = alloc::vec![$(
+                    {
+                        let is_first = core::mem::replace(&mut first, false);
+                        let schema = g.subschema_for::<WrapSchema<T, $param>>();
+
+                        if !is_first {
+                            json_schema!({
+                                "writeOnly": true,
+                                "allOf": [schema]
+                            })
+                        } else {
+                            schema
+                        }
+                    }
+                ),+];
+
+                json_schema!({
+                    "anyOf": subschemas
+                })
+            }
+        }
+    }
+}
+
+schema_for_pickfirst!(A);
+schema_for_pickfirst!(A B);
+schema_for_pickfirst!(A B C);
+schema_for_pickfirst!(A B C D);
+
+impl<T, TA> JsonSchemaAs<T> for SetLastValueWins<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    fn schema_id() -> Cow<'static, str> {
+        format!(
+            "serde_with::SetLastValueWins<{}>",
+            <WrapSchema<T, TA> as JsonSchema>::schema_id()
+        )
+        .into()
+    }
+
+    fn schema_name() -> Cow<'static, str> {
+        format!(
+            "SetLastValueWins<{}>",
+            <WrapSchema<T, TA> as JsonSchema>::schema_name()
+        )
+        .into()
+    }
+
+    fn json_schema(g: &mut SchemaGenerator) -> Schema {
+        let mut schema = <WrapSchema<T, TA> as JsonSchema>::json_schema(g);
+        let object = schema.ensure_object();
+
+        // We explicitly allow duplicate items since the whole point of
+        // SetLastValueWins is to take the duplicate value.
+        object.remove("uniqueItems");
+
+        schema
+    }
+
+    fn inline_schema() -> bool {
+        <WrapSchema<T, TA> as JsonSchema>::inline_schema()
+    }
+}
+
+impl<T, TA> JsonSchemaAs<T> for SetPreventDuplicates<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(WrapSchema<T, TA>);
+}
+
+impl<SEP, T, TA> JsonSchemaAs<T> for StringWithSeparator<SEP, TA>
+where
+    SEP: Separator,
+{
+    forward_schema!(String);
+}
+
+impl<T, TA> JsonSchemaAs<Vec<T>> for VecSkipError<TA>
+where
+    TA: JsonSchemaAs<T>,
+{
+    forward_schema!(Vec<WrapSchema<T, TA>>);
+}
+
+mod timespan {
+    use super::*;
+
+    // #[non_exhaustive] is not actually necessary here but it should
+    // help avoid warnings about semver breakage if this ever changes.
+    #[non_exhaustive]
+    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+    pub enum TimespanTargetType {
+        String,
+        F64,
+        U64,
+        I64,
+    }
+
+    impl TimespanTargetType {
+        pub const fn is_signed(self) -> bool {
+            !matches!(self, Self::U64)
+        }
+    }
+
+    /// Internal helper trait used to constrain which types we implement
+    /// `JsonSchemaAs<T>` for.
+    pub trait TimespanSchemaTarget<F> {
+        /// The underlying type.
+        ///
+        /// This is mainly used to decide which variant of the resulting schema
+        /// should be marked as `write_only: true`.
+        const TYPE: TimespanTargetType;
+
+        /// Whether the target type is signed.
+        ///
+        /// This is only true for `std::time::Duration`.
+        const SIGNED: bool = true;
+    }
+
+    #[cfg_attr(not(feature = "std"), allow(unused_macros))]
+    macro_rules! timespan_type_of {
+        (String) => {
+            TimespanTargetType::String
+        };
+        (f64) => {
+            TimespanTargetType::F64
+        };
+        (i64) => {
+            TimespanTargetType::I64
+        };
+        (u64) => {
+            TimespanTargetType::U64
+        };
+    }
+
+    #[cfg_attr(not(feature = "std"), allow(unused_macros))]
+    macro_rules! declare_timespan_target {
+        ( $target:ty { $($format:ident),* $(,)? } ) => {
+            $(
+                impl TimespanSchemaTarget<$format> for $target {
+                    const TYPE: TimespanTargetType = timespan_type_of!($format);
+                }
+            )*
+        }
+    }
+
+    impl TimespanSchemaTarget<u64> for Duration {
+        const TYPE: TimespanTargetType = TimespanTargetType::U64;
+        const SIGNED: bool = false;
+    }
+
+    impl TimespanSchemaTarget<f64> for Duration {
+        const TYPE: TimespanTargetType = TimespanTargetType::F64;
+        const SIGNED: bool = false;
+    }
+
+    impl TimespanSchemaTarget<String> for Duration {
+        const TYPE: TimespanTargetType = TimespanTargetType::String;
+        const SIGNED: bool = false;
+    }
+
+    #[cfg(feature = "std")]
+    declare_timespan_target!(SystemTime { i64, f64, String });
+
+    #[cfg(feature = "chrono_0_4")]
+    declare_timespan_target!(::chrono_0_4::Duration { i64, f64, String });
+    #[cfg(feature = "chrono_0_4")]
+    declare_timespan_target!(::chrono_0_4::DateTime<::chrono_0_4::Utc> { i64, f64, String });
+    #[cfg(all(feature = "chrono_0_4", feature = "std"))]
+    declare_timespan_target!(::chrono_0_4::DateTime<::chrono_0_4::Local> { i64, f64, String });
+    #[cfg(feature = "chrono_0_4")]
+    declare_timespan_target!(::chrono_0_4::NaiveDateTime { i64, f64, String });
+
+    #[cfg(feature = "time_0_3")]
+    declare_timespan_target!(::time_0_3::Duration { i64, f64, String });
+    #[cfg(feature = "time_0_3")]
+    declare_timespan_target!(::time_0_3::OffsetDateTime { i64, f64, String });
+    #[cfg(feature = "time_0_3")]
+    declare_timespan_target!(::time_0_3::PrimitiveDateTime { i64, f64, String });
+}
+
+use self::timespan::{TimespanSchemaTarget, TimespanTargetType};
+
+/// Internal type used for the base impls on `DurationXXX` and `TimestampYYY` types.
+///
+/// This allows the `JsonSchema` impls that are Strict to be generic without
+/// committing to it as part of the public API.
+struct Timespan<Format, Strictness>(PhantomData<(Format, Strictness)>);
+
+impl<T, F> JsonSchemaAs<T> for Timespan<F, Strict>
+where
+    T: TimespanSchemaTarget<F>,
+    F: Format + JsonSchema,
+{
+    forward_schema!(F);
+}
+
+impl TimespanTargetType {
+    pub(crate) fn to_flexible_schema(self, signed: bool) -> Schema {
+        let mut number = json_schema!({
+            "type": "number"
+        });
+
+        if !signed {
+            number
+                .ensure_object()
+                .insert("minimum".into(), serde_json::json!(0.0));
+        }
+
+        // This is a more lenient version of the regex used to determine
+        // whether JSON numbers are valid. Specifically, it allows multiple
+        // leading zeroes whereas that is illegal in JSON.
+        let regex = r#"[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?"#;
+        let mut string = json_schema!({
+            "type": "string",
+            "pattern": match signed {
+                true => format!("^-?{regex}$"),
+                false => format!("^{regex}$"),
+            }
+        });
+
+        if self == Self::String {
+            number
+                .ensure_object()
+                .insert("writeOnly".into(), true.into());
+        } else {
+            string
+                .ensure_object()
+                .insert("writeOnly".into(), true.into());
+        }
+
+        json_schema!({
+            "oneOf": [number, string]
+        })
+    }
+
+    pub(crate) fn schema_id(self) -> &'static str {
+        match self {
+            Self::String => "serde_with::FlexibleStringTimespan",
+            Self::F64 => "serde_with::FlexibleF64Timespan",
+            Self::U64 => "serde_with::FlexibleU64Timespan",
+            Self::I64 => "serde_with::FlexibleI64Timespan",
+        }
+    }
+}
+
+impl<T, F> JsonSchemaAs<T> for Timespan<F, Flexible>
+where
+    T: TimespanSchemaTarget<F>,
+    F: Format + JsonSchema,
+{
+    fn schema_name() -> Cow<'static, str> {
+        <T as TimespanSchemaTarget<F>>::TYPE
+            .schema_id()
+            .strip_prefix("serde_with::")
+            .expect("schema id did not start with `serde_with::` - this is a bug")
+            .into()
+    }
+
+    fn schema_id() -> Cow<'static, str> {
+        <T as TimespanSchemaTarget<F>>::TYPE.schema_id().into()
+    }
+
+    fn json_schema(_: &mut SchemaGenerator) -> Schema {
+        <T as TimespanSchemaTarget<F>>::TYPE
+            .to_flexible_schema(<T as TimespanSchemaTarget<F>>::SIGNED)
+    }
+
+    fn inline_schema() -> bool {
+        true
+    }
+}
+
+macro_rules! forward_duration_schema {
+    ($ty:ident) => {
+        impl<T, F> JsonSchemaAs<T> for $ty<F, Strict>
+        where
+            T: TimespanSchemaTarget<F>,
+            F: Format + JsonSchema
+        {
+            forward_schema!(WrapSchema<T, Timespan<F, Strict>>);
+        }
+
+        impl<T, F> JsonSchemaAs<T> for $ty<F, Flexible>
+        where
+            T: TimespanSchemaTarget<F>,
+            F: Format + JsonSchema
+        {
+            forward_schema!(WrapSchema<T, Timespan<F, Flexible>>);
+        }
+    };
+}
+
+forward_duration_schema!(DurationSeconds);
+forward_duration_schema!(DurationMilliSeconds);
+forward_duration_schema!(DurationMicroSeconds);
+forward_duration_schema!(DurationNanoSeconds);
+
+forward_duration_schema!(DurationSecondsWithFrac);
+forward_duration_schema!(DurationMilliSecondsWithFrac);
+forward_duration_schema!(DurationMicroSecondsWithFrac);
+forward_duration_schema!(DurationNanoSecondsWithFrac);
+
+forward_duration_schema!(TimestampSeconds);
+forward_duration_schema!(TimestampMilliSeconds);
+forward_duration_schema!(TimestampMicroSeconds);
+forward_duration_schema!(TimestampNanoSeconds);
+
+forward_duration_schema!(TimestampSecondsWithFrac);
+forward_duration_schema!(TimestampMilliSecondsWithFrac);
+forward_duration_schema!(TimestampMicroSecondsWithFrac);
+forward_duration_schema!(TimestampNanoSecondsWithFrac);
+
+//===================================================================
+// Extra internal helper structs
+
+struct DropGuard<T, F: FnOnce(T)> {
+    value: ManuallyDrop<T>,
+    guard: Option<F>,
+}
+
+impl<T, F: FnOnce(T)> DropGuard<T, F> {
+    pub fn new(value: T, guard: F) -> Self {
+        Self {
+            value: ManuallyDrop::new(value),
+            guard: Some(guard),
+        }
+    }
+
+    pub fn unguarded(value: T) -> Self {
+        Self {
+            value: ManuallyDrop::new(value),
+            guard: None,
+        }
+    }
+}
+
+impl<T, F: FnOnce(T)> Deref for DropGuard<T, F> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.value
+    }
+}
+
+impl<T, F: FnOnce(T)> DerefMut for DropGuard<T, F> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.value
+    }
+}
+
+impl<T, F: FnOnce(T)> Drop for DropGuard<T, F> {
+    fn drop(&mut self) {
+        // SAFETY: value is known to be initialized since we only ever remove it here.
+        let value = unsafe { ManuallyDrop::take(&mut self.value) };
+
+        if let Some(guard) = self.guard.take() {
+            guard(value);
+        }
+    }
+}
+
+trait NumberExt: Sized {
+    fn saturating_sub(&self, count: u64) -> Self;
+}
+
+impl NumberExt for serde_json::Number {
+    fn saturating_sub(&self, count: u64) -> Self {
+        if let Some(v) = self.as_u64() {
+            return v.saturating_sub(count).into();
+        }
+
+        if let Some(v) = self.as_i64() {
+            if count < i64::MAX as u64 {
+                return v.saturating_sub(count as _).into();
+            }
+        }
+
+        if let Some(v) = self.as_f64() {
+            return serde_json::Number::from_f64(v - (count as f64))
+                .expect("saturating_sub resulted in NaN");
+        }
+
+        unreachable!()
+    }
+}
diff --git a/serde_with_macros/CHANGELOG.md b/serde_with_macros/CHANGELOG.md
index 48a104b..7730bad 100644
--- a/serde_with_macros/CHANGELOG.md
+++ b/serde_with_macros/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 
 ## [Unreleased]
 
+### Added
+
+* Added support for `schemars` v1 under the `schemars_1` feature flag
+
 ## [3.13.0] - 2025-06-14
 
 ### Added
diff --git a/serde_with_macros/Cargo.toml b/serde_with_macros/Cargo.toml
index 6a92a95..dedde17 100644
--- a/serde_with_macros/Cargo.toml
+++ b/serde_with_macros/Cargo.toml
@@ -35,6 +35,7 @@ maintenance = { status = "actively-developed" }
 [features]
 schemars_0_8 = []
 schemars_0_9 = []
+schemars_1 = []
 
 [dependencies]
 darling = "0.20.0"
diff --git a/serde_with_macros/src/lib.rs b/serde_with_macros/src/lib.rs
index 9edd44c..4a0adb9 100644
--- a/serde_with_macros/src/lib.rs
+++ b/serde_with_macros/src/lib.rs
@@ -573,7 +573,7 @@ fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool {
 /// ```
 ///
 /// # A note on `schemars` integration
-/// When the `schemars_0_8` or `schemars_0_9` features are enabled this macro
+/// When the `schemars_0_8`, `schemars_0_9`, or `schemars_1` features are enabled this macro
 /// will scan for
 /// `#[derive(JsonSchema)]` attributes and, if found, will add
 /// `#[schemars(with = "Schema<T, ...>")]` annotations to any fields with a
@@ -615,7 +615,12 @@ pub fn serde_as(args: TokenStream, input: TokenStream) -> TokenStream {
                 .unwrap_or_else(|| syn::parse_quote!(::serde_with));
 
             let schemars_config = match container_options.enable_schemars_support {
-                _ if cfg!(not(any(feature = "schemars_0_8", feature = "schemars_0_9"))) => {
+                _ if cfg!(not(any(
+                    feature = "schemars_0_8",
+                    feature = "schemars_0_9",
+                    feature = "schemars_1"
+                ))) =>
+                {
                     SchemaFieldConfig::False
                 }
                 Some(condition) => condition.into(),


Test patch:
diff --git a/serde_with/tests/schemars_1/main.rs b/serde_with/tests/schemars_1/main.rs
new file mode 100644
index 0000000..8397906
--- /dev/null
+++ b/serde_with/tests/schemars_1/main.rs
@@ -0,0 +1,1130 @@
+//! Test Cases
+
+use crate::utils::{check_matches_schema, check_valid_json_schema};
+use core::ops;
+use expect_test::expect_file;
+use schemars::JsonSchema;
+use serde::Serialize;
+use serde_json::json;
+use serde_with::*;
+use std::collections::BTreeSet;
+
+// This avoids us having to add `#[schemars(crate = "::schemars_1")]` all
+// over the place. We're not testing that and it is inconvenient.
+extern crate schemars_1 as schemars;
+
+mod utils;
+
+/// Declare a snapshot tests for a struct.
+///
+/// The snapshot files are stored under the `schemars_1` folder alongside
+/// this test file.
+macro_rules! declare_snapshot_test {
+    {$(
+        $( #[$tattr:meta] )*
+        $test:ident {
+            $( #[$stattr:meta] )*
+            struct $name:ident {
+                $(
+                    $( #[ $fattr:meta ] )*
+                    $field:ident : $ty:ty
+                ),*
+                $(,)?
+            }
+        }
+    )*} => {$(
+        #[test]
+        $(#[$tattr])*
+        fn $test() {
+            #[serde_as]
+            #[derive(JsonSchema, Serialize)]
+            $( #[$stattr] )*
+            struct $name {
+                $(
+                    $( #[$fattr] )*
+                    $field: $ty,
+                )*
+            }
+
+            let schema = schemars::schema_for!($name);
+            let _ = jsonschema::Validator::new(
+                &serde_json::to_value(&schema).expect("generated schema is not valid json"),
+            )
+            .expect("generated schema is not valid");
+
+            let mut schema = serde_json::to_string_pretty(&schema)
+                .expect("schema could not be serialized");
+            schema.push('\n');
+
+            let filename = concat!("./", module_path!(), "::", stringify!($test), ".json")
+                .replace("::", "/")
+                .replace("schemars_1/", "");
+
+            let expected = expect_file![filename];
+            expected.assert_eq(&schema);
+        }
+    )*}
+}
+
+#[test]
+fn schemars_basic() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    #[schemars(crate = "::schemars_1")]
+    struct Basic {
+        /// Basic field, no attribute
+        bare_field: u32,
+
+        /// Field that directly uses `DisplayFromStr`
+        #[serde_as(as = "DisplayFromStr")]
+        display_from_str: u32,
+
+        /// Same does not implement `JsonSchema` directly so this checks that the
+        /// correct schemars attribute was injected.
+        #[serde_as(as = "Same")]
+        same: u32,
+
+        /// This checks that Same still works when wrapped in a box.
+        #[serde_as(as = "Box<Same>")]
+        box_same: Box<u32>,
+
+        /// Same thing, but with a Vec this time.
+        #[serde_as(as = "Vec<_>")]
+        vec_same: Vec<u32>,
+    }
+
+    let schema = schemars::schema_for!(Basic);
+    let mut schema = serde_json::to_string_pretty(&schema).expect("schema could not be serialized");
+    schema.push('\n');
+
+    let expected = expect_file!["./schemars_basic.json"];
+    expected.assert_eq(&schema);
+}
+
+#[test]
+fn schemars_other_cfg_attrs() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    struct Test {
+        #[serde_as(as = "DisplayFromStr")]
+        #[cfg_attr(any(), arbitrary("some" |weird| syntax::<bool, 2>()))]
+        #[cfg_attr(any(), schemars(with = "i32"))]
+        custom: i32,
+    }
+
+    check_matches_schema::<Test>(&json!({
+        "custom": "23",
+    }));
+}
+
+#[test]
+fn schemars_custom_with() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    struct Test {
+        #[serde_as(as = "DisplayFromStr")]
+        #[schemars(with = "i32")]
+        custom: i32,
+
+        #[serde_as(as = "DisplayFromStr")]
+        #[cfg_attr(any(), schemars(with = "i32"))]
+        with_disabled: i32,
+
+        #[serde_as(as = "DisplayFromStr")]
+        #[cfg_attr(all(), schemars(with = "i32"))]
+        always_enabled: i32,
+    }
+
+    check_matches_schema::<Test>(&json!({
+        "custom": 3,
+        "with_disabled": "5",
+        "always_enabled": 7,
+    }));
+}
+
+#[test]
+fn schemars_deserialize_only_bug_735() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    #[schemars(crate = "::schemars_1")]
+    struct Basic {
+        /// Basic field, no attribute
+        bare_field: u32,
+
+        /// Will emit matching schemars attribute
+        #[serde_as(as = "PickFirst<(_, DisplayFromStr)>")]
+        both: u32,
+
+        /// Can emit schemars with `serialize_as`, but it will be ignored
+        #[serde_as(serialize_as = "PickFirst<(_, DisplayFromStr)>")]
+        serialize_only: u32,
+
+        /// schemars doesn't support `deserialize_as`
+        #[serde_as(deserialize_as = "PickFirst<(_, DisplayFromStr)>")]
+        deserialize_only: u32,
+
+        /// Can emit schemars with `serialize_as`, but it will be ignored
+        /// schemars doesn't support `deserialize_as`
+        #[serde_as(
+            serialize_as = "PickFirst<(_, DisplayFromStr)>",
+            deserialize_as = "PickFirst<(_, DisplayFromStr)>"
+        )]
+        serialize_and_deserialize: u32,
+    }
+
+    let schema = schemars::schema_for!(Basic);
+    let mut schema = serde_json::to_string_pretty(&schema).expect("schema could not be serialized");
+    schema.push('\n');
+
+    let expected = expect_file!["./schemars_deserialize_only_bug_735.json"];
+    expected.assert_eq(&schema);
+}
+
+#[test]
+fn schemars_custom_schema_with() {
+    fn custom_int(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
+        use schemars::json_schema;
+
+        json_schema!({
+            "type": "integer"
+        })
+    }
+
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    struct Test {
+        #[serde_as(as = "DisplayFromStr")]
+        #[schemars(schema_with = "custom_int")]
+        custom: i32,
+
+        #[serde_as(as = "DisplayFromStr")]
+        #[cfg_attr(any(), schemars(schema_with = "custom_int"))]
+        with_disabled: i32,
+
+        #[serde_as(as = "DisplayFromStr")]
+        #[cfg_attr(all(), schemars(schema_with = "custom_int"))]
+        always_enabled: i32,
+    }
+
+    check_matches_schema::<Test>(&json!({
+        "custom": 3,
+        "with_disabled": "5",
+        "always_enabled": 7,
+    }));
+}
+
+mod test_std {
+    use super::*;
+    use std::collections::{BTreeMap, VecDeque};
+
+    declare_snapshot_test! {
+        option {
+            struct Test {
+                #[serde_with(as = "Option<_>")]
+                optional: Option<i32>,
+            }
+        }
+
+        vec {
+            struct Test {
+                #[serde_with(as = "Vec<_>")]
+                vec: Vec<String>
+            }
+        }
+
+        vec_deque {
+            struct Test {
+                #[serde_with(as = "VecDeque<_>")]
+                vec_deque: VecDeque<String>
+            }
+        }
+
+        map {
+            struct Test {
+                #[serde_with(as = "BTreeMap<_, _>")]
+                map: BTreeMap<String, i32>
+            }
+        }
+
+        set {
+            struct Test {
+                #[serde_with(as = "BTreeSet<_>")]
+                map: BTreeSet<String>,
+            }
+        }
+
+        tuples {
+            struct Test {
+                #[serde_with(as = "()")]
+                tuple0: (),
+
+                #[serde_with(as = "(_ ,)")]
+                tuple1: (i32,),
+
+                #[serde_with(as = "(_, _)")]
+                tuple2: (i32, i32),
+
+                #[serde_with(as = "(_, _, _)")]
+                tuple3: (i32, i32, String)
+            }
+        }
+    }
+}
+
+mod snapshots {
+    use super::*;
+    use serde_with::formats::*;
+
+    #[allow(dead_code)]
+    #[derive(JsonSchema, Serialize)]
+    enum Mappable {
+        A(i32),
+        B(String),
+        C { c: i32, b: Option<u64> },
+    }
+
+    #[derive(JsonSchema, Serialize)]
+    struct KvMapData {
+        #[serde(rename = "$key$")]
+        key: String,
+
+        a: u32,
+        b: String,
+        c: f32,
+        d: bool,
+    }
+
+    #[allow(dead_code, variant_size_differences)]
+    #[derive(JsonSchema, Serialize)]
+    #[serde(tag = "$key$")]
+    enum KvMapEnum {
+        TypeA { a: u32 },
+        TypeB { b: String },
+        TypeC { c: bool },
+    }
+
+    #[derive(JsonSchema, Serialize)]
+    struct KvMapFlatten {
+        #[serde(flatten)]
+        data: KvMapEnum,
+        extra: bool,
+    }
+
+    declare_snapshot_test! {
+        bytes {
+            struct Test {
+                #[serde_as(as = "Bytes")]
+                bytes: Vec<u8>,
+            }
+        }
+
+        default_on_null {
+            struct Test {
+                #[serde_as(as = "DefaultOnNull<_>")]
+                data: String,
+            }
+        }
+
+        string_with_separator {
+            struct Test {
+                #[serde_as(as = "StringWithSeparator<CommaSeparator, String>")]
+                data: Vec<String>,
+            }
+        }
+
+        from_into {
+            struct Test {
+                #[serde_as(as = "FromInto<u64>")]
+                data: u32,
+            }
+        }
+
+        map {
+            struct Test {
+                #[serde_as(as = "Map<_, _>")]
+                data: Vec<(String, u32)>,
+            }
+        }
+
+        map_fixed {
+            struct Test {
+                #[serde_as(as = "Map<_, _>")]
+                data: [(String, u32); 4],
+            }
+        }
+
+        set_last_value_wins {
+            struct Test {
+                #[serde_as(as = "SetLastValueWins<_>")]
+                data: BTreeSet<u32>,
+            }
+        }
+
+        set_prevent_duplicates {
+            struct Test {
+                #[serde_as(as = "SetPreventDuplicates<_>")]
+                data: BTreeSet<u32>,
+            }
+        }
+
+        duration {
+            struct Test {
+                #[serde_as(as = "DurationSeconds<u64, Flexible>")]
+                seconds: std::time::Duration,
+
+                #[serde_as(as = "DurationSecondsWithFrac<f64, Flexible>")]
+                frac: std::time::Duration,
+
+                #[serde_as(as = "DurationSeconds<String, Flexible>")]
+                flexible_string: std::time::Duration,
+
+                #[serde_as(as = "DurationSeconds<u64, Strict>")]
+                seconds_u64_strict: std::time::Duration,
+
+                #[serde_as(as = "TimestampSeconds<i64, Flexible>")]
+                time_i64: std::time::SystemTime,
+            }
+        }
+
+        enum_map {
+            struct Test {
+                #[serde_as(as = "EnumMap")]
+                data: Vec<Mappable>,
+            }
+        }
+
+        key_value_map {
+            struct Test {
+                #[serde_as(as = "KeyValueMap<_>")]
+                data: Vec<KvMapData>,
+            }
+        }
+
+        key_value_map_enum {
+            struct Test {
+                #[serde_as(as = "KeyValueMap<_>")]
+                data: Vec<KvMapEnum>,
+            }
+        }
+
+        key_value_map_flatten {
+            struct Test {
+                #[serde_as(as = "KeyValueMap<_>")]
+                data: Vec<KvMapFlatten>,
+            }
+        }
+
+        one_or_many_prefer_one {
+            #[serde(transparent)]
+            struct Test {
+                #[serde_as(as = "OneOrMany<_, PreferOne>")]
+                data: Vec<i32>,
+            }
+        }
+
+        pickfirst {
+            #[serde(transparent)]
+            struct Test {
+                #[serde_as(as = "PickFirst<(_, DisplayFromStr)>")]
+                value: u32
+            }
+        }
+
+        pickfirst_nested {
+            #[serde(transparent)]
+            struct Test {
+                #[serde_as(as = "OneOrMany<PickFirst<(_, DisplayFromStr)>>")]
+                optional_value: Vec<u32>
+            }
+        }
+
+        one_or_many_nested {
+            struct Test {
+                #[serde_as(as = "Option<OneOrMany<_>>")]
+                optional_many: Option<Vec<String>>,
+            }
+        }
+    }
+}
+
+mod derive {
+    use super::*;
+
+    #[serde_as]
+    #[derive(Serialize)]
+    #[cfg_attr(all(), derive(JsonSchema))]
+    struct Enabled {
+        #[serde_as(as = "DisplayFromStr")]
+        field: u32,
+    }
+
+    #[allow(dead_code)]
+    #[serde_as]
+    #[derive(Serialize)]
+    #[cfg_attr(any(), derive(JsonSchema))]
+    struct Disabled {
+        // If we are incorrectly adding `#[schemars(with = ...)]` attributes
+        // then we should get an error on this field.
+        #[serde_as(as = "DisplayFromStr")]
+        field: u32,
+    }
+
+    #[test]
+    fn test_enabled_has_correct_schema() {
+        check_valid_json_schema(&Enabled { field: 77 });
+    }
+}
+
+mod array {
+    use super::*;
+
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    struct FixedArray {
+        #[serde_as(as = "[_; 3]")]
+        array: [u32; 3],
+    }
+
+    #[test]
+    fn test_serialized_is_valid() {
+        let array = FixedArray { array: [1, 2, 3] };
+
+        check_valid_json_schema(&array);
+    }
+
+    #[test]
+    fn test_valid_json() {
+        let value = json!({ "array": [1, 2, 3] });
+        check_matches_schema::<FixedArray>(&value);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_too_short() {
+        check_matches_schema::<FixedArray>(&json!({
+            "array": [1],
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_too_long() {
+        check_matches_schema::<FixedArray>(&json!({
+            "array": [1, 2, 3, 4]
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_wrong_item_type() {
+        check_matches_schema::<FixedArray>(&json!({
+            "array": ["1", "2", "3"]
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_oob_item() {
+        check_matches_schema::<FixedArray>(&json!({
+            "array": [-1, 0x1_0000_0000i64, 32]
+        }));
+    }
+}
+
+mod bool_from_int {
+    use super::*;
+    use serde_with::formats::{Flexible, Strict};
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct BoolStrict {
+        #[serde_as(as = "BoolFromInt<Strict>")]
+        value: bool,
+    }
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct BoolFlexible {
+        #[serde_as(as = "BoolFromInt<Flexible>")]
+        value: bool,
+    }
+
+    #[test]
+    fn test_serialized_strict_is_valid() {
+        check_valid_json_schema(&vec![
+            BoolStrict { value: true },
+            BoolStrict { value: false },
+        ]);
+    }
+
+    #[test]
+    fn test_serialized_flexible_is_valid() {
+        check_valid_json_schema(&vec![
+            BoolFlexible { value: true },
+            BoolFlexible { value: false },
+        ]);
+    }
+
+    #[test]
+    #[should_panic]
+    fn strict_out_of_range() {
+        check_matches_schema::<BoolStrict>(&json!({
+            "value": 5
+        }));
+    }
+
+    #[test]
+    fn flexible_out_of_range() {
+        check_matches_schema::<BoolFlexible>(&json!({
+            "value": 5
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn flexible_wrong_type() {
+        check_matches_schema::<BoolFlexible>(&json!({
+            "value": "seven"
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_fractional_value_strict() {
+        check_matches_schema::<BoolStrict>(&json!({
+            "value": 0.5
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_fractional_value_flexible() {
+        check_matches_schema::<BoolFlexible>(&json!({
+            "value": 0.5
+        }));
+    }
+}
+
+mod bytes_or_string {
+    use super::*;
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct Test {
+        #[serde_as(as = "BytesOrString")]
+        bytes: Vec<u8>,
+    }
+
+    #[test]
+    fn test_serialized_is_valid() {
+        check_valid_json_schema(&Test {
+            bytes: b"test".to_vec(),
+        });
+    }
+
+    #[test]
+    fn test_string_valid_json() {
+        check_matches_schema::<Test>(&json!({
+            "bytes": "test string"
+        }));
+    }
+
+    #[test]
+    fn test_bytes_valid_json() {
+        check_matches_schema::<Test>(&json!({
+            "bytes": [1, 2, 3, 4]
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_int_not_valid_json() {
+        check_matches_schema::<Test>(&json!({
+            "bytes": 5
+        }));
+    }
+}
+
+mod enum_map {
+    use super::*;
+
+    #[derive(Serialize, JsonSchema)]
+    struct InnerStruct {
+        c: String,
+        d: f64,
+    }
+
+    #[derive(Serialize, JsonSchema)]
+    enum Inner {
+        A(i32),
+        B(String),
+        C(InnerStruct),
+    }
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    #[serde(transparent)]
+    struct Outer(#[serde_as(as = "EnumMap")] Vec<Inner>);
+
+    #[test]
+    fn test_serialized_is_valid() {
+        check_valid_json_schema(&Outer(vec![
+            Inner::A(5),
+            Inner::B("test".into()),
+            Inner::C(InnerStruct {
+                c: "c".into(),
+                d: -34.0,
+            }),
+        ]));
+    }
+
+    #[test]
+    fn test_matches_expected() {
+        check_matches_schema::<Outer>(&json!({
+            "A": 75,
+            "B": "BBBBBB",
+            "C": {
+                "c": "inner C",
+                "d": 777
+            }
+        }));
+    }
+
+    #[test]
+    fn test_no_fields_required() {
+        check_matches_schema::<Outer>(&json!({}));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_mixed_up_schemas() {
+        check_matches_schema::<Outer>(&json!({
+            "A": "b",
+            "B": 5
+        }));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_invalid_key() {
+        check_matches_schema::<Outer>(&json!({
+            "invalid": 4
+        }));
+    }
+}
+
+mod duration {
+    use super::*;
+    use serde_with::formats::{Flexible, Strict};
+    use std::time::{Duration, SystemTime};
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct DurationTest {
+        #[serde_as(as = "DurationSeconds<u64, Strict>")]
+        strict_u64: Duration,
+
+        #[serde_as(as = "DurationSeconds<String, Strict>")]
+        strict_str: Duration,
+
+        #[serde_as(as = "DurationSecondsWithFrac<f64, Strict>")]
+        strict_f64: Duration,
+
+        #[serde_as(as = "DurationSeconds<u64, Flexible>")]
+        flexible_u64: Duration,
+
+        #[serde_as(as = "DurationSeconds<f64, Flexible>")]
+        flexible_f64: Duration,
+
+        #[serde_as(as = "DurationSeconds<String, Flexible>")]
+        flexible_str: Duration,
+    }
+
+    #[test]
+    fn test_serialized_is_valid() {
+        check_valid_json_schema(&DurationTest {
+            strict_u64: Duration::from_millis(2500),
+            strict_str: Duration::from_millis(2500),
+            strict_f64: Duration::from_millis(2500),
+            flexible_u64: Duration::from_millis(2500),
+            flexible_f64: Duration::from_millis(2500),
+            flexible_str: Duration::from_millis(2500),
+        });
+    }
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct FlexibleU64Duration(#[serde_as(as = "DurationSeconds<u64, Flexible>")] Duration);
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct FlexibleStringDuration(#[serde_as(as = "DurationSeconds<String, Flexible>")] Duration);
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct FlexibleTimestamp(#[serde_as(as = "TimestampSeconds<i64, Flexible>")] SystemTime);
+
+    #[test]
+    fn test_string_as_flexible_u64() {
+        check_matches_schema::<FlexibleU64Duration>(&json!("32"));
+    }
+
+    #[test]
+    fn test_integer_as_flexible_u64() {
+        check_matches_schema::<FlexibleU64Duration>(&json!(16));
+    }
+
+    #[test]
+    fn test_number_as_flexible_u64() {
+        check_matches_schema::<FlexibleU64Duration>(&json!(54.1));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_negative_as_flexible_u64() {
+        check_matches_schema::<FlexibleU64Duration>(&json!(-5));
+    }
+
+    #[test]
+    fn test_string_as_flexible_string() {
+        check_matches_schema::<FlexibleStringDuration>(&json!("32"));
+    }
+
+    #[test]
+    fn test_integer_as_flexible_string() {
+        check_matches_schema::<FlexibleStringDuration>(&json!(16));
+    }
+
+    #[test]
+    fn test_number_as_flexible_string() {
+        check_matches_schema::<FlexibleStringDuration>(&json!(54.1));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_negative_as_flexible_string() {
+        check_matches_schema::<FlexibleStringDuration>(&json!(-5));
+    }
+
+    #[test]
+    fn test_negative_as_flexible_timestamp() {
+        check_matches_schema::<FlexibleTimestamp>(&json!(-50000));
+    }
+
+    #[test]
+    fn test_negative_string_as_flexible_timestamp() {
+        check_matches_schema::<FlexibleTimestamp>(&json!("-50000"));
+    }
+}
+
+#[test]
+fn test_borrow_cow() {
+    use std::borrow::Cow;
+
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct Borrowed<'a> {
+        #[serde_as(as = "BorrowCow")]
+        data: Cow<'a, str>,
+    }
+
+    check_valid_json_schema(&Borrowed {
+        data: Cow::Borrowed("test"),
+    });
+}
+
+#[test]
+fn test_map() {
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct Test {
+        map: [(&'static str, u32); 2],
+    }
+
+    check_valid_json_schema(&Test {
+        map: [("a", 1), ("b", 2)],
+    });
+}
+
+#[test]
+fn test_if_is_human_readable() {
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct Test {
+        #[serde_as(as = "IfIsHumanReadable<DisplayFromStr>")]
+        data: i32,
+    }
+
+    check_valid_json_schema(&Test { data: 5 });
+    check_matches_schema::<Test>(&json!({ "data": "5" }));
+}
+
+#[test]
+fn test_set_last_value_wins_with_duplicates() {
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct Test {
+        #[serde_as(as = "SetLastValueWins<_>")]
+        set: BTreeSet<u32>,
+    }
+
+    check_matches_schema::<Test>(&json!({
+        "set": [ 1, 2, 3, 1, 4, 2 ]
+    }));
+}
+
+#[test]
+#[should_panic]
+fn test_set_prevent_duplicates_with_duplicates() {
+    #[serde_as]
+    #[derive(Serialize, JsonSchema)]
+    struct Test {
+        #[serde_as(as = "SetPreventDuplicates<_>")]
+        set: BTreeSet<u32>,
+    }
+
+    check_matches_schema::<Test>(&json!({
+        "set": [ 1, 1 ]
+    }));
+}
+
+mod key_value_map {
+    use super::*;
+    use std::{collections::BTreeMap, vec};
+
+    #[serde_as]
+    #[derive(Clone, Debug, JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct KVMap<E>(
+        #[serde_as(as = "KeyValueMap<_>")]
+        #[serde(bound(serialize = "E: Serialize", deserialize = "E: Deserialize<'de>"))]
+        Vec<E>,
+    );
+
+    #[derive(Clone, Debug, JsonSchema, Serialize)]
+    #[serde(untagged)]
+    enum UntaggedEnum {
+        A {
+            #[serde(rename = "$key$")]
+            key: String,
+            field1: String,
+        },
+        B(String, i32),
+    }
+
+    #[test]
+    fn test_untagged_enum() {
+        let value = KVMap(vec![
+            UntaggedEnum::A {
+                key: "v1".into(),
+                field1: "field".into(),
+            },
+            UntaggedEnum::B("v2".into(), 7),
+        ]);
+
+        check_valid_json_schema(&value);
+    }
+
+    #[derive(Clone, Debug, JsonSchema, Serialize)]
+    #[serde(untagged)]
+    enum UntaggedNestedEnum {
+        Nested(UntaggedEnum),
+        C {
+            #[serde(rename = "$key$")]
+            key: String,
+            field2: i32,
+        },
+    }
+
+    #[derive(Clone, Debug, Serialize)]
+    #[serde(transparent)]
+    struct LimitedProperties(serde_json::Value);
+
+    impl JsonSchema for LimitedProperties {
+        fn schema_name() -> std::borrow::Cow<'static, str> {
+            "LimitedProperties".into()
+        }
+
+        fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
+            schemars::json_schema!({
+                "type": "object",
+                "additionalProperties": { "type": "string" },
+                "maxProperties": 5,
+                "minProperties": 1,
+            })
+        }
+    }
+
+    #[test]
+    fn test_untagged_nested_enum() {
+        let value = KVMap(vec![
+            UntaggedNestedEnum::Nested(UntaggedEnum::A {
+                key: "v1".into(),
+                field1: "field".into(),
+            }),
+            UntaggedNestedEnum::Nested(UntaggedEnum::B("v2".into(), 7)),
+            UntaggedNestedEnum::C {
+                key: "v2".into(),
+                field2: 222,
+            },
+        ]);
+
+        check_valid_json_schema(&value);
+    }
+
+    #[test]
+    fn test_btreemap() {
+        let value = KVMap(vec![
+            BTreeMap::from_iter([("$key$", "a"), ("value", "b")]),
+            BTreeMap::from_iter([("$key$", "b"), ("value", "d")]),
+        ]);
+
+        check_valid_json_schema(&value);
+    }
+
+    #[test]
+    fn test_num_properties() {
+        let value = KVMap(vec![
+            LimitedProperties(serde_json::json!({
+                "$key$": "A",
+                "a": "b",
+                "c": "d",
+                "e": "f",
+                "g": "h",
+            })),
+            LimitedProperties(serde_json::json!({
+                "$key$": "B",
+                "a": "b",
+            })),
+        ]);
+
+        check_valid_json_schema(&value);
+    }
+}
+
+mod one_or_many {
+    use super::*;
+    use serde_with::formats::{PreferMany, PreferOne};
+
+    #[serde_as]
+    #[derive(Clone, Debug, JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct WithPreferOne(#[serde_as(as = "OneOrMany<_, PreferOne>")] Vec<i32>);
+
+    #[serde_as]
+    #[derive(Clone, Debug, JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct WithPreferMany(#[serde_as(as = "OneOrMany<_, PreferMany>")] Vec<i32>);
+
+    #[test]
+    fn test_prefer_one() {
+        let single = WithPreferOne(vec![7]);
+        let multiple = WithPreferOne(vec![1, 2, 3]);
+
+        check_valid_json_schema(&single);
+        check_valid_json_schema(&multiple);
+    }
+
+    #[test]
+    fn test_prefer_one_matches() {
+        check_matches_schema::<WithPreferOne>(&json!(7));
+        check_matches_schema::<WithPreferOne>(&json!([1, 2, 3]));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_prefer_one_no_invalid_type_one() {
+        check_matches_schema::<WithPreferOne>(&json!("test"));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_prefer_one_no_invalid_type_many() {
+        check_matches_schema::<WithPreferOne>(&json!(["test", 1]));
+    }
+
+    #[test]
+    fn test_prefer_many() {
+        let single = WithPreferMany(vec![7]);
+        let multiple = WithPreferMany(vec![1, 2, 3]);
+
+        check_valid_json_schema(&single);
+        check_valid_json_schema(&multiple);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_prefer_many_no_invalid_type_one() {
+        check_matches_schema::<WithPreferMany>(&json!("test"));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_prefer_many_no_invalid_type_many() {
+        check_matches_schema::<WithPreferMany>(&json!(["test", 1]));
+    }
+}
+
+#[test]
+fn test_pickfirst() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct IntOrDisplay(#[serde_as(as = "PickFirst<(_, DisplayFromStr)>")] u32);
+
+    check_matches_schema::<IntOrDisplay>(&json!(7));
+    check_matches_schema::<IntOrDisplay>(&json!("17"));
+}
+
+#[test]
+fn test_bound() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct S(#[serde_as(as = "ops::Bound<DisplayFromStr>")] ops::Bound<u32>);
+
+    check_valid_json_schema(&S(ops::Bound::Unbounded));
+    check_valid_json_schema(&S(ops::Bound::Included(42)));
+    check_valid_json_schema(&S(ops::Bound::Excluded(42)));
+}
+
+#[test]
+fn test_range() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct S(#[serde_as(as = "ops::Range<DisplayFromStr>")] ops::Range<u32>);
+
+    check_valid_json_schema(&S(3..7));
+}
+
+// Note: Not included in `schemars`
+// #[test]
+// fn test_rangefrom() {
+//     #[serde_as]
+//     #[derive(JsonSchema, Serialize)]
+//     #[serde(transparent)]
+//     struct S(#[serde_as(as = "ops::RangeFrom<DisplayFromStr>")] ops::RangeFrom<u32>);
+
+//     check_valid_json_schema(&S(3..));
+// }
+
+// #[test]
+// fn test_rangeto() {
+//     #[serde_as]
+//     #[derive(JsonSchema, Serialize)]
+//     #[serde(transparent)]
+//     struct S(#[serde_as(as = "ops::RangeTo<DisplayFromStr>")] ops::RangeTo<u32>);
+
+//     check_valid_json_schema(&S(..7));
+// }
+
+#[test]
+fn test_rangeinclusive() {
+    #[serde_as]
+    #[derive(JsonSchema, Serialize)]
+    #[serde(transparent)]
+    struct S(#[serde_as(as = "ops::RangeInclusive<DisplayFromStr>")] ops::RangeInclusive<u32>);
+
+    check_valid_json_schema(&S(3..=7));
+}
diff --git a/serde_with/tests/schemars_1/schemars_basic.json b/serde_with/tests/schemars_1/schemars_basic.json
new file mode 100644
index 0000000..aaaf49f
--- /dev/null
+++ b/serde_with/tests/schemars_1/schemars_basic.json
@@ -0,0 +1,45 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Basic",
+  "type": "object",
+  "properties": {
+    "bare_field": {
+      "description": "Basic field, no attribute",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    },
+    "display_from_str": {
+      "description": "Field that directly uses `DisplayFromStr`",
+      "type": "string"
+    },
+    "same": {
+      "description": "Same does not implement `JsonSchema` directly so this checks that the\ncorrect schemars attribute was injected.",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    },
+    "box_same": {
+      "description": "This checks that Same still works when wrapped in a box.",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    },
+    "vec_same": {
+      "description": "Same thing, but with a Vec this time.",
+      "type": "array",
+      "items": {
+        "type": "integer",
+        "format": "uint32",
+        "minimum": 0
+      }
+    }
+  },
+  "required": [
+    "bare_field",
+    "display_from_str",
+    "same",
+    "box_same",
+    "vec_same"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/schemars_deserialize_only_bug_735.json b/serde_with/tests/schemars_1/schemars_deserialize_only_bug_735.json
new file mode 100644
index 0000000..482e447
--- /dev/null
+++ b/serde_with/tests/schemars_1/schemars_deserialize_only_bug_735.json
@@ -0,0 +1,61 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Basic",
+  "type": "object",
+  "properties": {
+    "bare_field": {
+      "description": "Basic field, no attribute",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    },
+    "both": {
+      "description": "Will emit matching schemars attribute",
+      "$ref": "#/$defs/PickFirst(uint32string)"
+    },
+    "serialize_only": {
+      "description": "Can emit schemars with `serialize_as`, but it will be ignored",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    },
+    "deserialize_only": {
+      "description": "schemars doesn't support `deserialize_as`",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    },
+    "serialize_and_deserialize": {
+      "description": "Can emit schemars with `serialize_as`, but it will be ignored\nschemars doesn't support `deserialize_as`",
+      "type": "integer",
+      "format": "uint32",
+      "minimum": 0
+    }
+  },
+  "required": [
+    "bare_field",
+    "both",
+    "serialize_only",
+    "deserialize_only",
+    "serialize_and_deserialize"
+  ],
+  "$defs": {
+    "PickFirst(uint32string)": {
+      "anyOf": [
+        {
+          "type": "integer",
+          "format": "uint32",
+          "minimum": 0
+        },
+        {
+          "writeOnly": true,
+          "allOf": [
+            {
+              "type": "string"
+            }
+          ]
+        }
+      ]
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/bytes.json b/serde_with/tests/schemars_1/snapshots/bytes.json
new file mode 100644
index 0000000..8a35147
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/bytes.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "bytes": {
+      "type": "array",
+      "items": {
+        "type": "integer",
+        "format": "uint8",
+        "minimum": 0,
+        "maximum": 255
+      }
+    }
+  },
+  "required": [
+    "bytes"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/default_on_null.json b/serde_with/tests/schemars_1/snapshots/default_on_null.json
new file mode 100644
index 0000000..f5b2d7c
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/default_on_null.json
@@ -0,0 +1,16 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": [
+        "string",
+        "null"
+      ]
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/duration.json b/serde_with/tests/schemars_1/snapshots/duration.json
new file mode 100644
index 0000000..5059d41
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/duration.json
@@ -0,0 +1,70 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "seconds": {
+      "oneOf": [
+        {
+          "type": "number",
+          "minimum": 0.0
+        },
+        {
+          "type": "string",
+          "pattern": "^[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$",
+          "writeOnly": true
+        }
+      ]
+    },
+    "frac": {
+      "oneOf": [
+        {
+          "type": "number",
+          "minimum": 0.0
+        },
+        {
+          "type": "string",
+          "pattern": "^[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$",
+          "writeOnly": true
+        }
+      ]
+    },
+    "flexible_string": {
+      "oneOf": [
+        {
+          "type": "number",
+          "minimum": 0.0,
+          "writeOnly": true
+        },
+        {
+          "type": "string",
+          "pattern": "^[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$"
+        }
+      ]
+    },
+    "seconds_u64_strict": {
+      "type": "integer",
+      "format": "uint64",
+      "minimum": 0
+    },
+    "time_i64": {
+      "oneOf": [
+        {
+          "type": "number"
+        },
+        {
+          "type": "string",
+          "pattern": "^-?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$",
+          "writeOnly": true
+        }
+      ]
+    }
+  },
+  "required": [
+    "seconds",
+    "frac",
+    "flexible_string",
+    "seconds_u64_strict",
+    "time_i64"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/enum_map.json b/serde_with/tests/schemars_1/snapshots/enum_map.json
new file mode 100644
index 0000000..42186a8
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/enum_map.json
@@ -0,0 +1,48 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "$ref": "#/$defs/EnumMap(Mappable)"
+    }
+  },
+  "required": [
+    "data"
+  ],
+  "$defs": {
+    "EnumMap(Mappable)": {
+      "type": "object",
+      "properties": {
+        "A": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "B": {
+          "type": "string"
+        },
+        "C": {
+          "type": "object",
+          "properties": {
+            "c": {
+              "type": "integer",
+              "format": "int32"
+            },
+            "b": {
+              "type": [
+                "integer",
+                "null"
+              ],
+              "format": "uint64",
+              "minimum": 0
+            }
+          },
+          "required": [
+            "c"
+          ]
+        }
+      },
+      "additionalProperties": false
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/from_into.json b/serde_with/tests/schemars_1/snapshots/from_into.json
new file mode 100644
index 0000000..4a320f8
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/from_into.json
@@ -0,0 +1,15 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": "integer",
+      "format": "uint64",
+      "minimum": 0
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/key_value_map.json b/serde_with/tests/schemars_1/snapshots/key_value_map.json
new file mode 100644
index 0000000..cd9aeda
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/key_value_map.json
@@ -0,0 +1,44 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "$ref": "#/$defs/KeyValueMap(KvMapData)"
+    }
+  },
+  "required": [
+    "data"
+  ],
+  "$defs": {
+    "KeyValueMap(KvMapData)": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "object",
+        "properties": {
+          "d": {
+            "type": "boolean"
+          },
+          "a": {
+            "type": "integer",
+            "format": "uint32",
+            "minimum": 0
+          },
+          "b": {
+            "type": "string"
+          },
+          "c": {
+            "type": "number",
+            "format": "float"
+          }
+        },
+        "required": [
+          "a",
+          "b",
+          "c",
+          "d"
+        ]
+      }
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/key_value_map_enum.json b/serde_with/tests/schemars_1/snapshots/key_value_map_enum.json
new file mode 100644
index 0000000..cc9762c
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/key_value_map_enum.json
@@ -0,0 +1,57 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "$ref": "#/$defs/KeyValueMap(KvMapEnum)"
+    }
+  },
+  "required": [
+    "data"
+  ],
+  "$defs": {
+    "KeyValueMap(KvMapEnum)": {
+      "type": "object",
+      "additionalProperties": {
+        "oneOf": [
+          {
+            "type": "object",
+            "properties": {
+              "a": {
+                "type": "integer",
+                "format": "uint32",
+                "minimum": 0
+              }
+            },
+            "required": [
+              "a"
+            ]
+          },
+          {
+            "type": "object",
+            "properties": {
+              "b": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "b"
+            ]
+          },
+          {
+            "type": "object",
+            "properties": {
+              "c": {
+                "type": "boolean"
+              }
+            },
+            "required": [
+              "c"
+            ]
+          }
+        ]
+      }
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/key_value_map_flatten.json b/serde_with/tests/schemars_1/snapshots/key_value_map_flatten.json
new file mode 100644
index 0000000..ad88ce8
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/key_value_map_flatten.json
@@ -0,0 +1,66 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "$ref": "#/$defs/KeyValueMap(KvMapFlatten)"
+    }
+  },
+  "required": [
+    "data"
+  ],
+  "$defs": {
+    "KeyValueMap(KvMapFlatten)": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "object",
+        "properties": {
+          "extra": {
+            "type": "boolean"
+          }
+        },
+        "oneOf": [
+          {
+            "type": "object",
+            "properties": {
+              "a": {
+                "type": "integer",
+                "format": "uint32",
+                "minimum": 0
+              }
+            },
+            "required": [
+              "a"
+            ]
+          },
+          {
+            "type": "object",
+            "properties": {
+              "b": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "b"
+            ]
+          },
+          {
+            "type": "object",
+            "properties": {
+              "c": {
+                "type": "boolean"
+              }
+            },
+            "required": [
+              "c"
+            ]
+          }
+        ],
+        "required": [
+          "extra"
+        ]
+      }
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/map.json b/serde_with/tests/schemars_1/snapshots/map.json
new file mode 100644
index 0000000..79d32f2
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/map.json
@@ -0,0 +1,18 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "integer",
+        "format": "uint32",
+        "minimum": 0
+      }
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/map_fixed.json b/serde_with/tests/schemars_1/snapshots/map_fixed.json
new file mode 100644
index 0000000..79d32f2
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/map_fixed.json
@@ -0,0 +1,18 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "integer",
+        "format": "uint32",
+        "minimum": 0
+      }
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/one_or_many_nested.json b/serde_with/tests/schemars_1/snapshots/one_or_many_nested.json
new file mode 100644
index 0000000..9ba62e8
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/one_or_many_nested.json
@@ -0,0 +1,33 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "optional_many": {
+      "anyOf": [
+        {
+          "$ref": "#/$defs/OneOrMany(string,PreferOne)"
+        },
+        {
+          "type": "null"
+        }
+      ],
+      "default": null
+    }
+  },
+  "$defs": {
+    "OneOrMany(string,PreferOne)": {
+      "anyOf": [
+        {
+          "type": "string"
+        },
+        {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      ]
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/one_or_many_prefer_one.json b/serde_with/tests/schemars_1/snapshots/one_or_many_prefer_one.json
new file mode 100644
index 0000000..6f160fc
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/one_or_many_prefer_one.json
@@ -0,0 +1,22 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "$ref": "#/$defs/OneOrMany(int32,PreferOne)",
+  "$defs": {
+    "OneOrMany(int32,PreferOne)": {
+      "anyOf": [
+        {
+          "type": "integer",
+          "format": "int32"
+        },
+        {
+          "type": "array",
+          "items": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      ]
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/pickfirst.json b/serde_with/tests/schemars_1/snapshots/pickfirst.json
new file mode 100644
index 0000000..6f4cf3c
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/pickfirst.json
@@ -0,0 +1,24 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "$ref": "#/$defs/PickFirst(uint32string)",
+  "$defs": {
+    "PickFirst(uint32string)": {
+      "anyOf": [
+        {
+          "type": "integer",
+          "format": "uint32",
+          "minimum": 0
+        },
+        {
+          "writeOnly": true,
+          "allOf": [
+            {
+              "type": "string"
+            }
+          ]
+        }
+      ]
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/pickfirst_nested.json b/serde_with/tests/schemars_1/snapshots/pickfirst_nested.json
new file mode 100644
index 0000000..d6f15ab
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/pickfirst_nested.json
@@ -0,0 +1,37 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "$ref": "#/$defs/OneOrMany(PickFirst(uint32string),PreferOne)",
+  "$defs": {
+    "OneOrMany(PickFirst(uint32string),PreferOne)": {
+      "anyOf": [
+        {
+          "$ref": "#/$defs/PickFirst(uint32string)"
+        },
+        {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/PickFirst(uint32string)"
+          }
+        }
+      ]
+    },
+    "PickFirst(uint32string)": {
+      "anyOf": [
+        {
+          "type": "integer",
+          "format": "uint32",
+          "minimum": 0
+        },
+        {
+          "writeOnly": true,
+          "allOf": [
+            {
+              "type": "string"
+            }
+          ]
+        }
+      ]
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/snapshots/set_last_value_wins.json b/serde_with/tests/schemars_1/snapshots/set_last_value_wins.json
new file mode 100644
index 0000000..8a42400
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/set_last_value_wins.json
@@ -0,0 +1,18 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": "array",
+      "items": {
+        "type": "integer",
+        "format": "uint32",
+        "minimum": 0
+      }
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/set_prevent_duplicates.json b/serde_with/tests/schemars_1/snapshots/set_prevent_duplicates.json
new file mode 100644
index 0000000..a06a5be
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/set_prevent_duplicates.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": "array",
+      "uniqueItems": true,
+      "items": {
+        "type": "integer",
+        "format": "uint32",
+        "minimum": 0
+      }
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/snapshots/string_with_separator.json b/serde_with/tests/schemars_1/snapshots/string_with_separator.json
new file mode 100644
index 0000000..0964265
--- /dev/null
+++ b/serde_with/tests/schemars_1/snapshots/string_with_separator.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "data": {
+      "type": "string"
+    }
+  },
+  "required": [
+    "data"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/test_std/map.json b/serde_with/tests/schemars_1/test_std/map.json
new file mode 100644
index 0000000..209ef59
--- /dev/null
+++ b/serde_with/tests/schemars_1/test_std/map.json
@@ -0,0 +1,17 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "map": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "integer",
+        "format": "int32"
+      }
+    }
+  },
+  "required": [
+    "map"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/test_std/option.json b/serde_with/tests/schemars_1/test_std/option.json
new file mode 100644
index 0000000..2b9ef6f
--- /dev/null
+++ b/serde_with/tests/schemars_1/test_std/option.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "optional": {
+      "type": [
+        "integer",
+        "null"
+      ],
+      "format": "int32"
+    }
+  }
+}
diff --git a/serde_with/tests/schemars_1/test_std/set.json b/serde_with/tests/schemars_1/test_std/set.json
new file mode 100644
index 0000000..92ca2b5
--- /dev/null
+++ b/serde_with/tests/schemars_1/test_std/set.json
@@ -0,0 +1,17 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "map": {
+      "type": "array",
+      "uniqueItems": true,
+      "items": {
+        "type": "string"
+      }
+    }
+  },
+  "required": [
+    "map"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/test_std/tuples.json b/serde_with/tests/schemars_1/test_std/tuples.json
new file mode 100644
index 0000000..7b6051f
--- /dev/null
+++ b/serde_with/tests/schemars_1/test_std/tuples.json
@@ -0,0 +1,60 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "tuple0": {
+      "type": "null"
+    },
+    "tuple1": {
+      "type": "array",
+      "prefixItems": [
+        {
+          "type": "integer",
+          "format": "int32"
+        }
+      ],
+      "minItems": 1,
+      "maxItems": 1
+    },
+    "tuple2": {
+      "type": "array",
+      "prefixItems": [
+        {
+          "type": "integer",
+          "format": "int32"
+        },
+        {
+          "type": "integer",
+          "format": "int32"
+        }
+      ],
+      "minItems": 2,
+      "maxItems": 2
+    },
+    "tuple3": {
+      "type": "array",
+      "prefixItems": [
+        {
+          "type": "integer",
+          "format": "int32"
+        },
+        {
+          "type": "integer",
+          "format": "int32"
+        },
+        {
+          "type": "string"
+        }
+      ],
+      "minItems": 3,
+      "maxItems": 3
+    }
+  },
+  "required": [
+    "tuple0",
+    "tuple1",
+    "tuple2",
+    "tuple3"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/test_std/vec.json b/serde_with/tests/schemars_1/test_std/vec.json
new file mode 100644
index 0000000..6046cd2
--- /dev/null
+++ b/serde_with/tests/schemars_1/test_std/vec.json
@@ -0,0 +1,16 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "vec": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    }
+  },
+  "required": [
+    "vec"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/test_std/vec_deque.json b/serde_with/tests/schemars_1/test_std/vec_deque.json
new file mode 100644
index 0000000..d29a066
--- /dev/null
+++ b/serde_with/tests/schemars_1/test_std/vec_deque.json
@@ -0,0 +1,16 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "title": "Test",
+  "type": "object",
+  "properties": {
+    "vec_deque": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    }
+  },
+  "required": [
+    "vec_deque"
+  ]
+}
diff --git a/serde_with/tests/schemars_1/utils.rs b/serde_with/tests/schemars_1/utils.rs
new file mode 100644
index 0000000..8d5f838
--- /dev/null
+++ b/serde_with/tests/schemars_1/utils.rs
@@ -0,0 +1,144 @@
+#![allow(dead_code, missing_docs)]
+
+use core::fmt::Debug;
+use expect_test::Expect;
+use pretty_assertions::assert_eq;
+use serde::{de::DeserializeOwned, Serialize};
+
+#[track_caller]
+pub fn is_equal<T>(value: T, expected: Expect)
+where
+    T: Debug + DeserializeOwned + PartialEq + Serialize,
+{
+    let serialized = serde_json::to_string_pretty(&value).unwrap();
+    expected.assert_eq(&serialized);
+    assert_eq!(
+        value,
+        serde_json::from_str::<T>(&serialized).unwrap(),
+        "Deserialization differs from expected value."
+    );
+}
+
+/// Like [`is_equal`] but not pretty-print
+#[track_caller]
+pub fn is_equal_compact<T>(value: T, expected: Expect)
+where
+    T: Debug + DeserializeOwned + PartialEq + Serialize,
+{
+    let serialized = serde_json::to_string(&value).unwrap();
+    expected.assert_eq(&serialized);
+    assert_eq!(
+        value,
+        serde_json::from_str::<T>(&serialized).unwrap(),
+        "Deserialization differs from expected value."
+    );
+}
+
+#[track_caller]
+pub fn check_deserialization<T>(value: T, deserialize_from: &str)
+where
+    T: Debug + DeserializeOwned + PartialEq,
+{
+    assert_eq!(
+        value,
+        serde_json::from_str::<T>(deserialize_from).unwrap(),
+        "Deserialization differs from expected value."
+    );
+}
+
+#[track_caller]
+pub fn check_serialization<T>(value: T, serialize_to: Expect)
+where
+    T: Debug + Serialize,
+{
+    serialize_to.assert_eq(&serde_json::to_string_pretty(&value).unwrap());
+}
+
+#[track_caller]
+pub fn check_error_serialization<T>(value: T, error_msg: Expect)
+where
+    T: Debug + Serialize,
+{
+    error_msg.assert_eq(
+        &serde_json::to_string_pretty(&value)
+            .unwrap_err()
+            .to_string(),
+    );
+}
+
+#[track_caller]
+pub fn check_error_deserialization<T>(deserialize_from: &str, error_msg: Expect)
+where
+    T: Debug + DeserializeOwned,
+{
+    error_msg.assert_eq(
+        &serde_json::from_str::<T>(deserialize_from)
+            .unwrap_err()
+            .to_string(),
+    );
+}
+
+#[track_caller]
+pub fn check_matches_schema<T>(value: &serde_json::Value)
+where
+    T: schemars_1::JsonSchema,
+{
+    use jsonschema::Validator;
+    use std::fmt::Write;
+
+    let schema_object = serde_json::to_value(schemars_1::schema_for!(T))
+        .expect("schema for T could not be serialized to json");
+    let schema = match Validator::new(&schema_object) {
+        Ok(schema) => schema,
+        Err(e) => panic!(
+            "\n\
+                schema for T was not a valid JSON schema: {e}\n\
+                \n\
+                Json Schema:\n\
+                {}\n\
+            ",
+            serde_json::to_string_pretty(&schema_object)
+                .unwrap_or_else(|e| format!("> error: {e}"))
+        ),
+    };
+
+    let mut output = String::new();
+
+    let _ = writeln!(&mut output, "Object Value:");
+    let _ = writeln!(
+        &mut output,
+        "{}",
+        serde_json::to_string_pretty(&value).unwrap_or_else(|e| format!("> error: {e}"))
+    );
+    let _ = writeln!(&mut output);
+    let _ = writeln!(&mut output, "JSON Schema:");
+    let _ = writeln!(
+        &mut output,
+        "{}",
+        serde_json::to_string_pretty(&schema_object).unwrap_or_else(|e| format!("> error: {e}"))
+    );
+
+    if let Err(err) = schema.validate(value) {
+        let mut message = String::new();
+        let _ = writeln!(
+            &mut message,
+            "Object was not valid according to its own schema:"
+        );
+        let _ = writeln!(&mut message, "  -> {err}");
+        let _ = writeln!(&mut message);
+
+        panic!("{message} {output}");
+    } else {
+        eprint!("{output}");
+    }
+}
+
+#[track_caller]
+pub fn check_valid_json_schema<T>(value: &T)
+where
+    T: schemars_1::JsonSchema + Serialize,
+{
+    let value = serde_json::to_value(value).expect("could not serialize T to json");
+
+    check_matches_schema::<T>(&value);
+}