Repository: GREsau/schemars
Pull request #416 (2025-06-01 08:47:19)

Add `get_mut`, `pointer`, `pointer_mut` methods to `Schema`


Patch:
diff --git a/docs/_includes/examples/custom_serialization.rs b/docs/_includes/examples/custom_serialization.rs
index 8e2a695..7a049c8 100644
--- a/docs/_includes/examples/custom_serialization.rs
+++ b/docs/_includes/examples/custom_serialization.rs
@@ -21,9 +21,7 @@ pub struct MyStruct {
 
 fn make_custom_schema(generator: &mut SchemaGenerator) -> Schema {
     let mut schema = String::json_schema(generator);
-    schema
-        .ensure_object()
-        .insert("format".into(), "boolean".into());
+    schema.insert("format".into(), "boolean".into());
     schema
 }
 
diff --git a/schemars/examples/custom_serialization.rs b/schemars/examples/custom_serialization.rs
index 8e2a695..7a049c8 100644
--- a/schemars/examples/custom_serialization.rs
+++ b/schemars/examples/custom_serialization.rs
@@ -21,9 +21,7 @@ pub struct MyStruct {
 
 fn make_custom_schema(generator: &mut SchemaGenerator) -> Schema {
     let mut schema = String::json_schema(generator);
-    schema
-        .ensure_object()
-        .insert("format".into(), "boolean".into());
+    schema.insert("format".into(), "boolean".into());
     schema
 }
 
diff --git a/schemars/src/_private/mod.rs b/schemars/src/_private/mod.rs
index f95550d..f65a762 100644
--- a/schemars/src/_private/mod.rs
+++ b/schemars/src/_private/mod.rs
@@ -230,11 +230,7 @@ pub fn must_contain(schema: &mut Schema, substring: &str) {
 }
 
 pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) {
-    if let Some(inner_schema) = schema
-        .as_object_mut()
-        .and_then(|o| o.get_mut("items"))
-        .and_then(|i| i.try_into().ok())
-    {
+    if let Some(inner_schema) = schema.get_mut("items").and_then(|i| i.try_into().ok()) {
         f(inner_schema);
     }
 }
diff --git a/schemars/src/generate.rs b/schemars/src/generate.rs
index b1bde99..dfcd0b3 100644
--- a/schemars/src/generate.rs
+++ b/schemars/src/generate.rs
@@ -601,7 +601,7 @@ fn json_pointer_mut<'a>(
 
         let next_value = match object.entry(segment) {
             Entry::Occupied(o) => o.into_mut(),
-            Entry::Vacant(v) if create_if_missing => v.insert(Value::Object(JsonMap::default())),
+            Entry::Vacant(v) if create_if_missing => v.insert(Value::Object(JsonMap::new())),
             Entry::Vacant(_) => return None,
         };
 
diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs
index ca39366..0e68b59 100644
--- a/schemars/src/schema.rs
+++ b/schemars/src/schema.rs
@@ -246,6 +246,82 @@ impl Schema {
         self.0.as_object().and_then(|o| o.get(key))
     }
 
+    /// If the `Schema`'s underlying JSON value is an object, gets a mutable reference to that
+    /// object's value for the given key if it exists.
+    ///
+    /// This always returns `None` for bool schemas.
+    ///
+    /// # Example
+    /// ```
+    /// use schemars::json_schema;
+    /// use serde_json::{json, Value};
+    ///
+    /// let mut obj_schema = json_schema!({ "properties": {} });
+    /// if let Some(Value::Object(properties)) = obj_schema.get_mut("properties") {
+    ///     properties.insert("anything".to_owned(), true.into());
+    /// }
+    /// assert_eq!(obj_schema, json_schema!({ "properties": { "anything": true } }));
+    /// ```
+    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
+    where
+        String: core::borrow::Borrow<Q>,
+        Q: ?Sized + Ord + Eq + core::hash::Hash,
+    {
+        self.0.as_object_mut().and_then(|o| o.get_mut(key))
+    }
+    /// If the `Schema`'s underlying JSON value is an object, looks up a value within the schema
+    /// by a JSON Pointer.
+    ///
+    /// For more information on JSON Pointer, read [RFC6901](https://tools.ietf.org/html/rfc6901).
+    ///
+    /// This always returns `None` for bool schemas.
+    ///
+    /// # Example
+    /// ```
+    /// use schemars::json_schema;
+    /// use serde_json::json;
+    ///
+    /// let schema = json_schema!({
+    ///     "properties": {
+    ///         "anything": true
+    ///     }
+    /// });
+    ///
+    /// assert_eq!(schema.pointer("/properties/anything").unwrap(), &json!(true));
+    /// assert_eq!(schema.pointer("/$defs/example"), None);
+    /// ```
+    pub fn pointer(&self, pointer: &str) -> Option<&Value> {
+        self.0.pointer(pointer)
+    }
+
+    /// If the `Schema`'s underlying JSON value is an object, looks up a value by a JSON Pointer
+    /// and returns a mutable reference to that value.
+    ///
+    /// For more information on JSON Pointer, read [RFC6901](https://tools.ietf.org/html/rfc6901).
+    ///
+    /// This always returns `None` for bool schemas.
+    ///
+    /// # Example
+    /// ```
+    /// use schemars::{json_schema, Schema};
+    /// use serde_json::json;
+    ///
+    /// let mut schema = json_schema!({
+    ///     "properties": {
+    ///         "anything": true
+    ///     }
+    /// });
+    ///
+    /// let subschema_value = schema.pointer_mut("/properties/anything").unwrap();
+    /// let subschema: &mut Schema = subschema_value.try_into().unwrap();
+    /// subschema.ensure_object();
+    ///
+    /// assert_eq!(schema.pointer_mut("/properties/anything").unwrap(), &json!({}));
+    /// ```
+    pub fn pointer_mut(&mut self, pointer: &str) -> Option<&mut Value> {
+        self.0.pointer_mut(pointer)
+    }
+
     /// If the `Schema`'s underlying JSON value is an object, removes and returns its value for the
     /// given key.
     ///
diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs
index 81b2a55..8460d97 100644
--- a/schemars/src/ser.rs
+++ b/schemars/src/ser.rs
@@ -160,7 +160,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
         let mut schema = value.serialize(self)?;
 
         if include_title && !name.is_empty() {
-            schema.ensure_object().insert("title".into(), name.into());
+            schema.insert("title".into(), name.into());
         }
 
         Ok(schema)
diff --git a/schemars/src/transform.rs b/schemars/src/transform.rs
index 21a23d9..eb3e8ca 100644
--- a/schemars/src/transform.rs
+++ b/schemars/src/transform.rs
@@ -433,22 +433,19 @@ impl Default for AddNullable {
 impl Transform for AddNullable {
     fn transform(&mut self, schema: &mut Schema) {
         if schema.has_type("null") {
-            // has_type returned true so we know schema is an object
-            let obj = schema.as_object_mut().unwrap();
-
-            obj.insert("nullable".into(), true.into());
+            schema.insert("nullable".into(), true.into());
 
             // has_type returned true so we know "type" exists and is a string or array
-            let ty = obj.get_mut("type").unwrap();
+            let ty = schema.get_mut("type").unwrap();
             let only_allows_null =
                 ty.is_string() || ty.as_array().unwrap().iter().all(|v| v == "null");
 
             if only_allows_null {
                 if self.add_const_null {
-                    obj.insert("const".to_string(), Value::Null);
+                    schema.insert("const".to_string(), Value::Null);
 
                     if self.remove_null_type {
-                        obj.remove("type");
+                        schema.remove("type");
                     }
                 } else if self.remove_null_type {
                     *ty = Value::Array(Vec::new());
diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs
index 07abfa7..02d60a0 100644
--- a/schemars_derive/src/attr/validation.rs
+++ b/schemars_derive/src/attr/validation.rs
@@ -95,7 +95,7 @@ impl ValidationAttrs {
         if let Some(format) = &self.format {
             let f = format.schema_str();
             mutators.push(quote! {
-                    (#mut_ref_schema).ensure_object().insert("format".into(), #f.into());
+                    (#mut_ref_schema).insert("format".into(), #f.into());
             })
         };
 


Test patch:
diff --git a/schemars/tests/integration/settings.rs b/schemars/tests/integration/settings.rs
index 3434d4f..507811f 100644
--- a/schemars/tests/integration/settings.rs
+++ b/schemars/tests/integration/settings.rs
@@ -56,14 +56,12 @@ fn openapi3() {
     settings.transforms.insert(
         0,
         Box::new(|s: &mut Schema| {
-            let obj = s.ensure_object();
-            let defs = obj["components"]["schemas"].take();
-            obj.insert("$defs".to_owned(), defs);
+            let defs = s.pointer_mut("/components/schemas").unwrap().take();
+            s.insert("$defs".to_owned(), defs);
         }),
     );
     settings.transforms.push(Box::new(|s: &mut Schema| {
-        let obj = s.ensure_object();
-        obj["components"]["schemas"] = obj.remove("$defs").unwrap();
+        *s.pointer_mut("/components/schemas").unwrap() = s.remove("$defs").unwrap();
     }));
 
     test!(OuterStruct, settings.clone()).assert_snapshot();