Repository: mitsuhiko/minijinja (language: rust)

Issue:
auto impl ArgType trait for a Deserializeable value.
Currently, all filter parameter types need to implement the ArgType trait. This is mainly to implement the from_value function. But while a variable can be convert to Value by serde::serialize, it cannot be vice versa.

Is there a possibility to deserialize the Value back into a variable? Going a step further, is it possible to implement the ArgType trait for all types that implement Deserialize ?


Discussion / hints:
I use such codes:

```rust
impl<'a> ArgType<'a> for Time {
    type Output = Self;

    fn from_value(value: Option<&'a Value>) -> Result<Self::Output, minijinja::Error> {
        let a = serde_json::to_string(value.unwrap()).unwrap();
        Ok(serde_json::from_str(&a).unwrap())
    }
}
```
I think what this is calling for is probably two things:

- implementing `IntoDeserializer` for `Value`.
- Adding a wrapper type `ViaDeserialize<T>` (name tbd) which uses `IntoDeserializer` and `Deref`s into `T`.

With that one could do something like this:

```rust
fn strftime(time: ViaDeserialize<Time>) -> String {
    time.to_string()
}
```
Yes, this is also a good solution. But is there an implementation that makes the user imperceptible? For example, if Time implements the Deserialize trait, then it can be used directly:

```rust
fn strftime(time: Time) -> String {
    time.to_string()
}
```
I don't think this can be done without specialization.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index caf78d5f..4675f7bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ All notable changes to MiniJinja are documented here.
 ## 1.0.6
 
 - Re-report `Value` directly from the crate root for convenience.
+- It's now possible to `deserialize` from a `Value`.  Additionally the
+  `ViaDeserialize<T>` argument type was added to support value conversions
+  via serde as argument type.  #325
 
 ## 1.0.5
 
diff --git a/examples/README.md b/examples/README.md
index b89ec0df..9e99cab6 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -15,6 +15,7 @@ the `cargo run` command.  Alternatively you can do `cargo run -p example-name`.
 * [dsl](dsl): shows how to use MiniJinja has a DSL.
 * [dynamic-context](dynamic-context): demonstrates how to use dynamic objects as template context.
 * [dynamic-objects](dynamic-objects): demonstrates how to use dynamic objects in templates.
+* [deserialize](deserialize): demonstrates how you can deserialize directly from a value.
 * [error](error): shows the built-in error reporting support.
 * [eval-to-state](eval-to-state): Demonstrates what can be done with evaluating to state.
 * [expr](expr): demonstrates the expression evaluation support.
