Repository: extendr/extendr
Pull request #363 (2022-01-18 18:02:53)

Add support for saving/loading R objects.
This PR adds the ability to save and load R objects to RDS.

```
        let v = Robj::from(1).save_to_bytes(PstreamFormat::AsciiFormat, 3, None)?;
        let s = std::str::from_utf8(&v).unwrap();
        assert!(s.starts_with("A\n"));
        assert!(s.contains("\nUTF-8\n"));
```



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c92f1a63e..772b7f477 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ## extendr devel
 
+- Register S3 method `[[.rust_class` just as `$.rust_class`.
+
 - Extra Serialize support.
 
 - Direct Debug support for wrappers.
diff --git a/extendr-api/src/deserializer.rs b/extendr-api/src/deserializer.rs
new file mode 100644
index 000000000..d3762fe3a
--- /dev/null
+++ b/extendr-api/src/deserializer.rs
@@ -0,0 +1,1149 @@
+//! Convert R objects to a wide variety of types.
+//!
+use crate::error::{Error, Result};
+use crate::na::CanBeNA;
+use crate::robj::{Attributes, Length, Robj, Types};
+use crate::scalar::{Rbool, Rfloat, Rint};
+use crate::wrapper::{Doubles, Integers, List, Logicals, Rstr, Strings};
+use crate::Rany;
+use serde::de::{
+    Deserialize, DeserializeSeed, Deserializer, EnumAccess, MapAccess, SeqAccess, VariantAccess,
+    Visitor,
+};
+use serde::forward_to_deserialize_any;
+use std::convert::TryFrom;
+
+/// Convert any R object to a Deserialize object.
+pub fn from_robj<'de, T>(robj: &'de Robj) -> Result<T>
+where
+    T: Deserialize<'de>,
+{
+    let t = T::deserialize(robj)?;
+    Ok(t)
+}
+
+// Allow errors to popagate to extendr errors.
+impl serde::de::Error for Error {
+    fn custom<T>(msg: T) -> Self
+    where
+        T: std::fmt::Display,
+    {
+        Error::Other(msg.to_string())
+    }
+}
+
+// Convert unnamed lists to sequences.
+struct ListGetter<'a> {
+    list: &'a [Robj],
+}
+
+impl<'de> SeqAccess<'de> for ListGetter<'de> {
+    type Error = Error;
+
+    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
+    where
+        T: DeserializeSeed<'de>,
+    {
+        if self.list.is_empty() {
+            Ok(None)
+        } else {
+            let e = &self.list[0];
+            self.list = &self.list[1..];
+            seed.deserialize(e).map(Some)
+        }
+    }
+}
+
+// Convert named lists to maps.
+struct NamedListGetter<'a> {
+    keys: &'a [Rstr],
+    values: &'a [Robj],
+}
+
+impl<'de> MapAccess<'de> for NamedListGetter<'de> {
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
+    where
+        K: DeserializeSeed<'de>,
+    {
+        if self.keys.is_empty() {
+            Ok(None)
+        } else {
+            let e = &self.keys[0];
+            self.keys = &self.keys[1..];
+            seed.deserialize(e).map(Some)
+        }
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
+    where
+        V: DeserializeSeed<'de>,
+    {
+        let e = &self.values[0];
+        self.values = &self.values[1..];
+        seed.deserialize(e)
+    }
+}
+
+// Allow us to use Integers, Doubles and Logicals.
+struct SliceGetter<'a, E> {
+    list: &'a [E],
+}
+
+// Allow us to use Integers and Rint.
+impl<'de> Deserializer<'de> for Rint {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if let Some(val) = self.into() {
+            visitor.visit_i32(val)
+        } else {
+            visitor.visit_unit()
+        }
+    }
+
+    forward_to_deserialize_any! {
+        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
+        bytes byte_buf option unit unit_struct newtype_struct seq tuple
+        tuple_struct map struct enum identifier ignored_any
+    }
+}
+
+// Allow us to use Doubles and Rfloat.
+impl<'de> Deserializer<'de> for Rfloat {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if let Some(val) = self.into() {
+            visitor.visit_f64(val)
+        } else {
+            visitor.visit_unit()
+        }
+    }
+
+    forward_to_deserialize_any! {
+        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
+        bytes byte_buf option unit unit_struct newtype_struct seq tuple
+        tuple_struct map struct enum identifier ignored_any
+    }
+}
+
+// Allow us to use Logicals and Rbool.
+impl<'de> Deserializer<'de> for Rbool {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if let Some(val) = self.into() {
+            visitor.visit_bool(val)
+        } else {
+            visitor.visit_unit()
+        }
+    }
+
+    forward_to_deserialize_any! {
+        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
+        bytes byte_buf option unit unit_struct newtype_struct seq tuple
+        tuple_struct map struct enum identifier ignored_any
+    }
+}
+
+// Decode identifiers from the "names" attribute of lists.
+impl<'de> Deserializer<'de> for &'de Rstr {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        unimplemented!()
+    }
+
+    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_borrowed_str(self.as_str())
+    }
+
+    forward_to_deserialize_any! {
+        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char string
+        str bytes byte_buf option unit unit_struct newtype_struct seq tuple
+        tuple_struct map struct enum ignored_any
+    }
+}
+
+// Get the variant name and content of an enum.
+impl<'de> EnumAccess<'de> for &'de Robj {
+    type Error = Error;
+    type Variant = Self;
+
+    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
+    where
+        V: DeserializeSeed<'de>,
+    {
+        match self.as_any() {
+            Rany::Strings(s) if s.len() == 1 => {
+                let variant = seed.deserialize(self)?;
+                Ok((variant, self))
+            }
+            Rany::List(list) if list.len() == 1 => {
+                if let Some(keys) = self.get_attrib(crate::wrapper::symbol::names_symbol()) {
+                    if let Ok(keys) = Strings::try_from(keys) {
+                        let keys = keys.as_slice();
+                        let values = &list.as_slice()[0];
+                        let variant = seed.deserialize(&keys[0])?;
+                        return Ok((variant, values));
+                    }
+                }
+                Err(Error::Other("Expected named List for enum".into()))
+            }
+            _ => Err(Error::Other("Expected String or List for enum".into())),
+        }
+    }
+}
+
+// Decode enum variants of various kinds.
+impl<'de> VariantAccess<'de> for &'de Robj {
+    type Error = Error;
+
+    fn unit_variant(self) -> Result<()> {
+        Ok(())
+    }
+
+    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
+    where
+        T: DeserializeSeed<'de>,
+    {
+        seed.deserialize(self)
+    }
+
+    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        serde::de::Deserializer::deserialize_seq(self, visitor)
+    }
+
+    fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        serde::de::Deserializer::deserialize_map(self, visitor)
+    }
+}
+
+// Enable sequences from Integers, Doubles and Logicals.
+impl<'de, E: Deserializer<'de>> SeqAccess<'de> for SliceGetter<'de, E>
+where
+    Error: From<<E as Deserializer<'de>>::Error>,
+    E: Copy,
+{
+    type Error = Error;
+
+    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
+    where
+        T: DeserializeSeed<'de>,
+    {
+        if self.list.is_empty() {
+            Ok(None)
+        } else {
+            let res = seed.deserialize(self.list[0])?;
+            self.list = &self.list[1..];
+            Ok(Some(res))
+        }
+    }
+}
+
+// Given an Robj, generate a value of many kinds.
+impl<'de> Deserializer<'de> for &'de Robj {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        let len = self.len();
+        match self.as_any() {
+            Rany::Null(_) => self.deserialize_unit(visitor),
+            Rany::Integers(_v) => {
+                if len == 1 {
+                    self.deserialize_i32(visitor)
+                } else {
+                    self.deserialize_seq(visitor)
+                }
+            }
+            Rany::Doubles(_v) => {
+                if len == 1 {
+                    self.deserialize_f64(visitor)
+                } else {
+                    self.deserialize_seq(visitor)
+                }
+            }
+            Rany::Logicals(_v) => {
+                if len == 1 {
+                    self.deserialize_bool(visitor)
+                } else {
+                    self.deserialize_seq(visitor)
+                }
+            }
+            Rany::List(_v) => self.deserialize_seq(visitor),
+            Rany::Strings(_v) => {
+                if len == 1 {
+                    self.deserialize_str(visitor)
+                } else {
+                    self.deserialize_seq(visitor)
+                }
+            }
+            _ => Err(Error::Other(format!(
+                "deserialize_any: unexpected {:?}",
+                self.rtype()
+            ))),
+        }
+    }
+
+    fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if let Rany::Null(_) = self.as_any() {
+            visitor.visit_unit()
+        } else {
+            Err(Error::ExpectedNull(self.clone()))
+        }
+    }
+
+    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_bool(bool::try_from(self.clone())?)
+    }
+
+    fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_i8(i8::try_from(self.clone())?)
+    }
+
+    fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_i16(i16::try_from(self.clone())?)
+    }
+
+    fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_i32(i32::try_from(self.clone())?)
+    }
+
+    fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_i64(i64::try_from(self.clone())?)
+    }
+
+    fn deserialize_i128<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_i128(i64::try_from(self.clone())? as i128)
+    }
+
+    fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_u8(u8::try_from(self.clone())?)
+    }
+
+    fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_u16(u16::try_from(self.clone())?)
+    }
+
+    fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_u32(u32::try_from(self.clone())?)
+    }
+
+    fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_u64(u64::try_from(self.clone())?)
+    }
+
+    fn deserialize_u128<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_u128(u64::try_from(self.clone())? as u128)
+    }
+
+    fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_f32(f32::try_from(self.clone())?)
+    }
+
+    fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_f64(f64::try_from(self.clone())?)
+    }
+
+    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        let s = <&str>::try_from(self.clone())?;
+        let mut c = s.chars();
+        if let Some(ch) = c.next() {
+            if c.next() == None {
+                return visitor.visit_char(ch);
+            }
+        }
+        Err(Error::ExpectedString(self.clone()))
+    }
+
+    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_borrowed_str(<&str>::try_from(self.clone())?)
+    }
+
+    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_string(<&str>::try_from(self.clone())?.into())
+    }
+
+    fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if let Rany::Raw(val) = self.as_any() {
+            visitor.visit_bytes(val.as_slice())
+        } else {
+            Err(Error::ExpectedRaw(self.clone()))
+        }
+    }
+
+    fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        if let Rany::Raw(val) = self.as_any() {
+            visitor.visit_byte_buf(val.as_slice().to_owned())
+        } else {
+            Err(Error::ExpectedRaw(self.clone()))
+        }
+    }
+
+    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        #![allow(clippy::if_same_then_else)]
+        if let Rany::Null(_) = self.as_any() {
+            visitor.visit_none()
+        } else if self.is_na() {
+            visitor.visit_none()
+        } else {
+            visitor.visit_some(self)
+        }
+    }
+
+    fn deserialize_unit_struct<V>(self, _name: &str, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_unit(visitor)
+    }
+
+    fn deserialize_newtype_struct<V>(self, _name: &str, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
+    fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_seq(visitor)
+    }
+
+    fn deserialize_tuple_struct<V>(
+        self,
+        _name: &'static str,
+        _len: usize,
+        visitor: V,
+    ) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_seq(visitor)
+    }
+
+    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        match self.as_any() {
+            Rany::List(val) => {
+                let lg = ListGetter {
+                    list: val.as_slice(),
+                };
+                Ok(visitor.visit_seq(lg)?)
+            }
+            Rany::Integers(val) => {
+                let lg = SliceGetter { list: &*val };
+                Ok(visitor.visit_seq(lg)?)
+            }
+            Rany::Doubles(val) => {
+                let lg = SliceGetter { list: &*val };
+                Ok(visitor.visit_seq(lg)?)
+            }
+            Rany::Logicals(val) => {
+                let lg = SliceGetter { list: &*val };
+                Ok(visitor.visit_seq(lg)?)
+            }
+            Rany::Strings(_val) => {
+                // Grubby hack that will go away once PRs are merged.
+                // use std::convert::TryInto;
+                // let val : Strings = val.clone().try_into().unwrap();
+                // let lg = StringGetter { list: &*val };
+                // Ok(visitor.visit_seq(lg)?)
+                unimplemented!("Deserialize shortcut for Strings");
+            }
+            _ => Err(Error::ExpectedList(self.clone())),
+        }
+    }
+
+    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        match self.as_any() {
+            Rany::List(val) => {
+                if let Some(keys) = self.get_attrib(crate::wrapper::symbol::names_symbol()) {
+                    if let Ok(keys) = Strings::try_from(keys) {
+                        let keys = keys.as_slice();
+                        let lg = NamedListGetter {
+                            keys,
+                            values: val.as_slice(),
+                        };
+                        return visitor.visit_map(lg);
+                    }
+                }
+                Err(Error::ExpectedList(self.clone()))
+            }
+            _ => Err(Error::ExpectedList(self.clone())),
+        }
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        _name: &'static str,
+        _fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_map(visitor)
+    }
+
+    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_str(visitor)
+    }
+
+    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        self.deserialize_any(visitor)
+    }
+
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value>
+    where
+        V: Visitor<'de>,
+    {
+        visitor.visit_enum(self)
+    }
+}
+
+struct RintVisitor;
+
+impl<'de> Visitor<'de> for RintVisitor {
+    type Value = Rint;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("an integer between -2^31+1 and 2^31")
+    }
+
+    fn visit_i32<E>(self, value: i32) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(value.into())
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Rint::na())
+    }
+}
+
+impl<'de> Deserialize<'de> for Rint {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Rint, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_i32(RintVisitor)
+    }
+}
+
+struct RfloatVisitor;
+
+impl<'de> Visitor<'de> for RfloatVisitor {
+    type Value = Rfloat;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a floating point value")
+    }
+
+    fn visit_f64<E>(self, value: f64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(value.into())
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Rfloat::na())
+    }
+}
+
+impl<'de> Deserialize<'de> for Rfloat {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Rfloat, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_f64(RfloatVisitor)
+    }
+}
+
+struct RboolVisitor;
+
+impl<'de> Visitor<'de> for RboolVisitor {
+    type Value = Rbool;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a boolean point value")
+    }
+
+    fn visit_bool<E>(self, value: bool) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(value.into())
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Rbool::na())
+    }
+}
+
+impl<'de> Deserialize<'de> for Rbool {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Rbool, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_bool(RboolVisitor)
+    }
+}
+
+struct RobjVisitor;
+
+impl<'de> Visitor<'de> for RobjVisitor {
+    type Value = Robj;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a value convertable to a Robj")
+    }
+
+    fn visit_bool<E>(self, value: bool) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(value.into())
+    }
+
+    fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        if v > i32::MIN as i64 && v <= i32::MAX as i64 {
+            Ok((v as i32).into())
+        } else {
+            Ok((v as f64).into())
+        }
+    }
+
+    fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        if v <= i32::MAX as u64 {
+            Ok((v as i32).into())
+        } else {
+            Ok((v as f64).into())
+        }
+    }
+
+    fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(v.into())
+    }
+
+    fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(v.into())
+    }
+
+    fn visit_bytes<E>(self, v: &[u8]) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(v.into())
+    }
+
+    fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Robj::from(()))
+    }
+
+    fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
+    where
+        A: SeqAccess<'de>,
+    {
+        // All sequences get converted to lists at the moment.
+        // We could check the first element and then assume the rest are the sme.
+        let mut values: Vec<Robj> = Vec::with_capacity(seq.size_hint().unwrap_or(8));
+        while let Some(value) = seq.next_element()? {
+            values.push(value);
+        }
+        Ok(values.into())
+    }
+
+    fn visit_map<M>(self, mut access: M) -> std::result::Result<Self::Value, M::Error>
+    where
+        M: MapAccess<'de>,
+    {
+        let mut keys: Vec<&str> = Vec::with_capacity(access.size_hint().unwrap_or(8));
+        let mut values: Vec<Robj> = Vec::with_capacity(access.size_hint().unwrap_or(8));
+
+        while let Some((key, value)) = access.next_entry()? {
+            keys.push(key);
+            values.push(value);
+        }
+
+        Ok(List::from_values(values).set_names(keys).unwrap())
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Robj::from(()))
+    }
+
+    fn visit_newtype_struct<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_enum<A>(self, _data: A) -> std::result::Result<Self::Value, A::Error>
+    where
+        A: EnumAccess<'de>,
+    {
+        // TODO: find an example of this.
+        unimplemented!();
+        // let (de, variant) = data.variant()?;
+        // de.deserialize_any(self)
+    }
+}
+
+impl<'de> Deserialize<'de> for Robj {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Robj, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(RobjVisitor)
+    }
+}
+
+struct IntegersVisitor;
+
+impl<'de> Visitor<'de> for IntegersVisitor {
+    type Value = Integers;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a value convertable to Integers")
+    }
+
+    fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        if v > i32::MIN as i64 && v <= i32::MAX as i64 {
+            Ok(Integers::from_values([v as i32]))
+        } else {
+            Err(serde::de::Error::custom("out of range for Integers"))
+        }
+    }
+
+    fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        if v <= i32::MAX as u64 {
+            Ok(Integers::from_values([v as i32]))
+        } else {
+            Err(serde::de::Error::custom("out of range for Integers"))
+        }
+    }
+
+    fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Integers::from_values([Rint::na()]))
+    }
+
+    fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
+    where
+        A: SeqAccess<'de>,
+    {
+        let mut values: Vec<Rint> = Vec::with_capacity(seq.size_hint().unwrap_or(8));
+        while let Some(value) = seq.next_element()? {
+            values.push(value);
+        }
+        Ok(Integers::from_values(values))
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Integers::from_values([Rint::na()]))
+    }
+}
+
+impl<'de> Deserialize<'de> for Integers {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Integers, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(IntegersVisitor)
+    }
+}
+
+struct DoublesVisitor;
+
+impl<'de> Visitor<'de> for DoublesVisitor {
+    type Value = Doubles;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a value convertable to Doubles")
+    }
+
+    fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Doubles::from_values([v as f64]))
+    }
+
+    fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Doubles::from_values([v as f64]))
+    }
+
+    fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Doubles::from_values([v]))
+    }
+
+    fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Doubles::from_values([Rfloat::na()]))
+    }
+
+    fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
+    where
+        A: SeqAccess<'de>,
+    {
+        let mut values: Vec<Rfloat> = Vec::with_capacity(seq.size_hint().unwrap_or(8));
+        while let Some(value) = seq.next_element()? {
+            values.push(value);
+        }
+        Ok(Doubles::from_values(values))
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Doubles::from_values([Rfloat::na()]))
+    }
+}
+
+impl<'de> Deserialize<'de> for Doubles {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Doubles, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(DoublesVisitor)
+    }
+}
+
+struct LogicalsVisitor;
+
+impl<'de> Visitor<'de> for LogicalsVisitor {
+    type Value = Logicals;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a value convertable to Logicals")
+    }
+
+    fn visit_bool<E>(self, v: bool) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Logicals::from_values([v]))
+    }
+
+    fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Logicals::from_values([Rbool::na()]))
+    }
+
+    fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
+    where
+        A: SeqAccess<'de>,
+    {
+        let mut values: Vec<Rbool> = Vec::with_capacity(seq.size_hint().unwrap_or(8));
+        while let Some(value) = seq.next_element()? {
+            values.push(value);
+        }
+        Ok(Logicals::from_values(values))
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Logicals::from_values([Rbool::na()]))
+    }
+}
+
+impl<'de> Deserialize<'de> for Logicals {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Logicals, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(LogicalsVisitor)
+    }
+}
+
+struct StringsVisitor;
+
+impl<'de> Visitor<'de> for StringsVisitor {
+    type Value = Strings;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a value convertable to Strings")
+    }
+
+    fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(v.into())
+    }
+
+    fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Strings::from_values([<&str>::na()]))
+    }
+
+    fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
+    where
+        A: SeqAccess<'de>,
+    {
+        let mut values: Vec<Rstr> = Vec::with_capacity(seq.size_hint().unwrap_or(8));
+        while let Some(value) = seq.next_element()? {
+            values.push(value);
+        }
+        Ok(Strings::from_values(values))
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Strings::from_values([<&str>::na()]))
+    }
+}
+
+impl<'de> Deserialize<'de> for Strings {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Strings, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(StringsVisitor)
+    }
+}
+
+struct RstrVisitor;
+
+impl<'de> Visitor<'de> for RstrVisitor {
+    type Value = Rstr;
+
+    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        formatter.write_str("a value convertable to Rstr")
+    }
+
+    fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(v.into())
+    }
+
+    fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Rstr::from_string(<&str>::na()))
+    }
+
+    fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+
+    fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Rstr::from_string(<&str>::na()))
+    }
+}
+
+impl<'de> Deserialize<'de> for Rstr {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Rstr, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(RstrVisitor)
+    }
+}
diff --git a/extendr-api/src/io/load.rs b/extendr-api/src/io/load.rs
new file mode 100644
index 000000000..e655febe3
--- /dev/null
+++ b/extendr-api/src/io/load.rs
@@ -0,0 +1,77 @@
+use crate::{catch_r_error, error::Error, error::Result, robj::Robj};
+use libR_sys::*;
+use std::io::Read;
+
+use super::PstreamFormat;
+
+pub struct ReadHook {
+    func: Option<unsafe extern "C" fn(arg1: SEXP, arg2: SEXP) -> SEXP>,
+    data: SEXP,
+}
+
+pub trait Load {
+    /// Save an object in the R data format.
+    /// `version` should probably be 3.
+    fn load<P: AsRef<std::path::Path>>(
+        path: &P,
+        format: PstreamFormat,
+        hook: Option<ReadHook>,
+    ) -> Result<Robj> {
+        let mut reader = std::fs::File::open(path)
+            .map_err(|_| Error::Other(format!("could not open file {:?}", path.as_ref())))?;
+        Self::from_reader(&mut reader, format, hook)
+    }
+
+    /// Save an object in the R data format to a `Write` trait.
+    /// `version` should probably be 3.
+    fn from_reader<R: Read>(
+        reader: &mut R,
+        format: PstreamFormat,
+        hook: Option<ReadHook>,
+    ) -> Result<Robj> {
+        unsafe extern "C" fn inchar<R: Read>(arg1: R_inpstream_t) -> ::std::os::raw::c_int {
+            let reader = &mut *((*arg1).data as *mut R);
+            let buf: &mut [u8] = &mut [0_u8];
+            reader.read_exact(buf).map(|_| buf[0].into()).unwrap_or(-1)
+        }
+
+        unsafe extern "C" fn inbytes<R: Read>(
+            arg1: R_inpstream_t,
+            arg2: *mut ::std::os::raw::c_void,
+            arg3: ::std::os::raw::c_int,
+        ) {
+            let reader = &mut *((*arg1).data as *mut R);
+            let buf = std::slice::from_raw_parts_mut(arg2 as *mut u8, arg3 as usize);
+            reader.read_exact(buf).unwrap();
+        }
+
+        let read_ptr: *mut R = reader as &mut R;
+        let data = unsafe { std::mem::transmute(read_ptr) };
+
+        let (hook_func, hook_data) = if let Some(hook) = hook {
+            (hook.func, hook.data)
+        } else {
+            (None, unsafe { R_NilValue })
+        };
+
+        // let sexp = self.get();
+        // pub type R_inpstream_t = *mut R_inpstream_st;
+        let mut state = R_inpstream_st {
+            data,
+            type_: format as R_pstream_format_t,
+            InChar: Some(inchar::<R>),
+            InBytes: Some(inbytes::<R>),
+            InPersistHookFunc: hook_func,
+            InPersistHookData: hook_data,
+            native_encoding: [0; 64],
+            nat2nat_obj: std::ptr::null_mut(),
+            nat2utf8_obj: std::ptr::null_mut(),
+        };
+
+        Ok(Robj::from_sexp(catch_r_error(move || unsafe {
+            R_Unserialize(&mut state as R_inpstream_t)
+        })?))
+    }
+}
+
+impl Load for Robj {}
diff --git a/extendr-api/src/io/mod.rs b/extendr-api/src/io/mod.rs
new file mode 100644
index 000000000..4aeb24cc2
--- /dev/null
+++ b/extendr-api/src/io/mod.rs
@@ -0,0 +1,13 @@
+pub enum PstreamFormat {
+    AnyFormat = 0,
+    AsciiFormat = 1,
+    BinaryFormat = 2,
+    XdrFormat = 3,
+    AsciihexFormat = 4,
+}
+
+mod load;
+mod save;
+
+pub use load::Load;
+pub use save::Save;
diff --git a/extendr-api/src/io/save.rs b/extendr-api/src/io/save.rs
new file mode 100644
index 000000000..a892685d8
--- /dev/null
+++ b/extendr-api/src/io/save.rs
@@ -0,0 +1,109 @@
+//! Wrapper for R output streams.
+
+use crate::{catch_r_error, error::Error, error::Result, robj::GetSexp};
+use libR_sys::*;
+use std::io::Write;
+
+use super::PstreamFormat;
+
+/// The hook will convert some objects into strings.
+pub struct WriteHook {
+    pub func: unsafe extern "C" fn(arg1: SEXP, arg2: SEXP) -> SEXP,
+    pub data: SEXP,
+}
+
+pub struct OutStream<W: Write> {
+    r_state: R_outpstream_st,
+    writer: W,
+}
+
+impl<W: Write> OutStream<W> {
+    pub fn from_writer(
+        writer: W,
+        format: PstreamFormat,
+        version: i32,
+        hook: Option<WriteHook>,
+    ) -> Box<OutStream<W>> {
+        unsafe extern "C" fn outchar<W: Write>(arg1: R_outpstream_t, arg2: ::std::os::raw::c_int) {
+            let writer = &mut *((*arg1).data as *mut W);
+            let b = [arg2 as u8];
+            writer.write_all(&b).unwrap();
+        }
+
+        unsafe extern "C" fn outbytes<W: Write>(
+            arg1: R_outpstream_t,
+            arg2: *mut ::std::os::raw::c_void,
+            arg3: ::std::os::raw::c_int,
+        ) {
+            let writer = &mut *((*arg1).data as *mut W);
+            let b = std::slice::from_raw_parts(arg2 as *mut u8, arg3 as usize);
+            writer.write_all(b).unwrap();
+        }
+
+        {
+            let (hook_fn, hook_data) = if let Some(WriteHook { func, data }) = hook {
+                (Some(func), data)
+            } else {
+                unsafe { (None, R_NilValue) }
+            };
+
+            let r_state = libR_sys::R_outpstream_st {
+                data: std::ptr::null_mut(),
+                type_: format as R_pstream_format_t,
+                version,
+                OutChar: Some(outchar::<W>),
+                OutBytes: Some(outbytes::<W>),
+                OutPersistHookFunc: hook_fn,
+                OutPersistHookData: hook_data,
+            };
+            let mut os = Box::new(OutStream { r_state, writer });
+            os.r_state.data = &mut os.writer as *mut W as R_pstream_data_t;
+            os
+        }
+    }
+}
+
+pub trait Save: GetSexp {
+    /// Save an object in the R data format.
+    /// `version` should probably be 3.
+    fn save<P: AsRef<std::path::Path>>(
+        &self,
+        path: &P,
+        format: PstreamFormat,
+        version: i32,
+        hook: Option<WriteHook>,
+    ) -> Result<()> {
+        let mut writer = std::fs::File::create(path.as_ref())
+            .map_err(|_| Error::Other(format!("could not create file {:?}", path.as_ref())))?;
+        self.to_writer(&mut writer, format, version, hook)
+    }
+
+    /// Save an object in the R data format to a `Write` trait.
+    /// `version` should probably be 3.
+    fn to_writer<W: Write>(
+        &self,
+        writer: &mut W,
+        format: PstreamFormat,
+        version: i32,
+        hook: Option<WriteHook>,
+    ) -> Result<()> {
+        let mut os = OutStream::from_writer(writer, format, version, hook);
+
+        let stream = &mut os.r_state as R_outpstream_t;
+        let sexp = unsafe { self.get() };
+        if !(2..=3).contains(&version) {
+            return Err(Error::Other(format!(
+                "version must be 2 or 3, got {:?}",
+                version
+            )));
+        }
+
+        catch_r_error(move || unsafe {
+            R_Serialize(sexp, stream);
+            R_NilValue
+        })?;
+        Ok(())
+    }
+}
+
+impl<R: GetSexp> Save for R {}
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 6de270ad5..df8c0c5d2 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -225,6 +225,7 @@
 
 pub mod error;
 pub mod functions;
