Repository: GREsau/schemars (rust)

Issue:
how to generate json schema from serde_json::value::Value?
i want to generate json schema from dynamic json value.
i don't know whether schemars support this function.

```rust
    let data = r#"
        {
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;
    let v: Value = serde_json::from_str(data)? ;
```

Pull request description:
Generate schema from any serializable value
Fixes #47

Patch:
diff --git a/README.md b/README.md
index 324b26d..0ce3798 100644
--- a/README.md
+++ b/README.md
@@ -200,6 +200,68 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap());
 
 `#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
 
+### Schema from Example Values
+
+If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant.
+
+```rust
+use schemars::schema_for_value;
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct MyStruct {
+    pub my_int: i32,
+    pub my_bool: bool,
+    pub my_nullable_enum: Option<MyEnum>,
+}
+
+#[derive(Serialize)]
+pub enum MyEnum {
+    StringNewType(String),
+    StructVariant { floats: Vec<f32> },
+}
+
+fn main() {
+    let schema = schema_for_value!(MyStruct {
+        my_int: 123,
+        my_bool: true,
+        my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
+    });
+    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
+}
+```
+
+<details>
+<summary>Click to see the output JSON schema...</summary>
+
+```json
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "MyStruct",
+  "examples": [
+    {
+      "my_bool": true,
+      "my_int": 123,
+      "my_nullable_enum": {
+        "StringNewType": "foo"
+      }
+    }
+  ],
+  "type": "object",
+  "properties": {
+    "my_bool": {
+      "type": "boolean"
+    },
+    "my_int": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "my_nullable_enum": true
+  }
+}
+```
+</details>
+
 ## Feature Flags
 - `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
 - `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
diff --git a/docs/3-generating.md b/docs/3-generating.md
index f5c2d83..29222f0 100644
--- a/docs/3-generating.md
+++ b/docs/3-generating.md
@@ -20,6 +20,8 @@ If you want more control over how the schema is generated, you can use the [`gen
 
 See the API documentation for more info on how to use those types for custom schema generation.
 
-<!-- TODO: 
+<!-- TODO:
 create and link to example
+
+Generating schema from example value
 -->