diff --git a/examples/deserialize/Cargo.toml b/examples/deserialize/Cargo.toml
new file mode 100644
index 00000000..087c7870
--- /dev/null
+++ b/examples/deserialize/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "deserialize"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+minijinja = { path = "../../minijinja" }
+serde = { version = "1.0.159", features = ["derive"] }
diff --git a/examples/deserialize/README.md b/examples/deserialize/README.md
new file mode 100644
index 00000000..22f3546b
--- /dev/null
+++ b/examples/deserialize/README.md
@@ -0,0 +1,8 @@
+# deserialize
+
+Demonstrates how you can deserialize MiniJinja values
+into other types.
+
+```console
+$ cargo run
+```
diff --git a/examples/deserialize/src/main.rs b/examples/deserialize/src/main.rs
new file mode 100644
index 00000000..8d838c5e
--- /dev/null
+++ b/examples/deserialize/src/main.rs
@@ -0,0 +1,43 @@
+use std::path::PathBuf;
+
+use minijinja::value::{Value, ViaDeserialize};
+use minijinja::{context, Environment};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Point {
+    x: f32,
+    y: f32,
+}
+
+fn dirname(path: ViaDeserialize<PathBuf>) -> String {
+    match path.parent() {
+        Some(parent) => parent.display().to_string(),
+        None => "".to_string(),
+    }
+}
+
+fn main() {
+    let mut env = Environment::new();
+    env.add_template("path.txt", include_str!("path.txt"))
+        .unwrap();
+    env.add_filter("dirname", dirname);
+
+    // First example: shows ViaDeserialize
+    let template = env.get_template("path.txt").unwrap();
+    println!(
+        "{}",
+        template
+            .render(context! {
+                path => std::env::current_dir().unwrap(),
+            })
+            .unwrap()
+    );
+
+    // Second example shows how you can deserialize directly from a value
+    let point = Point { x: -1.0, y: 1.0 };
+    let point_value = Value::from_serializable(&point);
+    println!("Point serialized as value: {}", point_value);
+    let point_again = Point::deserialize(point_value).unwrap();
+    println!("Point deserialization: {:?}", point_again);
+}
diff --git a/examples/deserialize/src/path.txt b/examples/deserialize/src/path.txt
new file mode 100644
index 00000000..baf39f05
--- /dev/null
+++ b/examples/deserialize/src/path.txt
@@ -0,0 +1,2 @@
+Path: {{ path }}
+Parent: {{ path|dirname }}
\ No newline at end of file
diff --git a/minijinja/src/error.rs b/minijinja/src/error.rs
index ba363a7d..e5e63ce2 100644
--- a/minijinja/src/error.rs
+++ b/minijinja/src/error.rs
@@ -127,6 +127,9 @@ pub enum ErrorKind {
     UndefinedError,
     /// Not able to serialize this value.
     BadSerialization,
+    /// Not able to deserialize this value.
+    #[cfg(feature = "deserialization")]
+    CannotDeserialize,
     /// An error happened in an include.
     BadInclude,
     /// An error happened in a super block.
@@ -167,6 +170,8 @@ impl ErrorKind {
             ErrorKind::EvalBlock => "could not render block",
             ErrorKind::CannotUnpack => "cannot unpack",
             ErrorKind::WriteFailure => "failed to write output",
+            #[cfg(feature = "deserialization")]
+            ErrorKind::CannotDeserialize => "cannot deserialize",
             #[cfg(feature = "fuel")]
             ErrorKind::OutOfFuel => "engine ran out of fuel",
             #[cfg(feature = "custom_syntax")]
diff --git a/minijinja/src/lib.rs b/minijinja/src/lib.rs
index 9164b343..8c2ce640 100644
--- a/minijinja/src/lib.rs
+++ b/minijinja/src/lib.rs
@@ -155,7 +155,8 @@
 //!   - `debug`: if this feature is removed some debug functionality of the engine is
 //!     removed as well.  This mainly affects the quality of error reporting.
 //!   - `deserialization`: when removed this disables deserialization support for
-//!     the [`Value`](crate::value::Value) type.
+//!     the [`Value`](crate::value::Value) type, removes the `ViaDeserialize`
+//!     type and the error type no longer implements `serde::de::Error`.
 //!
 //! There are some additional features that provide extra functionality:
 //!
diff --git a/minijinja/src/value/argtypes.rs b/minijinja/src/value/argtypes.rs
index cfc814e0..88465c9e 100644
--- a/minijinja/src/value/argtypes.rs
+++ b/minijinja/src/value/argtypes.rs
@@ -111,6 +111,9 @@ where
 /// * values: [`Value`], `&Value`
 /// * vectors: [`Vec<T>`]
 /// * sequences: [`&dyn SeqObject`](crate::value::SeqObject)
+/// * serde deserializable: [`ViaDeserialize<T>`](crate::value::deserialize::ViaDeserialize)
+/// * keyword arguments: [`Kwargs`]
+/// * leftover arguments: [`Rest<T>`]
 ///
 /// The type is also implemented for optional values (`Option<T>`) which is used
 /// to encode optional parameters to filters, functions or tests.  Additionally
diff --git a/minijinja/src/value/deserialize.rs b/minijinja/src/value/deserialize.rs
index 65acc116..00c3d9ff 100644
--- a/minijinja/src/value/deserialize.rs
+++ b/minijinja/src/value/deserialize.rs
@@ -1,7 +1,11 @@
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+
 use serde::de::{self, MapAccess, SeqAccess, Visitor};
-use serde::Deserialize;
+use serde::{forward_to_deserialize_any, Deserialize};
 
-use crate::value::{KeyRef, MapType, Value, ValueMap, ValueRepr};
+use crate::value::{ArgType, KeyRef, MapType, Value, ValueKind, ValueMap, ValueRepr};
+use crate::{Error, ErrorKind};
 
 impl<'de> Deserialize<'de> for Value {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -101,3 +105,389 @@ impl<'de> Visitor<'de> for ValueVisitor {
         Ok(Value(ValueRepr::Map(rv.into(), MapType::Normal)))
     }
 }
+
+/// Utility type to deserialize an argument.
+///
+/// This allows you to directly accept a type that implements [`Deserialize`] as an
+/// argument to a filter or test.  The type dereferences into the inner type and
+/// it also lets you move out the inner type.
+///
+/// ```rust
+/// # use minijinja::Environment;
+/// use std::path::PathBuf;
+/// use minijinja::value::ViaDeserialize;
+///
+/// fn dirname(path: ViaDeserialize<PathBuf>) -> String {
+///     match path.parent() {
+///         Some(parent) => parent.display().to_string(),
+///         None => "".to_string()
+///     }
+/// }
+///
+/// # let mut env = Environment::new();
+/// env.add_filter("dirname", dirname);
+/// ```
+#[cfg_attr(docsrs, doc(cfg(feature = "deserialization")))]
+pub struct ViaDeserialize<T>(pub T);
+
+impl<'a, T: Deserialize<'a>> ArgType<'a> for ViaDeserialize<T> {
+    type Output = Self;
+
+    fn from_value(value: Option<&'a Value>) -> Result<Self::Output, Error> {
+        match value {
+            Some(value) => T::deserialize(value).map(ViaDeserialize),
+            None => Err(Error::from(ErrorKind::MissingArgument)),
+        }
+    }
+}
+
+impl<T> Deref for ViaDeserialize<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<T> DerefMut for ViaDeserialize<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+struct ValueDeserializer<E> {
+    value: Value,
+    error: PhantomData<fn() -> E>,
+}
+
+impl<E> ValueDeserializer<E> {
+    fn new(value: Value) -> ValueDeserializer<E> {
+        ValueDeserializer {
+            value,
+            error: PhantomData,
+        }
+    }
+}
+
+impl<'de, E> de::Deserializer<'de> for ValueDeserializer<E>
+where
+    E: de::Error,
+{
+    type Error = E;
+
+    fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+        match self.value.0 {
+            ValueRepr::Invalid(ref error) => Err(de::Error::custom(error)),
+            ValueRepr::Bool(v) => visitor.visit_bool(v),
+            ValueRepr::U64(v) => visitor.visit_u64(v),
+            ValueRepr::I64(v) => visitor.visit_i64(v),
+            ValueRepr::I128(v) => visitor.visit_i128(v.0),
+            ValueRepr::U128(v) => visitor.visit_u128(v.0),
+            ValueRepr::F64(v) => visitor.visit_f64(v),
+            ValueRepr::String(ref v, _) => visitor.visit_str(v),
+            ValueRepr::Undefined | ValueRepr::None => visitor.visit_unit(),
+            ValueRepr::Bytes(ref v) => visitor.visit_bytes(v),
+            ValueRepr::Seq(_) | ValueRepr::Map(..) | ValueRepr::Dynamic(_) => {
+                if let Some(s) = self.value.as_seq() {
+                    visitor.visit_seq(de::value::SeqDeserializer::new(
+                        s.iter().map(ValueDeserializer::new),
+                    ))
+                } else if self.value.kind() == ValueKind::Map {
+                    let iter = ok!(self.value.try_iter().map_err(|e| { de::Error::custom(e) }));
+                    visitor.visit_map(de::value::MapDeserializer::new(iter.map(|k| {
+                        (
+                            ValueDeserializer::new(k.clone()),
+                            ValueDeserializer::new(self.value.get_item(&k).unwrap_or_default()),
+                        )
+                    })))
+                } else {
+                    Err(de::Error::invalid_type(
+                        value_to_unexpected(&self.value),
+                        &"supported value",
+                    ))
+                }
+            }
+        }
+    }
+
+    fn deserialize_option<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+        match self.value.0 {
+            ValueRepr::None | ValueRepr::Undefined => visitor.visit_unit(),
+            _ => visitor.visit_some(self),
+        }
+    }
+
+    fn deserialize_enum<V: de::Visitor<'de>>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error> {
+        let (variant, value) = match self.value.kind() {
+            ValueKind::Map => {
+                let mut iter = ok!(self
+                    .value
+                    .try_iter()
+                    .map_err(|err| { de::Error::custom(err) }));
+                let variant = match iter.next() {
+                    Some(v) => v,
+                    None => {
+                        return Err(de::Error::invalid_value(
+                            de::Unexpected::Map,
+                            &"map with a single key",
+                        ));
+                    }
+                };
+                // enums are encoded as maps with a single key:value pair
+                if iter.next().is_some() {
+                    return Err(de::Error::invalid_value(
+                        de::Unexpected::Map,
+                        &"map with a single key",
+                    ));
+                }
+                let val = self.value.get_item_opt(&variant);
+                (variant, val)
+            }
+            ValueKind::String => (self.value, None),
+            _other => {
+                return Err(de::Error::invalid_type(
+                    value_to_unexpected(&self.value),
+                    &"string or map",
+                ))
+            }
+        };
+
+        let d = EnumDeserializer {
+            variant,
+            value,
+            error: Default::default(),
+        };
+        visitor.visit_enum(d)
+    }
+
+    forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit
+        seq bytes byte_buf map unit_struct
+        tuple_struct struct tuple ignored_any identifier newtype_struct
+    }
+}
+
+impl<'de, E> de::IntoDeserializer<'de, E> for ValueDeserializer<E>
+where
+    E: de::Error,
+{
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
+struct EnumDeserializer<E> {
+    variant: Value,
+    value: Option<Value>,
+    error: PhantomData<fn() -> E>,
+}
+
+impl<'de, E> de::EnumAccess<'de> for EnumDeserializer<E>
+where
+    E: de::Error,
+{
+    type Error = E;
+    type Variant = VariantDeserializer<Self::Error>;
+
+    fn variant_seed<V>(
+        self,
+        seed: V,
+    ) -> Result<(V::Value, VariantDeserializer<Self::Error>), Self::Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        let visitor = VariantDeserializer {
+            value: self.value,
+            error: Default::default(),
+        };
+        seed.deserialize(ValueDeserializer::new(self.variant))
+            .map(|v| (v, visitor))
+    }
+}
+
+struct VariantDeserializer<E> {
+    value: Option<Value>,
+    error: PhantomData<fn() -> E>,
+}
+
+impl<'de, E> de::VariantAccess<'de> for VariantDeserializer<E>
+where
+    E: de::Error,
+{
+    type Error = E;
+
+    fn unit_variant(self) -> Result<(), Self::Error> {
+        match self.value {
+            Some(value) => de::Deserialize::deserialize(ValueDeserializer::new(value)),
+            None => Ok(()),
+        }
+    }
+
+    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        match self.value {
+            Some(value) => seed.deserialize(ValueDeserializer::new(value)),
+            None => Err(de::Error::invalid_type(
+                de::Unexpected::UnitVariant,
+                &"newtype variant",
+            )),
+        }
+    }
+
+    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value.as_ref().and_then(|x| x.as_seq()) {
+            Some(seq) => de::Deserializer::deserialize_any(
+                de::value::SeqDeserializer::new(seq.iter().map(ValueDeserializer::new)),
+                visitor,
+            ),
+            None => Err(de::Error::invalid_type(
+                self.value
+                    .as_ref()
+                    .map_or(de::Unexpected::Unit, value_to_unexpected),
+                &"tuple variant",
+            )),
+        }
+    }
+
+    fn struct_variant<V>(
+        self,
+        _fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value.as_ref().map(|x| (x.kind(), x)) {
+            Some((ValueKind::Map, val)) => {
+                let iter = ok!(val
+                    .try_iter()
+                    .map_err(|_| { de::Error::custom("non iterable map") }));
+                de::Deserializer::deserialize_any(
+                    de::value::MapDeserializer::new(iter.map(|k| {
+                        (
+                            ValueDeserializer::new(k.clone()),
+                            ValueDeserializer::new(val.get_item(&k).unwrap_or_default()),
+                        )
+                    })),
+                    visitor,
+                )
+            }
+            _ => Err(de::Error::invalid_type(
+                self.value
+                    .as_ref()
+                    .map_or(de::Unexpected::Unit, value_to_unexpected),
+                &"struct variant",
+            )),
+        }
+    }
+}
+
+fn value_to_unexpected(value: &Value) -> de::Unexpected {
+    match value.0 {
+        ValueRepr::Undefined | ValueRepr::None => de::Unexpected::Unit,
+        ValueRepr::Bool(val) => de::Unexpected::Bool(val),
+        ValueRepr::U64(val) => de::Unexpected::Unsigned(val),
+        ValueRepr::I64(val) => de::Unexpected::Signed(val),
+        ValueRepr::F64(val) => de::Unexpected::Float(val),
+        ValueRepr::Invalid(_) => de::Unexpected::Other("<invalid value>"),
+        ValueRepr::U128(val) => {
+            let unsigned = val.0 as u64;
+            if unsigned as u128 == val.0 {
+                de::Unexpected::Unsigned(unsigned)
+            } else {
+                de::Unexpected::Other("u128")
+            }
+        }
+        ValueRepr::I128(val) => {
+            let signed = val.0 as i64;
+            if signed as i128 == val.0 {
+                de::Unexpected::Signed(signed)
+            } else {
+                de::Unexpected::Other("u128")
+            }
+        }
+        ValueRepr::String(ref s, _) => de::Unexpected::Str(s),
+        ValueRepr::Bytes(ref b) => de::Unexpected::Bytes(b),
+        ValueRepr::Seq(_) => de::Unexpected::Seq,
+        ValueRepr::Map(_, _) => de::Unexpected::Map,
+        ValueRepr::Dynamic(ref d) => match d.kind() {
+            super::ObjectKind::Plain => de::Unexpected::Other("plain object"),
+            super::ObjectKind::Seq(_) => de::Unexpected::Seq,
+            super::ObjectKind::Struct(_) => de::Unexpected::Map,
+        },
+    }
+}
+
+/// When the `deserialization` feature is enabled, the MiniJinja error type
+/// can be used as serde deserialization error.
+#[cfg_attr(docsrs, doc(cfg(feature = "deserialization")))]
+impl de::Error for Error {
+    fn custom<T>(msg: T) -> Self
+    where
+        T: std::fmt::Display,
+    {
+        Error::new(ErrorKind::CannotDeserialize, msg.to_string())
+    }
+}
+
+impl<'de> de::Deserializer<'de> for Value {
+    type Error = Error;
+
+    fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+        ValueDeserializer::new(self).deserialize_any(visitor)
+    }
+
+    fn deserialize_option<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+        ValueDeserializer::new(self).deserialize_option(visitor)
+    }
+
+    fn deserialize_enum<V: de::Visitor<'de>>(
+        self,
+        name: &'static str,
+        variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error> {
+        ValueDeserializer::new(self).deserialize_enum(name, variants, visitor)
+    }
+
+    fn deserialize_newtype_struct<V: de::Visitor<'de>>(
+        self,
+        name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Self::Error> {
+        ValueDeserializer::new(self).deserialize_newtype_struct(name, visitor)
+    }
+
+    forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit
+        seq bytes byte_buf map unit_struct
+        tuple_struct struct tuple ignored_any identifier
+    }
+}
+
+impl<'de, 'v> de::Deserializer<'de> for &'v Value {
+    type Error = Error;
+
+    fn deserialize_any<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+        ValueDeserializer::new(self.clone()).deserialize_any(visitor)
+    }
+
+    forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit
+        seq bytes byte_buf map unit_struct
+        tuple_struct struct tuple ignored_any identifier
+        option enum newtype_struct
+    }
+}
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index a09db5b0..2339c711 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -9,7 +9,7 @@
 //! filter to take a [`String`](std::string::String).  However for some more
 //! advanced use cases it's useful to know that this type exists.
 //!