+pub mod io;
 pub mod iter;
 pub mod lang_macros;
 pub mod metadata;
@@ -235,6 +236,9 @@ pub mod rmacros;
 #[cfg(feature = "serde")]
 pub mod serializer;
 
+#[cfg(feature = "serde")]
+pub mod deserializer;
+
 pub mod graphics;
 pub mod robj;
 pub mod scalar;
diff --git a/extendr-api/src/metadata.rs b/extendr-api/src/metadata.rs
index a04eb36b4..90981c197 100644
--- a/extendr-api/src/metadata.rs
+++ b/extendr-api/src/metadata.rs
@@ -317,6 +317,9 @@ fn write_impl_wrapper(
     // so we pass preprocessed value
     writeln!(w, "`$.{}` <- function (self, name) {{ func <- {}[[name]]; environment(func) <- environment(); func }}\n", imp.name, imp_name_fixed)?;
 
+    writeln!(w, "#' @export")?;
+    writeln!(w, "`[[.{}` <- `$.{}`\n", imp.name, imp.name)?;
+
     Ok(())
 }
 
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 4128db7ca..101072282 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -219,6 +219,12 @@ impl Robj {
             Robj { inner: sexp }
         })
     }
+
+    /// A ref of an robj can be constructed from a ref to a SEXP
+    /// as they have the same layout.
+    pub fn from_sexp_ref(sexp: &SEXP) -> &Self {
+        unsafe { std::mem::transmute(sexp) }
+    }
 }
 
 pub trait Types: GetSexp {
diff --git a/extendr-api/src/serializer.rs b/extendr-api/src/serializer.rs
index 46896d628..0da0a23a7 100644
--- a/extendr-api/src/serializer.rs
+++ b/extendr-api/src/serializer.rs
@@ -2,15 +2,14 @@
 
 use crate::error::{Error, Result};
 use crate::na::CanBeNA;
-use crate::robj::{GetSexp, Length, Rinternals, Types};
+use crate::robj::{Attributes, GetSexp, Length, Rinternals, Types};
 use crate::scalar::{Rbool, Rfloat, Rint};
-use crate::{
-    Attributes, Doubles, Environment, Expressions, Function, Integers, Language, Logicals,
-    Pairlist, Primitive, Promise, Raw, Rstr, Strings, Symbol, S4,
+use crate::wrapper::{
+    Doubles, Environment, Expressions, Function, Integers, Language, Logicals, Pairlist, Primitive,
+    Promise, Raw, Rstr, Symbol, S4,
 };
 use crate::{List, Rany, Robj};
 use serde::{ser, Serialize};
-// use crate::list;
 
 impl ser::Error for Error {
     fn custom<T: std::fmt::Display>(msg: T) -> Self {
@@ -18,45 +17,45 @@ impl ser::Error for Error {
     }
 }
 
-struct Serializer {
+struct RobjSerializer {
     robj: Option<Robj>,
 }
 
 struct SerializeSeq<'a> {
     values: Vec<Robj>,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
 }
 
 struct SerializeTuple<'a> {
     values: Vec<Robj>,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
 }
 
 struct SerializeTupleStruct<'a> {
     values: Vec<Robj>,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
 }
 
 struct SerializeTupleVariant<'a> {
     values: Vec<Robj>,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
     variant: String,
 }
 
 struct SerializeMap<'a> {
     values: Vec<(String, Robj)>,
     key: String,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
 }
 
 struct SerializeStruct<'a> {
     values: Vec<(String, Robj)>,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
 }
 
 struct SerializeStructVariant<'a> {
     values: Vec<(String, Robj)>,
