Join us Sept 17 at .local NYC! Use code WEB50 to save 50% on tickets. Learn more >
MongoDB Event
Docs Menu
Docs Home
/ / /
Rust Driver
/

Data Modeling and Serialization

In this guide, you can learn about how the Rust driver handles conversions between BSON and Rust types. The process of converting a Rust type to BSON is called serialization, while the reverse process is called deserialization.

The Rust language uses a static type system, but BSON has a dynamic schema. To handle conversions between Rust types and BSON, the driver and the bson crate integrate functionality from the Serde framework.

Tip

The bson crate is exported with the mongodb crate. To learn how to install the serde crate, see serde at the crates.io crate registry.

By implementing functionality from the serde crate into your application, you can use custom Rust types such as structs and enums to model your data.

This guide includes the following sections:

  • Generic Type Parameter describes collection parameterization and data modeling

  • Custom Data Model describes how to define custom Rust types to model data in your collections

  • Custom Serialization describes how to modify default serialization and deserialization behavior by using attributes and provides examples

  • Additional Information provides links to resources and API documentation for types and methods mentioned in this guide

When you create a Collection instance, you must specify a generic type parameter to represent the type of data that models the documents in your collection. To learn more about specifying a generic type parameter, see the Collection Parameterization section of the guide on Databases and Collections.

We recommend that you define and use a custom type to model your collection's data instead of using the Document type.

You can use any Rust data type that implements the Serialize and Deserialize traits from the serde crate as the generic type parameter for a Collection instance. To implement the Serialize and Deserialize traits, you must include the following derive attribute before defining a Rust type:

#[derive(Serialize, Deserialize)]

The following code defines a sample Article struct that implements the serde serialization traits:

#[derive(Serialize, Deserialize)]
struct Article {
title: String,
date: DateTime,
content: String,
content_embeddings: Vector,
}

Tip

Vector Type

Starting in bson library v2.14, you can use the bson::binary::Vector type to represent vectors of numeric values.

Since this type is serialized as a BSON binary vector, usage of Vector can improve storage efficiency. To learn more, see the BSON specification.

The following code accesses the articles collection with the Article struct as its generic type parameter:

let my_coll: Collection<Article> = client
.database("db")
.collection("articles");

Because the Collection instance is parameterized with the Article struct, you can perform CRUD operations with this type. The following code inserts an Article instance into the collection:

let article = Article {
title: "Maintaining Your Garden in Winter".to_string(),
date: DateTime::now(),
content: "As fall winds down, you might be wondering what you should be doing in your garden in the coming months ...".to_string(),
content_embeddings: Vector::Float32(vec! [0.01020927,-0.011224265,0.015686288,-0.018586276,-0.023160344])
};
my_coll.insert_one(article).await?;

If your collection contains multiple schemas, you can define a custom type to model each data type and create clones of the original Collection instance for each type. You can create clones of a Collection instance by using the clone_with_type() method.

Suppose you originally parameterized a collection with a struct called Square, but you later realize that you want to insert a different type of data, modeled by the Circle struct, into the collection. The following code parameterizes a collection with the Square type, then creates a clone of the collection that is parameterized with the Circle type:

let shapes_coll: Collection<Square> = client
.database("db")
.collection("shapes");
// ... perform some operations with Square
let shapes_coll: Collection<Circle> = shapes_coll.clone_with_type();
// ... perform some operations with Circle

You can modify the default serialization and deserialization behavior of the Rust driver by using attributes from the serde crate. Attributes are optional pieces of metadata attached to fields of structs or variants of enums.

The serde crate provides the serialize_with and deserialize_with attributes, which take helper functions as values. These helper functions customize serialization and deserialization on specific fields and variants. To specify an attribute on a field, include the attribute before the field definition:

#[derive(Serialize, Deserialize)]
struct MyStruct {
#[serde(serialize_with = "<helper function>")]
field1: String,
// ... other fields
}

In the following sections, you can find examples that use helper types from the bson library to achieve common serialization tasks.

The examples show the following approaches:

  • BSON 3.0: Uses the serde_as annotation provided by the serde_with crate, which offers additional flexibility and composition capabilities

  • BSON 2.x: Uses the traditional serialize_with helper functions

BSON 2.15 is the default version. Choose the approach that matches your BSON crate version.