-//! # Converting Values
+//! # Basic Value Conversions
 //!
 //! Values are typically created via the [`From`] trait:
 //!
@@ -31,11 +31,22 @@
 //! let value: Value = [("key", "value")].into_iter().collect();
 //! ```
 //!
+//! To to into the inverse directly the various [`TryFrom`](std::convert::TryFrom)
+//! implementations can be used:
+//!
+//! ```
+//! # use minijinja::value::Value;
+//! use std::convert::TryFrom;
+//! let v = u64::try_from(Value::from(42)).unwrap();
+//! ```
+//!
 //! The special [`Undefined`](Value::UNDEFINED) value also exists but does not
 //! have a rust equivalent.  It can be created via the [`UNDEFINED`](Value::UNDEFINED)
 //! constant.
 //!
-//! MiniJinja will however create values via an indirection via [`serde`] when
+//! # Serde Conversions
+//!
+//! MiniJinja will usually however create values via an indirection via [`serde`] when
 //! a template is rendered or an expression is evaluated.  This can also be
 //! triggered manually by using the [`Value::from_serializable`] method:
 //!
@@ -44,14 +55,21 @@
 //! let value = Value::from_serializable(&[1, 2, 3]);
 //! ```
 //!
-//! To to into the inverse directly the various [`TryFrom`](std::convert::TryFrom)
-//! implementations can be used:
+//! The inverse of that operation is to pass a value directly as serializer to
+//! a type that supports deserialization.  This requires the `deserialization`
+//! feature.
 //!