-    parent: &'a mut Serializer,
+    parent: &'a mut RobjSerializer,
     variant: String,
 }
 
@@ -92,17 +91,17 @@ pub fn to_robj<T>(value: &T) -> Result<Robj>
 where
     T: Serialize,
 {
-    let mut serializer = Serializer { robj: None };
+    let mut serializer = RobjSerializer { robj: None };
 
     value.serialize(&mut serializer)?;
     Ok(serializer.robj.unwrap())
 }
 
-impl<'a> ser::Serializer for &'a mut Serializer {
-    // The output type produced by this `Serializer` during successful
+impl<'a> ser::Serializer for &'a mut RobjSerializer {
+    // The output type produced by this `RobjSerializer` during successful
     // serialization. Most serializers that produce text or binary output should
     // set `Ok = ()` and serialize into an `io::Write` or buffer contained
-    // within the `Serializer` instance, as happens here. Serializers that build
+    // within the `RobjSerializer` instance, as happens here. Serializers that build
     // in-memory data structures may be simplified by using `Ok` to propagate
     // the data structure around.
     type Ok = ();
@@ -113,7 +112,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
     // Associated types for keeping track of additional state while serializing
     // compound data structures like sequences and maps. In this case no
     // additional state is required beyond what is already stored in the
-    // Serializer struct.
+    // RobjSerializer struct.
     type SerializeSeq = self::SerializeSeq<'a>;
     type SerializeTuple = self::SerializeTuple<'a>;
     type SerializeTupleStruct = self::SerializeTupleStruct<'a>;
@@ -241,7 +240,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
         _variant_index: u32,
         variant: &'static str,
     ) -> Result<()> {
-        self.robj = Some(List::from_pairs([(variant, Robj::from(()))]).into());
+        self.robj = Some(Robj::from(variant));
         Ok(())
     }
 
@@ -611,33 +610,19 @@ impl ser::Serialize for Integers {
         S: ser::Serializer,
     {
         if self.len() == 1 {
-            serializer.serialize_i32(self.elt(0).inner())
+            self.elt(0).serialize(serializer)
         } else {
             use serde::ser::SerializeSeq;
             let mut s = serializer.serialize_seq(Some(self.len()))?;
-            for v in self.as_robj().as_integer_slice().unwrap() {
-                // TODO: use get_range when available.
-                s.serialize_element(v)?;
+            for v in self.iter() {
+                s.serialize_element(&v)?;
             }
             s.end()
         }
     }
 }
 
-impl ser::Serialize for List {
-    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
-    where
-        S: ser::Serializer,
-    {
-        if self.has_names() {
-            serializer.collect_map(self.iter())
-        } else {
-            serializer.collect_seq(self.values())
-        }
-    }
-}
-
-impl ser::Serialize for Doubles {
+impl ser::Serialize for Logicals {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
@@ -645,25 +630,30 @@ impl ser::Serialize for Doubles {
         if self.len() == 1 {
             self.elt(0).serialize(serializer)
         } else {
-            serializer.collect_seq(self.iter())
+            use serde::ser::SerializeSeq;
+            let mut s = serializer.serialize_seq(Some(self.len()))?;
+            for v in self.iter() {
+                s.serialize_element(&v)?;
+            }
+            s.end()
         }
     }
 }
 
-impl ser::Serialize for Logicals {
+impl ser::Serialize for List {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
     {
-        if self.len() == 1 {
-            self.elt(0).serialize(serializer)
+        if self.has_names() {
+            serializer.collect_map(self.iter())
         } else {
-            serializer.collect_seq(self.iter())
+            serializer.collect_seq(self.values())
         }
     }
 }
 
-impl ser::Serialize for Strings {
+impl ser::Serialize for Doubles {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
@@ -671,7 +661,12 @@ impl ser::Serialize for Strings {
         if self.len() == 1 {
             self.elt(0).serialize(serializer)
         } else {
-            serializer.collect_seq(self.iter())
+            use serde::ser::SerializeSeq;
+            let mut s = serializer.serialize_seq(Some(self.len()))?;
+            for v in self.iter() {
+                s.serialize_element(&v)?;
+            }
+            s.end()
         }
     }
 }
@@ -689,51 +684,51 @@ impl ser::Serialize for Rstr {
     }
 }
 
-impl ser::Serialize for Rint {
+impl ser::Serialize for Raw {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
     {
-        if self.is_na() {
-            serializer.serialize_unit()
-        } else {
-            serializer.serialize_i32(self.inner())
-        }
+        serializer.serialize_bytes(self.as_robj().as_raw_slice().unwrap())
     }
 }
 
-impl ser::Serialize for Rfloat {
+impl ser::Serialize for Rint {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
     {
-        if self.is_na() {
-            serializer.serialize_unit()
+        if let Some(v) = (*self).into() {
+            serializer.serialize_i32(v)
         } else {
-            serializer.serialize_f64(self.inner())
+            serializer.serialize_unit()
         }
     }
 }
 
-impl ser::Serialize for Rbool {
+impl ser::Serialize for Rfloat {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
     {
-        if self.is_na() {
-            serializer.serialize_unit()
+        if let Some(v) = (*self).into() {
+            serializer.serialize_f64(v)
         } else {
-            serializer.serialize_bool(self.is_true())
+            serializer.serialize_unit()
         }
     }
 }
 
-impl ser::Serialize for Raw {
+impl ser::Serialize for Rbool {
     fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
     where
         S: ser::Serializer,
     {
-        serializer.serialize_bytes(self.as_robj().as_raw_slice().unwrap())
+        if let Some(v) = (*self).into() {
+            serializer.serialize_bool(v)
+        } else {
+            serializer.serialize_unit()
+        }
     }
 }
 
diff --git a/extendr-api/src/wrapper/externalptr.rs b/extendr-api/src/wrapper/externalptr.rs
index f7050e94c..875911dd4 100644
--- a/extendr-api/src/wrapper/externalptr.rs
+++ b/extendr-api/src/wrapper/externalptr.rs
@@ -24,6 +24,40 @@ pub struct ExternalPtr<T: Debug + 'static> {
     marker: std::marker::PhantomData<T>,
 }
 
+impl<T: Debug + 'static> robj::GetSexp for ExternalPtr<T> {
+    unsafe fn get(&self) -> SEXP {
+        self.robj.get()
+    }
+
+    /// Get a reference to a Robj for this type.
+    fn as_robj(&self) -> &Robj {
+        &self.robj
+    }
+
+    /// Get a mutable reference to a Robj for this type.
+    fn as_robj_mut(&mut self) -> &mut Robj {
+        &mut self.robj
+    }
+}
+
+/// len() and is_empty()
+impl<T: Debug + 'static> Length for ExternalPtr<T> {}
+
+/// rtype() and rany()
+impl<T: Debug + 'static> Types for ExternalPtr<T> {}
+
+/// as_*()
+impl<T: Debug + 'static> Conversions for ExternalPtr<T> {}
+
+/// find_var() etc.
+impl<T: Debug + 'static> Rinternals for ExternalPtr<T> {}
+
+/// as_typed_slice_raw() etc.
+impl<T: Debug + 'static> Slices for ExternalPtr<T> {}
+
+/// dollar() etc.
+impl<T: Debug + 'static> Operators for ExternalPtr<T> {}
+
 impl<T: Debug + 'static> Deref for ExternalPtr<T> {
     type Target = T;
 
diff --git a/extendr-api/src/wrapper/strings.rs b/extendr-api/src/wrapper/strings.rs
index b521c34b9..d08e2136b 100644
--- a/extendr-api/src/wrapper/strings.rs
+++ b/extendr-api/src/wrapper/strings.rs
@@ -60,7 +60,7 @@ impl Strings {
     }
 
     /// This is a relatively expensive operation, so use a variable if using this in a loop.
-    pub fn as_slice(&self) -> &[Rstr] {
+    pub fn as_slice<'a>(&self) -> &'a [Rstr] {
         unsafe {
             let data = STRING_PTR_RO(self.robj.get()) as *const Rstr;
             let len = self.robj.len();


Test patch:
diff --git a/extendr-api/tests/deserializer_tests.rs b/extendr-api/tests/deserializer_tests.rs
new file mode 100644
index 000000000..ea49e5e1f
--- /dev/null
+++ b/extendr-api/tests/deserializer_tests.rs
@@ -0,0 +1,174 @@
+#[cfg(feature = "serde")]
+mod test {
+    use extendr_api::deserializer::from_robj;
+    use extendr_api::prelude::*;
+    use serde::Deserialize;
+
+    ////////////////////////////////////////////////////////////////////////////////
+    ///
+    /// Deserialize from a Robj.
+    ///
+    /// Like JSON, we can use a Robj as a storage format.
+    ///
+    /// For example if creating vectors from a RDS file or returning a structure
+    /// or just doing a conversion.
+    ///
+    #[test]
+    fn test_deserialize_robj() {
+        test! {
+            // In these tests, the wrapper is transparent and just tests the contents.
+            // So Int(i32) is actually testing i32.
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct Null;
+            assert_eq!(from_robj::<Null>(&r!(NULL)), Ok(Null));
+            assert_eq!(from_robj::<Null>(&r!(1)), Err(Error::ExpectedNull(r!(1))));
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct Int(i32);
+            assert_eq!(from_robj::<Int>(&r!(1)), Ok(Int(1)));
+            assert_eq!(from_robj::<Int>(&r!(1.0)), Ok(Int(1)));
+            assert_eq!(from_robj::<Int>(&r!(NULL)).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct RInt(Rint);
+            assert_eq!(from_robj::<RInt>(&r!(1)), Ok(RInt(1.into())));
+            assert_eq!(from_robj::<RInt>(&r!(1.0)), Ok(RInt(1.into())));
+            assert_eq!(from_robj::<RInt>(&r!(Rint::na())).is_err(), true);
+            assert_eq!(from_robj::<RInt>(&r!(NULL)).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct Float(f64);
+            assert_eq!(from_robj::<Float>(&r!(1)), Ok(Float(1.0)));
+            assert_eq!(from_robj::<Float>(&r!(1.0)), Ok(Float(1.0)));
+            assert_eq!(from_robj::<Float>(&r!(NULL)).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct RFloat(Rfloat);
+            assert_eq!(from_robj::<RFloat>(&r!(1)), Ok(RFloat(1.0.into())));
+            assert_eq!(from_robj::<RFloat>(&r!(1.0)), Ok(RFloat(1.0.into())));
+            assert_eq!(from_robj::<RFloat>(&r!(Rfloat::na())).is_err(), true);
+            assert_eq!(from_robj::<RFloat>(&r!(NULL)).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct Bool(bool);
+            assert_eq!(from_robj::<Bool>(&r!(TRUE)), Ok(Bool(true)));
+            assert_eq!(from_robj::<Bool>(&r!(FALSE)), Ok(Bool(false)));
+            assert_eq!(from_robj::<Bool>(&r!(NULL)).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct RBool(Rbool);
+            assert_eq!(from_robj::<RBool>(&r!(TRUE)), Ok(RBool(TRUE)));
+            assert_eq!(from_robj::<RBool>(&r!(FALSE)), Ok(RBool(FALSE)));
+            assert_eq!(from_robj::<RBool>(&r!(Rbool::na())).is_err(), true);
+            assert_eq!(from_robj::<RBool>(&r!(NULL)).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct VInt(Vec<i32>);
+            assert_eq!(from_robj::<VInt>(&r!(list!(1, 2))), Ok(VInt(vec![1, 2])));
+            assert_eq!(from_robj::<VInt>(&r!([1, 2])), Ok(VInt(vec![1, 2])));
+            assert_eq!(from_robj::<VInt>(&r!([1, 2, i32::na()])).is_err(), true);
+
+            // Any integer type will do.
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct VInt16(Vec<i16>);
+            assert_eq!(from_robj::<VInt16>(&r!(list!(1, 2))), Ok(VInt16(vec![1, 2])));
+            assert_eq!(from_robj::<VInt16>(&r!([1, 2])), Ok(VInt16(vec![1, 2])));
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct VFloat64(Vec<f64>);
+            assert_eq!(from_robj::<VFloat64>(&r!(list!(1, 2))), Ok(VFloat64(vec![1., 2.])));
+            assert_eq!(from_robj::<VFloat64>(&r!([1, 2])), Ok(VFloat64(vec![1., 2.])));
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct VBool(Vec<bool>);
+            assert_eq!(from_robj::<VBool>(&r!(list!(TRUE, FALSE))), Ok(VBool(vec![true, false])));
+            assert_eq!(from_robj::<VBool>(&r!([TRUE, FALSE])), Ok(VBool(vec![true, false])));
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct Str(String);
+            assert_eq!(from_robj::<Str>(&r!("xyz")), Ok(Str("xyz".into())));
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct StrSlice<'a>(&'a str);
+            assert_eq!(from_robj::<StrSlice>(&r!("xyz")), Ok(StrSlice("xyz")));
+
+            // Structs are mapped to named lists.
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct Struct { a: i32, b: f64 }
+            assert_eq!(from_robj::<Struct>(&r!(list!(a=1, b=2))), Ok(Struct{ a: 1, b: 2.0 }));
+
+            // Enums are mapped to named lists of lists.
+            #[derive(Deserialize, PartialEq, Debug)]
+            enum Enum {
+                Unit,
+                Newtype(u32),
+                Tuple(u32, u32),
+                Struct { a: u32 },
+            }
+
+            let j = r!("Unit");
+            let expected = Enum::Unit;
+            assert_eq!(expected, from_robj(&j).unwrap());
+
+            // If the name is wrong:
+            let j = r!("UnitX");
+            assert_eq!(from_robj::<Enum>(&j).is_err(), true);
+
+            let j = r!(list!(Newtype=1));
+            let expected = Enum::Newtype(1);
+            assert_eq!(expected, from_robj(&j).unwrap());
+
+            let j = r!(list!(Tuple=list!(1, 2)));
+            let expected = Enum::Tuple(1, 2);
+            assert_eq!(expected, from_robj(&j).unwrap());
+
+            let j = r!(list!(Struct=list!(a=1)));
+            let expected = Enum::Struct { a: 1 };
+            assert_eq!(expected, from_robj(&j).unwrap());
+
+            // Many things will generate a Robj.
+            // But note that the original Robj will not be copied verbatim.
+            // The Deserialize trait for Robj can also be used to generate
+            // JSON and other formats for Robj.
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct ROBJ(Robj);
+            assert_eq!(from_robj::<ROBJ>(&r!(TRUE)), Ok(ROBJ(r!(TRUE))));
+            assert_eq!(from_robj::<ROBJ>(&r!(1)), Ok(ROBJ(r!(1))));
+            assert_eq!(from_robj::<ROBJ>(&r!(1.0)), Ok(ROBJ(r!(1.0))));
+            assert_eq!(from_robj::<ROBJ>(&r!("xyz")), Ok(ROBJ(r!("xyz"))));
+
+            // Sequences are always converted to lists.
+            assert_eq!(from_robj::<ROBJ>(&r!([TRUE, FALSE])), Ok(ROBJ(r!(list!(TRUE, FALSE)))));
+            assert_eq!(from_robj::<ROBJ>(&r!([1, 2])), Ok(ROBJ(r!(list!(1, 2)))));
+
+            // If you use a wrapper type, conversions are more specific.
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct RIntegers(Integers);
+            assert_eq!(from_robj::<RIntegers>(&r!(1)), Ok(RIntegers(Integers::from_values([1]))));
+            assert_eq!(from_robj::<RIntegers>(&r!([1, 2])), Ok(RIntegers(Integers::from_values([1, 2]))));
+            assert_eq!(from_robj::<RIntegers>(&r!(1.0)).is_err(), true);
+            assert_eq!(from_robj::<RIntegers>(&r!("xyz")).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct RDoubles(Doubles);
+            assert_eq!(from_robj::<RDoubles>(&r!(1)), Ok(RDoubles(Doubles::from_values([1.0]))));
+            // assert_eq!(from_robj::<RDoubles>(&r!([1, 2])), Ok(RDoubles(Doubles::from_values([1.0, 2.0]))));
+            assert_eq!(from_robj::<RDoubles>(&r!([1.0, 2.0])), Ok(RDoubles(Doubles::from_values([1.0, 2.0]))));
+            assert_eq!(from_robj::<RDoubles>(&r!("xyz")).is_err(), true);
+
+            #[derive(Deserialize, PartialEq, Debug)]
+            struct RLogicals(Logicals);
+            assert_eq!(from_robj::<RLogicals>(&r!(TRUE)), Ok(RLogicals(Logicals::from_values([TRUE]))));
+            assert_eq!(from_robj::<RLogicals>(&r!([TRUE, FALSE, NA_LOGICAL])), Ok(RLogicals(Logicals::from_values([TRUE, FALSE, NA_LOGICAL]))));
+            assert_eq!(from_robj::<RLogicals>(&r!("xyz")).is_err(), true);
+
+            // This requires a PR that is not yet merged.
+            // #[derive(Deserialize, PartialEq, Debug)]
+            // struct RStrings(Strings);
+            // assert_eq!(from_robj::<RStrings>(&r!("xyz")), Ok(RStrings(Strings::from_values(["xyz"]))));
+            // assert_eq!(from_robj::<RStrings>(&r!(["a", "b"])), Ok(RStrings(Strings::from_values(["a", "b"]))));
+            // assert_eq!(from_robj::<RStrings>(&r!(0)).is_err(), true);
+        }
+    }
+}
diff --git a/extendr-api/tests/serializer_tests.rs b/extendr-api/tests/serializer_tests.rs
index 07b20ca99..c4c9c64fe 100644
--- a/extendr-api/tests/serializer_tests.rs
+++ b/extendr-api/tests/serializer_tests.rs
@@ -10,9 +10,9 @@ mod test {
     fn test_serialize_struct() {
         test! {
             #[derive(Serialize)]
-            struct Test {
+            struct Test<'a> {
                 int: i32,
-                seq: Vec<&'static str>,
+                seq: Vec<&'a str>,
             }
 
             let test = Test {
@@ -37,7 +37,7 @@ mod test {
             }
 
             let u = E::Unit;
-            let expected = list!(Unit=NULL);
+            let expected = r!("Unit");
             assert_eq!(to_robj(&u).unwrap(), r!(expected));
 
             let n = E::Newtype(1);
@@ -57,50 +57,103 @@ mod test {
     #[test]
     fn test_serialize_robj() {
         test! {
-            let s = r!(());
-            assert_eq!(to_robj(&s).unwrap(), r!(()));
-            let s = r!(sym!(xyz));
-            assert_eq!(to_robj(&s).unwrap(), r!("xyz"));
-            let s = r!(pairlist!(x=1, y=2));
-            assert_eq!(to_robj(&s).unwrap(), r!(list!(x=1, y=2)));
-            let s = R!("function (x) 1").unwrap();
-            assert_eq!(to_robj(&s).unwrap(), r!(()));
-            let s = r!(Environment::new_with_parent(global_env()));
-            assert_eq!(to_robj(&s).unwrap(), r!(()));
-            let s = r!(Promise::from_parts(r!(()), global_env()));
-            assert_eq!(to_robj(&s).unwrap(), r!(()));
-            let s = r!(Language::from_values([r!(())]));
-            assert_eq!(to_robj(&s).unwrap(), r!(()));
-            let s = r!(Primitive::from_string("+"));
-            assert_eq!(to_robj(&s).unwrap(), r!(".Primitive(\"+\")"));
-            let s = r!(Primitive::from_string("if"));
-            assert_eq!(to_robj(&s).unwrap(), r!(".Primitive(\"if\")"));
-            let s = r!(Rstr::from_string("xyz"));
-            assert_eq!(to_robj(&s).unwrap(), r!("xyz"));
-            let s = r!([TRUE, FALSE, NA_LOGICAL]);
-            assert_eq!(to_robj(&s).unwrap(), r!(list!(TRUE, FALSE, ())));
-            let s = r!([1, 2, 3]);
-            assert_eq!(to_robj(&s).unwrap(), r!(list![1, 2, 3]));
-            let s = r!([1., 2., 3.]);
-            assert_eq!(to_robj(&s).unwrap(), r!(list![1., 2., 3.]));
-            // Rany::Complexes(_complex) => serializer.serialize_unit(),
-            let s = r!(["1", "2", "3"]);
-            assert_eq!(to_robj(&s).unwrap(), r!(list!["1", "2", "3"]));
-            // Rany::Dot(_dot) => serializer.serialize_unit(),
-            // Rany::Any(_any) => serializer.serialize_unit(),
-            let s = r!(list![1, 2., "3"]);
-            assert_eq!(to_robj(&s).unwrap(), r!(list![1, 2., "3"]));
-            let s = r!(list![a=1, b=2., c="3"]);
-            assert_eq!(to_robj(&s).unwrap(), r!(list![a=1, b=2., c="3"]));
-            let s = r!(parse("1 + 2"));
-            assert_eq!(to_robj(&s).unwrap(), r!("expression(1 + 2)"));
-            // Rany::Bytecode(_bytecode) => serializer.serialize_unit(),
-            // Rany::ExternalPtr(_externalptr) => serializer.serialize_unit(),
-            // Rany::WeakRef(_weakref) => serializer.serialize_unit(),
-            let s = r!(Raw::from_bytes(&[1, 2, 3]));
-            assert_eq!(to_robj(&s).unwrap(), r!(Raw::from_bytes(&[1, 2, 3])));
-            // Rany::S4(value) => value.serialize(serializer),
-            // Rany::Unknown(_unknown) => serializer.serialize_unit(),
+            #[derive(Serialize)]
+            struct Null(Robj);
+            let s = Null(r!(NULL));
+            let expected = r!(NULL);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Sym(Symbol);
+            let s = Sym(sym!(xyz).try_into()?);
+            let expected = r!("xyz");
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Plist(Pairlist);
+            let s = Plist(pairlist!(a=1, b=2));
+            let expected = list!(a=1, b=2);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rstr1(Rstr);
+            let s = Rstr1(Rstr::from("xyz"));
+            let expected = r!("xyz");
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Int(Integers);
+            let s = Int(Integers::from_values([1]));
+            let expected = r!(1);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Int2(Integers);
+            let s = Int2(Integers::from_values([1, 2]));
+            let expected = r!(list![1, 2]);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Dbl2(Doubles);
+            let s = Dbl2(Doubles::from_values([1.0, 2.0]));
+            let expected = r!(list![1.0, 2.0]);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            // BUG! Will probably be fixed by "better-debug"
+            //
+            // #[derive(Serialize)]
+            // struct List1(List);
+            // let s = List1(list!(a=1, b=2));
+            // let expected = r!(list!(a=1, b=2));
+            // assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            // #[derive(Serialize)]
+            // struct List2(List);
+            // let s = List2(list!(1, 2));
+            // let expected = r!(list!(1, 2));
+            // assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Raw1(Raw);
+            let s = Raw1(Raw::from_bytes(&[1, 2, 3]));
+            let expected = r!(Raw::from_bytes(&[1, 2, 3]));
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rint1(Rint);
+            let s = Rint1(Rint::from(1));
+            let expected = r!(1);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rint2(Rint);
+            let s = Rint2(Rint::na());
+            let expected = r!(());
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rfloat1(Rfloat);
+            let s = Rfloat1(Rfloat::from(1.0));
+            let expected = r!(1.0);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rfloat2(Rfloat);
+            let s = Rfloat2(Rfloat::na());
+            let expected = r!(());
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rbool1(Rbool);
+            let s = Rbool1(Rbool::from(true));
+            let expected = r!(true);
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
+
+            #[derive(Serialize)]
+            struct Rbool2(Rbool);
+            let s = Rbool2(Rbool::na());
+            let expected = r!(());
+            assert_eq!(to_robj(&s).unwrap(), Robj::from(expected));
         }
     }
 }
diff --git a/extendr-api/tests/test_io.rs b/extendr-api/tests/test_io.rs
new file mode 100644
index 000000000..20683015f
--- /dev/null
+++ b/extendr-api/tests/test_io.rs
@@ -0,0 +1,46 @@
+use ::extendr_api::io::Load;
+
+#[test]
+fn test_save() {
+    use extendr_api::{io::PstreamFormat, io::Save, test, Result, Robj};
+    test! {
+        let mut w = Vec::new();
+        Robj::from(1).to_writer(&mut w, PstreamFormat::AsciiFormat, 3, None)?;
+        assert!(w[0] == b'A');
+
+        let mut w = Vec::new();
+        Robj::from(1).to_writer(&mut w, PstreamFormat::BinaryFormat, 3, None)?;
+        assert!(w[0] == b'B');
+
+        // let path : std::path::PathBuf = "/tmp/1".into();
+        // Robj::from(1).save(&path, PstreamFormat::AsciiFormat, 3, None)?;
+        // let s = std::fs::read(path).unwrap();
+        // assert!(s.starts_with(b"A\n"));
+    }
+}
+
+#[test]
+fn test_load() {
+    use extendr_api::{io::PstreamFormat, test, Result, Robj};
+    test! {
+        let text = r#"A
+3
+262402
+197888
+5
+UTF-8
+13
+1
+1
+"#;
+        // let mut w = Vec::new();
+        // Robj::from(1_i32).to_writer(&mut w, PstreamFormat::AsciiFormat, 3, None)?;
+        // assert!(w[0] == b'A');
+        // println!("{}", String::from_utf8(w.clone()).unwrap());
+
+        let mut c = std::io::Cursor::new(text);
+
+        let res = Robj::from_reader(&mut c, PstreamFormat::AsciiFormat, None);
+        assert_eq!(res, Ok(Robj::from(1_i32)));
+    }
+}
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index 06fd92cef..7e213630c 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -90,6 +90,9 @@ MyClass$me <- function() .Call(wrap__MyClass__me, self)
 #' @export
 `$.MyClass` <- function (self, name) { func <- MyClass[[name]]; environment(func) <- environment(); func }
 
+#' @export
+`[[.MyClass` <- `$.MyClass`
+
 `__MyClass` <- new.env(parent = emptyenv())
 
 `__MyClass`$new <- function() .Call(wrap____MyClass__new)
@@ -99,6 +102,9 @@ MyClass$me <- function() .Call(wrap__MyClass__me, self)
 #' @export
 `$.__MyClass` <- function (self, name) { func <- `__MyClass`[[name]]; environment(func) <- environment(); func }
 
+#' @export
+`[[.__MyClass` <- `$.__MyClass`
+
 #' Class for testing (unexported)
 MyClassUnexported <- new.env(parent = emptyenv())
 
@@ -109,6 +115,9 @@ MyClassUnexported$a <- function() .Call(wrap__MyClassUnexported__a, self)
 #' @export
 `$.MyClassUnexported` <- function (self, name) { func <- MyClassUnexported[[name]]; environment(func) <- environment(); func }
 
+#' @export
+`[[.MyClassUnexported` <- `$.MyClassUnexported`
+
 #' Class for testing (exported)
 #' @examples
 #' x <- MySubmoduleClass$new()
@@ -131,3 +140,6 @@ MySubmoduleClass$me <- function() .Call(wrap__MySubmoduleClass__me, self)
 #' @export
 `$.MySubmoduleClass` <- function (self, name) { func <- MySubmoduleClass[[name]]; environment(func) <- environment(); func }
 
+#' @export
+`[[.MySubmoduleClass` <- `$.MySubmoduleClass`
+
diff --git a/tests/extendrtests/tests/testthat/test-classes.R b/tests/extendrtests/tests/testthat/test-classes.R
index 709902e44..8e8135edb 100644
--- a/tests/extendrtests/tests/testthat/test-classes.R
+++ b/tests/extendrtests/tests/testthat/test-classes.R
@@ -1,10 +1,13 @@
 test_that("Exported class works", {
   x <- MyClass$new()
   expect_equal(x$a(), 0L)
+  expect_equal(x[["a"]](), 0L)
   x$set_a(10L)
   expect_equal(x$a(), 10L)
+  expect_equal(x[["a"]](), 10L)
   expect_equal(x$me(), x)
-  
+  expect_equal(x[["me"]](), x)
+
   expect_visible(x$a())
   expect_invisible(x$set_a(5L))
 })
@@ -13,4 +16,5 @@ test_that("Unexported class works", {
   # unexported code works in testthat tests
   x <- MyClassUnexported$new()
   expect_equal(x$a(), 22L)
+  expect_equal(x[["a"]](), 22L)
 })