diff --git a/docs/_includes/examples/from_value.rs b/docs/_includes/examples/from_value.rs
new file mode 100644
index 0000000..9d50961
--- /dev/null
+++ b/docs/_includes/examples/from_value.rs
@@ -0,0 +1,24 @@
+use schemars::schema_for_value;
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct MyStruct {
+    pub my_int: i32,
+    pub my_bool: bool,
+    pub my_nullable_enum: Option<MyEnum>,
+}
+
+#[derive(Serialize)]
+pub enum MyEnum {
+    StringNewType(String),
+    StructVariant { floats: Vec<f32> },
+}
+
+fn main() {
+    let schema = schema_for_value!(MyStruct {
+        my_int: 123,
+        my_bool: true,
+        my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
+    });
+    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
+}
diff --git a/docs/_includes/examples/from_value.schema.json b/docs/_includes/examples/from_value.schema.json
new file mode 100644
index 0000000..e7ca457
--- /dev/null
+++ b/docs/_includes/examples/from_value.schema.json
@@ -0,0 +1,24 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "MyStruct",
+  "examples": [
+    {
+      "my_bool": true,
+      "my_int": 123,
+      "my_nullable_enum": {
+        "StringNewType": "foo"
+      }
+    }
+  ],
+  "type": "object",
+  "properties": {
+    "my_bool": {
+      "type": "boolean"
+    },
+    "my_int": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "my_nullable_enum": true
+  }
+}
diff --git a/schemars/examples/from_value.rs b/schemars/examples/from_value.rs
new file mode 100644
index 0000000..9d50961
--- /dev/null
+++ b/schemars/examples/from_value.rs
@@ -0,0 +1,24 @@
+use schemars::schema_for_value;
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct MyStruct {
+    pub my_int: i32,
+    pub my_bool: bool,
+    pub my_nullable_enum: Option<MyEnum>,
+}
+
+#[derive(Serialize)]
+pub enum MyEnum {
+    StringNewType(String),
+    StructVariant { floats: Vec<f32> },
+}
+
+fn main() {
+    let schema = schema_for_value!(MyStruct {
+        my_int: 123,
+        my_bool: true,
+        my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
+    });
+    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
+}
diff --git a/schemars/examples/from_value.schema.json b/schemars/examples/from_value.schema.json
new file mode 100644
index 0000000..e7ca457
--- /dev/null
+++ b/schemars/examples/from_value.schema.json
@@ -0,0 +1,24 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "MyStruct",
+  "examples": [
+    {
+      "my_bool": true,
+      "my_int": 123,
+      "my_nullable_enum": {
+        "StringNewType": "foo"
+      }
+    }
+  ],
+  "type": "object",
+  "properties": {
+    "my_bool": {
+      "type": "boolean"
+    },
+    "my_int": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "my_nullable_enum": true
+  }
+}
diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs
index 1e7dab3..646f614 100644
--- a/schemars/src/flatten.rs
+++ b/schemars/src/flatten.rs
@@ -2,6 +2,9 @@ use crate::schema::*;
 use crate::{Map, Set};
 
 impl Schema {
+    /// This function is only public for use by schemars_derive.
+    ///
+    /// It should not be considered part of the public API.
     #[doc(hidden)]
     pub fn flatten(self, other: Self) -> Schema {
         if is_null_type(&self) {
diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs
index aff686c..72c1700 100644
--- a/schemars/src/gen.rs
+++ b/schemars/src/gen.rs
@@ -1,7 +1,7 @@
 /*!
 JSON Schema generator and settings.
 
-This module is useful if you want more control over how the schema generated then the [`schema_for!`] macro gives you.
+This module is useful if you want more control over how the schema generated than the [`schema_for!`] macro gives you.
 There are two main types in this module:two main types in this module:
 * [`SchemaSettings`], which defines what JSON Schema features should be used when generating schemas (for example, how `Option`s should be represented).
 * [`SchemaGenerator`], which manages the generation of a schema document.
@@ -11,6 +11,7 @@ use crate::flatten::Merge;
 use crate::schema::*;
 use crate::{visit::*, JsonSchema, Map};
 use dyn_clone::DynClone;
+use serde::Serialize;
 use std::{any::Any, collections::HashSet, fmt::Debug};
 
 /// Settings to customize how Schemas are generated.
@@ -314,6 +315,70 @@ impl SchemaGenerator {
         root
     }
 
+    /// Generates a root JSON Schema for the given example value.
+    ///
+    /// If the value implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`root_schema_for()`](Self::root_schema_for())
+    /// function which will generally produce a more precise schema, particularly when the value contains any enums.
+    pub fn root_schema_for_value<T: ?Sized + Serialize>(
+        &mut self,
+        value: &T,
+    ) -> Result<RootSchema, serde_json::Error> {
+        let mut schema = value
+            .serialize(crate::ser::Serializer {
+                gen: self,
+                include_title: true,
+            })?
+            .into_object();
+
+        if let Ok(example) = serde_json::to_value(value) {
+            schema.metadata().examples.push(example);
+        }
+
+        let mut root = RootSchema {
+            meta_schema: self.settings.meta_schema.clone(),
+            definitions: self.definitions.clone(),
+            schema,
+        };
+
+        for visitor in &mut self.settings.visitors {
+            visitor.visit_root_schema(&mut root)
+        }
+
+        Ok(root)
+    }
+
+    /// Consumes `self` and generates a root JSON Schema for the given example value.
+    ///
+    /// If the value  implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`into_root_schema_for()!`](Self::into_root_schema_for())
+    /// function which will generally produce a more precise schema, particularly when the value contains any enums.
+    pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
+        mut self,
+        value: &T,
+    ) -> Result<RootSchema, serde_json::Error> {
+        let mut schema = value
+            .serialize(crate::ser::Serializer {
+                gen: &mut self,
+                include_title: true,
+            })?
+            .into_object();
+
+        if let Ok(example) = serde_json::to_value(value) {
+            schema.metadata().examples.push(example);
+        }
+
+        let mut root = RootSchema {
+            meta_schema: self.settings.meta_schema,
+            definitions: self.definitions,
+            schema,
+        };
+
+        for visitor in &mut self.settings.visitors {
+            visitor.visit_root_schema(&mut root)
+        }
+
+        Ok(root)
+    }
+
     /// Attemps to find the schema that the given `schema` is referencing.
     ///
     /// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers
diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs
index 2c5b4b6..9d5fee0 100644
--- a/schemars/src/json_schema_impls/core.rs
+++ b/schemars/src/json_schema_impls/core.rs
@@ -25,6 +25,7 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
                     schema
                 }
                 schema => SchemaObject {
+                    // TODO technically the schema already accepts null, so this may be unnecessary
                     subschemas: Some(Box::new(SubschemaValidation {
                         any_of: Some(vec![schema, <()>::json_schema(gen)]),
                         ..Default::default()
diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs
index a4372f8..e77558b 100644
--- a/schemars/src/lib.rs
+++ b/schemars/src/lib.rs
@@ -195,9 +195,71 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap());
 
 `#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.
 
+### Schema from Example Values
+
+If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant.
+
+```rust
+use schemars::schema_for_value;
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct MyStruct {
+    pub my_int: i32,
+    pub my_bool: bool,
+    pub my_nullable_enum: Option<MyEnum>,
+}
+
+#[derive(Serialize)]
+pub enum MyEnum {
+    StringNewType(String),
+    StructVariant { floats: Vec<f32> },
+}
+
+fn main() {
+    let schema = schema_for_value!(MyStruct {
+        my_int: 123,
+        my_bool: true,
+        my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
+    });
+    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
+}
+```
+
+<details>
+<summary>Click to see the output JSON schema...</summary>
+
+```json
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "title": "MyStruct",
+    "examples": [
+        {
+            "my_bool": true,
+            "my_int": 123,
+            "my_nullable_enum": {
+                "StringNewType": "foo"
+            }
+        }
+    ],
+    "type": "object",
+    "properties": {
+        "my_bool": {
+            "type": "boolean"
+        },
+        "my_int": {
+            "type": "integer",
+            "format": "int32"
+        },
+        "my_nullable_enum": true
+    }
+}
+```
+</details>
+
 ## Feature Flags
-- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro.
-- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves.
+- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro
+- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves
 - `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject`
 
 ## Optional Dependencies
@@ -236,6 +298,7 @@ pub type MapEntry<'a, K, V> = indexmap::map::Entry<'a, K, V>;
 
 mod flatten;
 mod json_schema_impls;
+mod ser;
 #[macro_use]
 mod macros;
 
diff --git a/schemars/src/macros.rs b/schemars/src/macros.rs
index 226f2cc..18a6810 100644
--- a/schemars/src/macros.rs
+++ b/schemars/src/macros.rs
@@ -13,9 +13,66 @@
 ///
 /// let my_schema = schema_for!(MyStruct);
 /// ```
+#[cfg(doc)]
 #[macro_export]
 macro_rules! schema_for {
     ($type:ty) => {
         $crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>()
     };
 }
+
+/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given type using default settings.
+///
+/// The type must implement [`JsonSchema`](crate::JsonSchema).
+///
+/// # Example
+/// ```
+/// use schemars::{schema_for, JsonSchema};
+///
+/// #[derive(JsonSchema)]
+/// struct MyStruct {
+///     foo: i32,
+/// }
+///
+/// let my_schema = schema_for!(MyStruct);
+/// ```
+#[cfg(not(doc))]
+#[macro_export]
+macro_rules! schema_for {
+    ($type:ty) => {
+        $crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>()
+    };
+    ($_:expr) => {
+        compile_error!("This argument to `schema_for!` is not a type - did you mean to use `schema_for_value!` instead?")
+    };
+}
+
+/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given example value using default settings.
+///
+/// The value must implement [`Serialize`](serde::Serialize). If the value also implements [`JsonSchema`](crate::JsonSchema),
+/// then prefer using the [`schema_for!`](schema_for) macro which will generally produce a more precise schema,
+/// particularly when the value contains any enums.
+///
+/// If the `Serialize` implementation of the value decides to fail, this macro will panic.
+/// For a non-panicking alternative, create a [`SchemaGenerator`](crate::gen::SchemaGenerator) and use
+/// its [`into_root_schema_for_value`](crate::gen::SchemaGenerator::into_root_schema_for_value) method.
+///
+/// # Example
+/// ```
+/// use schemars::schema_for_value;
+///
+/// #[derive(serde::Serialize)]
+/// struct MyStruct {
+///     foo: i32,
+/// }
+///
+/// let my_schema = schema_for_value!(MyStruct { foo: 123 });
+/// ```
+#[macro_export]
+macro_rules! schema_for_value {
+    ($value:expr) => {
+        $crate::gen::SchemaGenerator::default()
+            .into_root_schema_for_value(&$value)
+            .unwrap()
+    };
+}
diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs
new file mode 100644
index 0000000..f70ae3e
--- /dev/null
+++ b/schemars/src/ser.rs
@@ -0,0 +1,496 @@
+use crate::schema::*;
+use crate::JsonSchema;
+use crate::{gen::SchemaGenerator, Map};
+use serde_json::{Error, Value};
+use std::{convert::TryInto, fmt::Display};
+
+pub(crate) struct Serializer<'a> {
+    pub(crate) gen: &'a mut SchemaGenerator,
+    pub(crate) include_title: bool,
+}
+
+pub(crate) struct SerializeSeq<'a> {
+    gen: &'a mut SchemaGenerator,
+    items: Option<Schema>,
+}
+
+pub(crate) struct SerializeTuple<'a> {
+    gen: &'a mut SchemaGenerator,
+    items: Vec<Schema>,
+    title: &'static str,
+}
+
+pub(crate) struct SerializeMap<'a> {
+    gen: &'a mut SchemaGenerator,
+    properties: Map<String, Schema>,
+    current_key: Option<String>,
+    title: &'static str,
+}
+
+macro_rules! forward_to_subschema_for {
+    ($fn:ident, $ty:ty) => {
+        fn $fn(self, _value: $ty) -> Result<Self::Ok, Self::Error> {
+            Ok(self.gen.subschema_for::<$ty>())
+        }
+    };
+}
+
+impl<'a> serde::Serializer for Serializer<'a> {
+    type Ok = Schema;
+    type Error = Error;
+
+    type SerializeSeq = SerializeSeq<'a>;
+    type SerializeTuple = SerializeTuple<'a>;
+    type SerializeTupleStruct = SerializeTuple<'a>;
+    type SerializeTupleVariant = Self;
+    type SerializeMap = SerializeMap<'a>;
+    type SerializeStruct = SerializeMap<'a>;
+    type SerializeStructVariant = Self;
+
+    forward_to_subschema_for!(serialize_bool, bool);
+    forward_to_subschema_for!(serialize_i8, i8);
+    forward_to_subschema_for!(serialize_i16, i16);
+    forward_to_subschema_for!(serialize_i32, i32);
+    forward_to_subschema_for!(serialize_i64, i64);
+    forward_to_subschema_for!(serialize_i128, i128);
+    forward_to_subschema_for!(serialize_u8, u8);
+    forward_to_subschema_for!(serialize_u16, u16);
+    forward_to_subschema_for!(serialize_u32, u32);
+    forward_to_subschema_for!(serialize_u64, u64);
+    forward_to_subschema_for!(serialize_u128, u128);
+    forward_to_subschema_for!(serialize_f32, f32);
+    forward_to_subschema_for!(serialize_f64, f64);
+    forward_to_subschema_for!(serialize_char, char);
+    forward_to_subschema_for!(serialize_str, &str);
+    forward_to_subschema_for!(serialize_bytes, &[u8]);
+
+    fn collect_str<T: ?Sized>(self, _value: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: Display,
+    {
+        Ok(self.gen.subschema_for::<String>())
+    }
+
+    fn collect_map<K, V, I>(self, iter: I) -> Result<Self::Ok, Self::Error>
+    where
+        K: serde::Serialize,
+        V: serde::Serialize,
+        I: IntoIterator<Item = (K, V)>,
+    {
+        let value_schema = iter
+            .into_iter()
+            .try_fold(None, |acc, (_, v)| {
+                if acc == Some(Schema::Bool(true)) {
+                    return Ok(acc);
+                }
+
+                let schema = v.serialize(Serializer {
+                    gen: self.gen,
+                    include_title: false,
+                })?;
+                Ok(match &acc {
+                    None => Some(schema),
+                    Some(items) if items != &schema => Some(Schema::Bool(true)),
+                    _ => acc,
+                })
+            })?
+            .unwrap_or(Schema::Bool(true));
+
+        Ok(SchemaObject {
+            instance_type: Some(InstanceType::Object.into()),
+            object: Some(Box::new(ObjectValidation {
+                additional_properties: Some(Box::new(value_schema)),
+                ..ObjectValidation::default()
+            })),
+            ..SchemaObject::default()
+        }
+        .into())
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        Ok(self.gen.subschema_for::<Option<Value>>())
+    }
+
+    fn serialize_some<T: ?Sized>(mut self, value: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        // FIXME nasty duplication of `impl JsonSchema for Option<T>`
+        fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
+            match instance_type {
+                SingleOrVec::Single(ty) if **ty != InstanceType::Null => {
+                    *instance_type = vec![**ty, InstanceType::Null].into()
+                }
+                SingleOrVec::Vec(ty) if !ty.contains(&InstanceType::Null) => {
+                    ty.push(InstanceType::Null)
+                }
+                _ => {}
+            };
+        }
+
+        let mut schema = value.serialize(Serializer {
+            gen: self.gen,
+            include_title: false,
+        })?;
+
+        if self.gen.settings().option_add_null_type {
+            schema = match schema {
+                Schema::Bool(true) => Schema::Bool(true),
+                Schema::Bool(false) => <()>::json_schema(&mut self.gen),
+                Schema::Object(SchemaObject {
+                    instance_type: Some(ref mut instance_type),
+                    ..
+                }) => {
+                    add_null_type(instance_type);
+                    schema
+                }
+                schema => SchemaObject {
+                    subschemas: Some(Box::new(SubschemaValidation {
+                        any_of: Some(vec![schema, <()>::json_schema(&mut self.gen)]),
+                        ..Default::default()
+                    })),
+                    ..Default::default()
+                }
+                .into(),
+            }
+        }
+
+        if self.gen.settings().option_nullable {
+            let mut schema_obj = schema.into_object();
+            schema_obj
+                .extensions
+                .insert("nullable".to_owned(), serde_json::json!(true));
+            schema = Schema::Object(schema_obj);
+        };
+
+        Ok(schema)
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        Ok(self.gen.subschema_for::<()>())
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
+        self.serialize_unit()
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<Self::Ok, Self::Error> {
+        Ok(Schema::Bool(true))
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        name: &'static str,
+        value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        let include_title = self.include_title;
+        let mut result = value.serialize(self);
+
+        if include_title {
+            if let Ok(Schema::Object(ref mut object)) = result {
+                object.metadata().title = Some(name.to_string());
+            }
+        }
+
+        result
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        Ok(Schema::Bool(true))
+    }
+
+    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        Ok(SerializeSeq {
+            gen: self.gen,
+            items: None,
+        })
+    }
+
+    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        Ok(SerializeTuple {
+            gen: self.gen,
+            items: Vec::with_capacity(len),
+            title: "",
+        })
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        let title = if self.include_title { name } else { "" };
+        Ok(SerializeTuple {
+            gen: self.gen,
+            items: Vec::with_capacity(len),
+            title,
+        })
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        Ok(SerializeMap {
+            gen: self.gen,
+            properties: Map::new(),
+            current_key: None,
+            title: "",
+        })
+    }
+
+    fn serialize_struct(
+        self,
+        name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        let title = if self.include_title { name } else { "" };
+        Ok(SerializeMap {
+            gen: self.gen,
+            properties: Map::new(),
+            current_key: None,
+            title,
+        })
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Ok(self)
+    }
+}
+
+impl serde::ser::SerializeTupleVariant for Serializer<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, _value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Schema::Bool(true))
+    }
+}
+
+impl serde::ser::SerializeStructVariant for Serializer<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(
+        &mut self,
+        _key: &'static str,
+        _value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Schema::Bool(true))
+    }
+}
+
+impl serde::ser::SerializeSeq for SerializeSeq<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        if self.items != Some(Schema::Bool(true)) {
+            let schema = value.serialize(Serializer {
+                gen: self.gen,
+                include_title: false,
+            })?;
+            match &self.items {
+                None => self.items = Some(schema),
+                Some(items) => {
+                    if items != &schema {
+                        self.items = Some(Schema::Bool(true))
+                    }
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        let items = self.items.unwrap_or(Schema::Bool(true));
+        Ok(SchemaObject {
+            instance_type: Some(InstanceType::Array.into()),
+            array: Some(Box::new(ArrayValidation {
+                items: Some(items.into()),
+                ..ArrayValidation::default()
+            })),
+            ..SchemaObject::default()
+        }
+        .into())
+    }
+}
+
+impl serde::ser::SerializeTuple for SerializeTuple<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        let schema = value.serialize(Serializer {
+            gen: self.gen,
+            include_title: false,
+        })?;
+        self.items.push(schema);
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        let len = self.items.len().try_into().ok();
+        let mut schema = SchemaObject {
+            instance_type: Some(InstanceType::Array.into()),
+            array: Some(Box::new(ArrayValidation {
+                items: Some(SingleOrVec::Vec(self.items)),
+                max_items: len,
+                min_items: len,
+                ..ArrayValidation::default()
+            })),
+            ..SchemaObject::default()
+        };
+
+        if !self.title.is_empty() {
+            schema.metadata().title = Some(self.title.to_owned());
+        }
+
+        Ok(schema.into())
+    }
+}
+
+impl serde::ser::SerializeTupleStruct for SerializeTuple<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        serde::ser::SerializeTuple::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        serde::ser::SerializeTuple::end(self)
+    }
+}
+
+impl serde::ser::SerializeMap for SerializeMap<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        // FIXME this is too lenient - we should return an error if serde_json
+        // doesn't allow T to be a key of a map.
+        let json = serde_json::to_string(key)?;
+        self.current_key = Some(
+            json.trim_start_matches('"')
+                .trim_end_matches('"')
+                .to_string(),
+        );
+
+        Ok(())
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        let key = self.current_key.take().unwrap_or_default();
+        let schema = value.serialize(Serializer {
+            gen: self.gen,
+            include_title: false,
+        })?;
+        self.properties.insert(key, schema);
+
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        let mut schema = SchemaObject {
+            instance_type: Some(InstanceType::Object.into()),
+            object: Some(Box::new(ObjectValidation {
+                properties: self.properties,
+                ..ObjectValidation::default()
+            })),
+            ..SchemaObject::default()
+        };
+
+        if !self.title.is_empty() {
+            schema.metadata().title = Some(self.title.to_owned());
+        }
+
+        Ok(schema.into())
+    }
+}
+
+impl serde::ser::SerializeStruct for SerializeMap<'_> {
+    type Ok = Schema;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(
+        &mut self,
+        key: &'static str,
+        value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: serde::Serialize,
+    {
+        let prop_schema = value.serialize(Serializer {
+            gen: self.gen,
+            include_title: false,
+        })?;
+        self.properties.insert(key.to_string(), prop_schema);
+
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        serde::ser::SerializeMap::end(self)
+    }
+}


Test patch:
diff --git a/schemars/tests/expected/from_value_2019_09.json b/schemars/tests/expected/from_value_2019_09.json
new file mode 100644
index 0000000..77e8953
--- /dev/null
+++ b/schemars/tests/expected/from_value_2019_09.json
@@ -0,0 +1,80 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "title": "MyStruct",
+  "examples": [
+    {
+      "myBool": true,
+      "myInnerStruct": {
+        "my_empty_map": {},
+        "my_empty_vec": [],
+        "my_map": {
+          "": 0.0
+        },
+        "my_tuple": [
+          "💩",
+          42
+        ],
+        "my_vec": [
+          "hello",
+          "world"
+        ]
+      },
+      "myInt": 123,
+      "myNullableEnum": null
+    }
+  ],
+  "type": "object",
+  "properties": {
+    "myInt": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "myBool": {
+      "type": "boolean"
+    },
+    "myNullableEnum": true,
+    "myInnerStruct": {
+      "type": "object",
+      "properties": {
+        "my_map": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "number",
+            "format": "double"
+          }
+        },
+        "my_vec": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "my_empty_map": {
+          "type": "object",
+          "additionalProperties": true
+        },
+        "my_empty_vec": {
+          "type": "array",
+          "items": true
+        },
+        "my_tuple": {
+          "type": "array",
+          "items": [
+            {
+              "type": "string",
+              "maxLength": 1,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "format": "uint8",
+              "minimum": 0.0
+            }
+          ],
+          "maxItems": 2,
+          "minItems": 2
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/from_value_draft07.json b/schemars/tests/expected/from_value_draft07.json
new file mode 100644
index 0000000..936eeaf
--- /dev/null
+++ b/schemars/tests/expected/from_value_draft07.json
@@ -0,0 +1,80 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "MyStruct",
+  "examples": [
+    {
+      "myBool": true,
+      "myInnerStruct": {
+        "my_empty_map": {},
+        "my_empty_vec": [],
+        "my_map": {
+          "": 0.0
+        },
+        "my_tuple": [
+          "💩",
+          42
+        ],
+        "my_vec": [
+          "hello",
+          "world"
+        ]
+      },
+      "myInt": 123,
+      "myNullableEnum": null
+    }
+  ],
+  "type": "object",
+  "properties": {
+    "myInt": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "myBool": {
+      "type": "boolean"
+    },
+    "myNullableEnum": true,
+    "myInnerStruct": {
+      "type": "object",
+      "properties": {
+        "my_map": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "number",
+            "format": "double"
+          }
+        },
+        "my_vec": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "my_empty_map": {
+          "type": "object",
+          "additionalProperties": true
+        },
+        "my_empty_vec": {
+          "type": "array",
+          "items": true
+        },
+        "my_tuple": {
+          "type": "array",
+          "items": [
+            {
+              "type": "string",
+              "maxLength": 1,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "format": "uint8",
+              "minimum": 0.0
+            }
+          ],
+          "maxItems": 2,
+          "minItems": 2
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/from_value_openapi3.json b/schemars/tests/expected/from_value_openapi3.json
new file mode 100644
index 0000000..52514e2
--- /dev/null
+++ b/schemars/tests/expected/from_value_openapi3.json
@@ -0,0 +1,80 @@
+{
+  "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
+  "title": "MyStruct",
+  "type": "object",
+  "properties": {
+    "myInt": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "myBool": {
+      "type": "boolean"
+    },
+    "myNullableEnum": {
+      "nullable": true
+    },
+    "myInnerStruct": {
+      "type": "object",
+      "properties": {
+        "my_map": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "number",
+            "format": "double"
+          }
+        },
+        "my_vec": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "my_empty_map": {
+          "type": "object",
+          "additionalProperties": true
+        },
+        "my_empty_vec": {
+          "type": "array",
+          "items": {}
+        },
+        "my_tuple": {
+          "type": "array",
+          "items": [
+            {
+              "type": "string",
+              "maxLength": 1,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "format": "uint8",
+              "minimum": 0.0
+            }
+          ],
+          "maxItems": 2,
+          "minItems": 2
+        }
+      }
+    }
+  },
+  "example": {
+    "myBool": true,
+    "myInnerStruct": {
+      "my_empty_map": {},
+      "my_empty_vec": [],
+      "my_map": {
+        "": 0.0
+      },
+      "my_tuple": [
+        "💩",
+        42
+      ],
+      "my_vec": [
+        "hello",
+        "world"
+      ]
+    },
+    "myInt": 123,
+    "myNullableEnum": null
+  }
+}
\ No newline at end of file
diff --git a/schemars/tests/from_value.rs b/schemars/tests/from_value.rs
new file mode 100644
index 0000000..9e6943f
--- /dev/null
+++ b/schemars/tests/from_value.rs
@@ -0,0 +1,77 @@
+mod util;
+use std::collections::HashMap;
+
+use schemars::gen::SchemaSettings;
+use serde::Serialize;
+use util::*;
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MyStruct {
+    pub my_int: i32,
+    pub my_bool: bool,
+    pub my_nullable_enum: Option<MyEnum>,
+    pub my_inner_struct: MyInnerStruct,
+    #[serde(skip)]
+    pub skip: i32,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub skip_if_none: Option<MyEnum>,
+}
+
+#[derive(Serialize)]
+pub struct MyInnerStruct {
+    pub my_map: HashMap<String, f64>,
+    pub my_vec: Vec<&'static str>,
+    pub my_empty_map: HashMap<String, f64>,
+    pub my_empty_vec: Vec<&'static str>,
+    pub my_tuple: (char, u8),
+}
+
+#[derive(Serialize)]
+pub enum MyEnum {
+    StringNewType(String),
+    StructVariant { floats: Vec<f32> },
+}
+
+fn make_value() -> MyStruct {
+    let mut value = MyStruct {
+        my_int: 123,
+        my_bool: true,
+        my_nullable_enum: None,
+        my_inner_struct: MyInnerStruct {
+            my_map: HashMap::new(),
+            my_vec: vec!["hello", "world"],
+            my_empty_map: HashMap::new(),
+            my_empty_vec: vec![],
+            my_tuple: ('💩', 42),
+        },
+        skip: 123,
+        skip_if_none: None,
+    };
+    value.my_inner_struct.my_map.insert(String::new(), 0.0);
+    value
+}
+
+#[test]
+fn schema_from_value_matches_draft07() -> TestResult {
+    let gen = SchemaSettings::draft07().into_generator();
+    let actual = gen.into_root_schema_for_value(&make_value())?;
+
+    test_schema(&actual, "from_value_draft07")
+}
+
+#[test]
+fn schema_from_value_matches_2019_09() -> TestResult {
+    let gen = SchemaSettings::draft2019_09().into_generator();
+    let actual = gen.into_root_schema_for_value(&make_value())?;
+
+    test_schema(&actual, "from_value_2019_09")
+}
+
+#[test]
+fn schema_from_value_matches_openapi3() -> TestResult {
+    let gen = SchemaSettings::openapi3().into_generator();
+    let actual = gen.into_root_schema_for_value(&make_value())?;
+
+    test_schema(&actual, "from_value_openapi3")
+}
diff --git a/schemars/tests/ui/schema_for_arg_value.rs b/schemars/tests/ui/schema_for_arg_value.rs
new file mode 100644
index 0000000..b5ead75
--- /dev/null
+++ b/schemars/tests/ui/schema_for_arg_value.rs
@@ -0,0 +1,5 @@
+use schemars::schema_for;
+
+fn main() {
+    let _schema = schema_for!(123);
+}
diff --git a/schemars/tests/ui/schema_for_arg_value.stderr b/schemars/tests/ui/schema_for_arg_value.stderr
new file mode 100644
index 0000000..c787985
--- /dev/null
+++ b/schemars/tests/ui/schema_for_arg_value.stderr
@@ -0,0 +1,7 @@
+error: This argument to `schema_for!` is not a type - did you mean to use `schema_for_value!` instead?
+ --> $DIR/schema_for_arg_value.rs:4:19
+  |
+4 |     let _schema = schema_for!(123);
+  |                   ^^^^^^^^^^^^^^^^
+  |
+  = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs
index fa191fe..9f365d6 100644
--- a/schemars/tests/util/mod.rs
+++ b/schemars/tests/util/mod.rs
@@ -18,7 +18,7 @@ pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
     test_schema(&actual, file)
 }
 
-fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
+pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
     let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
         Ok(j) => j,
         Err(e) => {