To use BSON 3.0 in your application, enable it as a feature in your project's Cargo.toml, similar to the following:

[dependencies.mongodb]
version = "3.3.0"
features = ["bson-3"]

When you enable BSON 3.0, you can use serde_as. To use serde_as, you must import it in your application, as shown in the following code:

use serde_with::serde_as;

To see a full list of Serde helper functions, see the serde_helpers API documentation.

You might want to represent the _id field in a document as a hexadecimal string in your struct. To convert the hexadecimal string to the ObjectId BSON type, select the BSON 3.0 (serde_as) or BSON 2.x (serialize_with) tab for corresponding code examples depending on your BSON crate version:

Use the bson::serde_helpers::object_id::FromHexString type with the serde_as annotation:

#[serde_as]
#[derive(Serialize, Deserialize)]
struct Order {
#[serde_as(as = "object_id::FromHexString")]
_id: String,
item: String,
}

Use the serialize_hex_string_as_object_id helper function:

#[derive(Serialize, Deserialize)]
struct Order {
#[serde(serialize_with = "serialize_hex_string_as_object_id")]
_id: String,
item: String,
}

To see how the driver serializes a sample Order struct to BSON, select from the following Struct and BSON tabs:

let order = Order {
_id: "6348acd2e1a47ca32e79f46f".to_string(),
item: "lima beans".to_string(),
};
{
"_id": { "$oid": "6348acd2e1a47ca32e79f46f" },
"item": "lima beans"
}

You might want to represent a DateTime field value in a document as an ISO-formatted string in BSON. Select the BSON 3.0(serde_as) or BSON 2.x (serialize_with) tab for corresponding code examples for this conversion.

Use the bson::serde_helpers::datetime::AsRfc3339String type with the serde_as annotation:

#[serde_as]
#[derive(Serialize, Deserialize)]
struct Order {
item: String,
#[serde_as(as = "datetime::AsRfc3339String")]
delivery_date: DateTime,
}

Use the serialize_bson_datetime_as_rfc3339_string helper function:

#[derive(Serialize, Deserialize)]
struct Order {
item: String,
#[serde(serialize_with = "serialize_bson_datetime_as_rfc3339_string")]
delivery_date: DateTime,
}

Select the BSON 3.0(serde_as) or BSON 2.x (serialize_with) tab to see how the driver serializes a sample Order struct to BSON.

let order = Order {
item: "lima beans".to_string(),
delivery_date: DateTime::now(),
};
{
"_id": { ... },
"item": "lima beans",
"delivery_date": "2023-09-26T17:30:18.181Z"
}

You might want to represent a u32 field value in a document as an f64, or Double, type in BSON. Select the BSON 3.0(serde_as) or BSON 2.x (serialize_with) tab for corresponding code samples to specify this conversion.

Use the bson::serde_helpers::u32::AsF64 type with the serde_as annotation:

#[serde_as]
#[derive(Serialize, Deserialize)]
struct Order {
item: String,
#[serde_as(as = "u32::AsF64")]
quantity: u32,
}

Use the serialize_u32_as_f64 helper function:

#[derive(Serialize, Deserialize)]
struct Order {
item: String,
#[serde(serialize_with = "serialize_u32_as_f64")]
quantity: u32,
}

Note

The BSON Double representation of a u32 value appears the same as the original value.

In addition to helper functions, the bson library provides modules that handle both serialization and deserialization. To select a module to use on a specific field or variant, set the value of the with attribute to the name of the module:

#[derive(Serialize, Deserialize)]
struct MyStruct {
#[serde(with = "<module>")]
field1: u32,
// ... other fields
}

For a full list of these modules, see the serde_helpers API documentation.

The serde crate provides many other attributes to customize serialization. The following list describes some common attributes and their functionality:

  • rename: serialize and deserialize a field with a specified name instead of the Rust struct or variant name

  • skip: do not serialize or deserialize the specified field

  • default: if no value is present during deserialization, use the default value from Default::default()

For a full list of serde attributes, see the serde Attributes API Documentation.

To learn more about BSON types, see BSON Types in the Server manual.

For more examples that demonstrate serde functionality, see the Structuring Data with Serde in Rust Developer Center article.

To learn more about the Serde framework, see the Serde documentation.

To learn more about the methods and types mentioned in this guide, see the following API documentation:

Back

Tutorial: CRUD Web App

On this page