-//! ```
-//! # use minijinja::value::Value;
-//! use std::convert::TryFrom;
-//! let v = u64::try_from(Value::from(42)).unwrap();
-//! ```
+#![cfg_attr(
+    feature = "deserialization",
+    doc = r"
+```
+# use minijinja::value::Value;
+use serde::Deserialize;
+let value = Value::from(vec![1, 2, 3]);
+let vec = Vec::<i32>::deserialize(value).unwrap();
+```
+"
+)]
 //!
 //! # Value Function Arguments
 //!
@@ -138,6 +156,9 @@ mod object;
 pub(crate) mod ops;
 mod serialize;
 
+#[cfg(feature = "deserialization")]
+pub use self::deserialize::ViaDeserialize;
+
 pub(crate) use crate::value::keyref::KeyRef;
 
 // We use in-band signalling to roundtrip some internal values.  This is
@@ -567,6 +588,11 @@ impl Value {
     /// engine today.  This is for instance the case for when keys are used in hash maps
     /// that the engine cannot deal with.  Invalid values are considered an implementation
     /// detail.  There is currently no API to validate a value.
+    ///
+    /// If the `deserialization` feature is enabled then the inverse of this method
+    /// is to use the [`Value`] type as serializer.  You can pass a value into the
+    /// [`deserialize`](serde::Deserialize::deserialize) method of a type that supports
+    /// serde deserialization.
     pub fn from_serializable<T: Serialize>(value: &T) -> Value {
         let _serialization_guard = mark_internal_serialization();
         let _optimization_guard = value_optimization();


Test patch:
diff --git a/minijinja/tests/test_deserialization.rs b/minijinja/tests/test_deserialization.rs
new file mode 100644
index 00000000..e0d0484a
--- /dev/null
+++ b/minijinja/tests/test_deserialization.rs
@@ -0,0 +1,120 @@
+#![cfg(feature = "deserialization")]
+use std::collections::BTreeMap;
+
+use serde::{Deserialize, Serialize};
+use similar_asserts::assert_eq;
+
+use minijinja::value::{SeqObject, StructObject, Value};
+
+#[test]
+fn test_seq() {
+    let v = Vec::<i32>::deserialize(Value::from(vec![1, 2, 3])).unwrap();
+    assert_eq!(v, vec![1, 2, 3]);
+}
+
+#[test]
+fn test_seq_object() {
+    struct X;
+
+    impl SeqObject for X {
+        fn get_item(&self, idx: usize) -> Option<Value> {
+            if idx < 3 {
+                Some(Value::from(idx + 1))
+            } else {
+                None
+            }
+        }
+
+        fn item_count(&self) -> usize {
+            3
+        }
+    }
+
+    let v = Vec::<i32>::deserialize(Value::from_seq_object(X)).unwrap();
+    assert_eq!(v, vec![1, 2, 3]);
+}
+
+#[test]
+fn test_map() {
+    let v = BTreeMap::<String, i32>::deserialize(Value::from_iter([
+        ("foo", Value::from(1)),
+        ("bar", Value::from(2)),
+    ]))
+    .unwrap();
+    assert_eq!(
+        v,
+        BTreeMap::from_iter([("foo".to_string(), 1), ("bar".to_string(), 2)])
+    );
+}
+
+#[test]
+fn test_struct_object() {
+    struct X;
+
+    impl StructObject for X {
+        fn get_field(&self, name: &str) -> Option<Value> {
+            match name {
+                "a" => Some(Value::from(1)),
+                "b" => Some(Value::from(2)),
+                _ => None,
+            }
+        }
+        fn static_fields(&self) -> Option<&'static [&'static str]> {
+            Some(&["a", "b"])
+        }
+    }
+
+    let v = BTreeMap::<String, i32>::deserialize(Value::from_struct_object(X)).unwrap();
+    assert_eq!(
+        v,
+        BTreeMap::from_iter([("a".to_string(), 1), ("b".to_string(), 2)])
+    );
+}
+
+#[test]
+fn test_basics() {
+    assert_eq!(bool::deserialize(Value::from(true)).unwrap(), true);
+    assert_eq!(bool::deserialize(Value::from(false)).unwrap(), false);
+    assert_eq!(f32::deserialize(Value::from(1.0)).unwrap(), 1.0);
+    assert_eq!(i32::deserialize(Value::from(2)).unwrap(), 2);
+    assert_eq!(String::deserialize(Value::from("foo")).unwrap(), "foo");
+    assert_eq!(Option::<i32>::deserialize(Value::from(2)).unwrap(), Some(2));
+    assert_eq!(Option::<i32>::deserialize(Value::from(())).unwrap(), None);
+}
+
+#[test]
+fn test_enum() {
+    #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+    enum Foo {
+        A,
+        B,
+        C(i32),
+    }
+
+    assert_eq!(Foo::deserialize(Value::from("A")).unwrap(), Foo::A);
+    assert_eq!(Foo::deserialize(Value::from("B")).unwrap(), Foo::B);
+    assert_eq!(
+        Foo::deserialize(Value::from(BTreeMap::from_iter([("C", 42)]))).unwrap(),
+        Foo::C(42)
+    );
+}
+
+#[test]
+fn test_invalid() {
+    struct X;
+
+    impl Serialize for X {
+        fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: serde::Serializer,
+        {
+            Err(serde::ser::Error::custom("meh"))
+        }
+    }
+
+    let v = Value::from_serializable(&X);
+    assert_eq!(v.to_string(), "<invalid value: meh>");
+
+    let err = bool::deserialize(v).unwrap_err();
+    assert_eq!(err.to_string(), "cannot deserialize: meh");
+}
diff --git a/minijinja/tests/test_value.rs b/minijinja/tests/test_value.rs
index 09b14360..f5131998 100644
--- a/minijinja/tests/test_value.rs
+++ b/minijinja/tests/test_value.rs
@@ -484,3 +484,46 @@ fn test_complex_key() {
         Some(Value::UNDEFINED)
     );
 }
