DEV Community

Cover image for Structuring Data with Serde in Rust for MongoDB
rachelle palmer
rachelle palmer

Posted on • Updated on

Structuring Data with Serde in Rust for MongoDB

TLDR: Working with BSON in Rust is tricky - BSON has a dynamic schema, while Rust uses a static type system. To perform conversion between BSON and Rust structs and enums, use Serde. Serde is a powerful tool that provides lots of functionality for conversion between different data formats. In the upcoming 1.2.0 releases of the MongoDB Rust driver and BSON library, we’ve made it even easier to work directly with Rust data types. This article is a condensed version of a much more detailed one on the MongoDB Developer Hub.

For more details on working with MongoDB in Rust, you can check out the documentation for the Rust driver and BSON library. Check out our Getting Started with Rust tutorial here.

-

Using the more recent version of the Rust toolchain (v1.44+), we introduced a generic type parameter to the Collection type. The generic parameter represents the type of data you want to insert into and find from your MongoDB collection. Any Rust data type that derives/implements the Serde Serialize and Deserialize traits can be used as a type parameter for a Collection.

For example, I’m working with the following struct that defines the schema of the data in my students collection:

#[derive(Serialize, Deserialize)
struct Student {
   name: String,
   grade: u32,
   test_scores: Vec<u32>,
}
Enter fullscreen mode Exit fullscreen mode

Create a generic Collection by using the Database::collection_with_type method and specifying Student as the data type:

let students: Collection<Student> = db.collection_with_type("students");
Enter fullscreen mode Exit fullscreen mode

Prior to the introduction of the generic Collection, the various CRUD Collection methods accepted and returned the Document type. This meant I would need to serialize my Student structs to Documents before inserting them into the students collection. Now, I can insert a Student directly into my collection:

let student = Student {
    name: "Emily".to_string(),
    grade: 10,
    test_scores: vec![98, 87, 100],
};
let result = students.insert_one(student, None).await;
Enter fullscreen mode Exit fullscreen mode

To find a Student directly from the Collection:

// student is of type Student
let student = students.find_one(doc! { "name": "Emily" }, None).await?;
Enter fullscreen mode Exit fullscreen mode

The default generic type for Collection is Document. This means that any Collection created without a generic type will continue to find and return the Document type, and any existing code that uses Collection will be unaffected by these changes.

The BSON library now includes a set of functions that implement common strategies for custom serialization and deserialization when working with BSON. You can use these functions by importing them from the serde_helpers module in the bson-rust crate and using the serialize_with and deserialize_with attributes.

We introduced modules that take care of both serialization and deserialization. For instance, I might want to represent binary data using the Uuid type in the Rust uuid crate:

#[derive(Serialize, Deserialize)]
struct Item {
   uuid: Uuid,
   // rest of fields
}
Enter fullscreen mode Exit fullscreen mode

Since BSON doesn’t have a specific UUID type, I’ll need to convert this data into binary if I want to serialize into BSON. I’ll also want to convert back to Uuid when deserializing from BSON. The uuid_as_binary module in the serde_helpers module can take care of both of these conversions. I’ll add the following attribute to use this module:

#[derive(Serialize, Deserialize)]
struct Item {
   #[serde(with = “uuid_as_binary”)]
   uuid: Uuid,
   // rest of fields
}
Enter fullscreen mode Exit fullscreen mode

This makes it easier to work directly with the Uuid type without needing to worry about how to convert it to and from BSON. The serde_helpers module introduces functions for several other common strategies too. If you're interested in more complex mapping capabilities, it's worth reading the Serde documentation on attributes.

Top comments (0)