+
+#[test]
+#[cfg(feature = "deserialization")]
+fn test_deserialize() {
+    use serde::Deserialize;
+
+    #[derive(Deserialize, Debug, PartialEq, Eq)]
+    struct Point {
+        x: i32,
+        y: i32,
+    }
+
+    let point_value = Value::from_iter([("x", Value::from(42)), ("y", Value::from(-23))]);
+    let point = Point::deserialize(point_value).unwrap();
+
+    assert_eq!(point, Point { x: 42, y: -23 });
+}
+
+#[test]
+#[cfg(feature = "deserialization")]
+fn test_via_deserialize() {
+    use minijinja::value::ViaDeserialize;
+    use serde::Deserialize;
+
+    #[derive(Deserialize, Debug, PartialEq, Eq)]
+    struct Point {
+        x: i32,
+        y: i32,
+    }
+
+    fn foo(point: ViaDeserialize<Point>) -> String {
+        format!("{}, {}", point.x, point.y)
+    }
+
+    let point_value = Value::from_iter([("x", Value::from(42)), ("y", Value::from(-23))]);
+
+    let mut env = Environment::new();
+    env.add_filter("foo", foo);
+    let state = env.empty_state();
+
+    let rv = state.apply_filter("foo", args![point_value]).unwrap();
+    assert_eq!(rv.to_string(), "42, -23");
+}