Skip to content

Commit 8b21b43

Browse files
authored
DOCSP-30545: serialization guide (#39)
1 parent fe346ec commit 8b21b43

File tree

5 files changed

+433
-7
lines changed

5 files changed

+433
-7
lines changed

source/fundamentals.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Fundamentals
1414
/fundamentals/crud
1515
/fundamentals/schema-validation
1616
/fundamentals/aggregation
17+
/fundamentals/serialization
18+
/fundamentals/tracing-logging
1719
/fundamentals/run-command
1820

1921
..
@@ -22,7 +24,6 @@ Fundamentals
2224
/fundamentals/context
2325
/fundamentals/auth
2426
/fundamentals/enterprise-auth
25-
/fundamentals/bson
2627
/fundamentals/indexes
2728
/fundamentals/transactions
2829
/fundamentals/logging

source/fundamentals/database-collection.txt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,23 @@ perform the following actions:
150150
To learn more about write concerns, see :manual:`Write Concern </reference/write-concern/>` in
151151
the Server manual.
152152

153+
.. _rust-coll-parameterization:
154+
153155
Collection Parameterization
154156
~~~~~~~~~~~~~~~~~~~~~~~~~~~
155-
157+
156158
You must parameterize your ``Collection`` instance by specifying what
157-
data type, such as ``Document``, you want to serialize the collection's
159+
data type you want to serialize the collection's
158160
data into. When you call a method on a ``Collection`` instance that is
159161
parameterized with a specific type, the method accepts or returns
160162
instances of this type.
161163

164+
.. note::
165+
166+
If you do not parameterize your ``Collection`` instance, the compiler
167+
infers the generic type when you perform a CRUD operation with a
168+
specified data type in the same scope.
169+
162170
The following example shows equivalent ways of parameterizing a
163171
collection with the ``Document`` type:
164172

@@ -175,7 +183,8 @@ collection with the ``Document`` type:
175183
You can avoid repetitive serialization and validation by defining a
176184
type that models your specific data.
177185

178-
.. TODO link to serialization guide
186+
To learn more about serialization in the {+driver-short+}, see the
187+
guide on :ref:`rust-serialization`.
179188

180189
.. _rust-create-collection:
181190

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
.. _rust-serialization:
2+
3+
===============================
4+
Data Modeling and Serialization
5+
===============================
6+
7+
.. contents:: On this page
8+
:local:
9+
:backlinks: none
10+
:depth: 2
11+
:class: singlecol
12+
13+
Overview
14+
--------
15+
16+
In this guide, you can learn about how the {+driver-short+} handles conversions
17+
between BSON and Rust types. The process of converting a Rust type to
18+
BSON is called **serialization**, while the reverse process is called
19+
**deserialization**.
20+
21+
The Rust language uses a static type system, but BSON has a dynamic
22+
schema. To handle conversions between Rust types and BSON, the driver and the
23+
``bson`` library integrate functionality from the Serde framework. To
24+
learn how to install the ``serde`` crate, see `serde
25+
<https://crates.io/crates/serde>`__ at the ``crates.io`` crate registry.
26+
27+
By implementing functionality from the ``serde`` crate into your
28+
application, you can use custom Rust types such as structs and enums
29+
to model your data.
30+
31+
This guide includes the following sections:
32+
33+
- :ref:`Generic Type Parameter <rust-generic-param>`: describes
34+
collection parameterization and data modeling
35+
36+
- :ref:`Custom Data Model <rust-custom-data-model>`: describes how to
37+
define custom Rust types to model data in your collections
38+
39+
- :ref:`Custom Serialization <rust-custom-serialization>`: describes how
40+
to modify default serialization and deserialization behavior by using
41+
attributes and provides examples
42+
43+
- :ref:`Additional Information <rust-serialization-addtl-info>`:
44+
provides links to additional resources and API documentation for types
45+
and methods mentioned in this guide
46+
47+
.. _rust-generic-param:
48+
49+
Generic Type Parameter
50+
----------------------
51+
52+
When you create a ``Collection`` instance, you must specify a generic
53+
type parameter to represent the type of data that models the documents
54+
in your collection. To learn more about specifying a generic type parameter,
55+
see the :ref:`Collection Parameterization section
56+
<rust-coll-parameterization>` of the guide on Databases and Collections.
57+
58+
We recommend that you define and use a custom type to model your
59+
collection's data instead of using the ``Document`` type.
60+
61+
.. _rust-custom-data-model:
62+
63+
Custom Data Model
64+
-----------------
65+
66+
You can use any Rust data type that implements the ``Serialize`` and
67+
``Deserialize`` traits from the ``serde`` crate as the generic type
68+
parameter for a ``Collection`` instance. To implement the ``Serialize``
69+
and ``Deserialize`` traits, you must use the following ``derive``
70+
statement before defining a Rust type:
71+
72+
.. code-block:: rust
73+
74+
#[derive(Serialize, Deserialize)]
75+
76+
Custom Struct Example
77+
~~~~~~~~~~~~~~~~~~~~~
78+
79+
The following code defines a sample ``Vegetable`` struct that implements
80+
the ``serde`` serialization traits:
81+
82+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
83+
:language: rust
84+
:dedent:
85+
:start-after: begin-veg-struct
86+
:end-before: end-veg-struct
87+
88+
The following code accesses the ``vegetables`` collection with
89+
``Vegetable`` as its generic type parameter:
90+
91+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
92+
:language: rust
93+
:dedent:
94+
:start-after: begin-access-coll
95+
:end-before: end-access-coll
96+
97+
Because the ``Collection`` instance is parameterized with the
98+
``Vegetable`` struct, you can perform CRUD operations with this type.
99+
The following code inserts a ``Vegetable`` instance into the collection:
100+
101+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
102+
:language: rust
103+
:dedent:
104+
:start-after: begin-insert-veg
105+
:end-before: end-insert-veg
106+
107+
Multiple Parameterizations
108+
~~~~~~~~~~~~~~~~~~~~~~~~~~
109+
110+
If your collection contains multiple schemas, you can define a custom
111+
type to model each data type and create clones of the original
112+
``Collection`` instance for each type. You can create clones of a
113+
``Collection`` instance by using the ``clone_with_type()`` method.
114+
115+
Suppose you originally parameterized a collection with a struct
116+
called ``Square``, but you later realize that you want to insert a different
117+
type of data, modeled by the ``Circle`` struct, into the collection.
118+
The following code parameterizes a collection with the ``Square`` type,
119+
then creates a clone of the collection that is parameterized with the
120+
``Circle`` type:
121+
122+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
123+
:language: rust
124+
:dedent:
125+
:start-after: begin-multiple-types
126+
:end-before: end-multiple-types
127+
128+
.. _rust-custom-serialization:
129+
130+
Custom Serialization
131+
--------------------
132+
133+
You can modify the default serialization and deserialization behavior of
134+
the {+driver-short+} by using **attributes** from the ``serde`` crate.
135+
Attributes are optional pieces of metadata attached to fields of
136+
structs or variants of enums.
137+
138+
The ``serde`` crate provides the ``serialize_with`` and
139+
``deserialize_with`` attributes, which take helper functions as values.
140+
These helper functions customize serialization and deserialization on
141+
specific fields and variants. To specify an attribute on a field,
142+
include the attribute before the field definition:
143+
144+
.. code-block:: rust
145+
146+
#[derive(Serialize, Deserialize)]
147+
struct MyStruct {
148+
#[serde(serialize_with = "<helper function>")]
149+
field1: String,
150+
// ... other fields
151+
}
152+
153+
In the following sections, you can find examples that use helper
154+
functions from the ``bson`` library to achieve common serialization tasks. To
155+
see a full list of these helper functions, see the `serde_helpers API
156+
documentation <https://docs.rs/bson/latest/bson/serde_helpers/index.html#functions>`__.
157+
158+
Serialize a String as an ObjectId
159+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160+
161+
You might want to represent the ``_id`` field in a document as a
162+
hexadecimal string in your struct. To convert the hexadecimal string to
163+
the ``ObjectId`` BSON type, use the
164+
``serialize_hex_string_as_object_id`` helper function as the value of
165+
the ``serialize_with`` attribute. The following example attaches the
166+
``serialize_with`` attribute to the ``_id`` field so that the driver
167+
serializes the hexadecimal string as an ``ObjectId`` type:
168+
169+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
170+
:language: rust
171+
:dedent:
172+
:start-after: begin-hex-to-objectid
173+
:end-before: end-hex-to-objectid
174+
175+
To see how the driver serializes a sample ``Order`` struct to BSON,
176+
select from the following :guilabel:`Struct` and :guilabel:`BSON` tabs:
177+
178+
.. tabs::
179+
180+
.. tab:: Struct
181+
:tabid: order struct
182+
183+
.. code-block:: rust
184+
:copyable: false
185+
:emphasize-lines: 2
186+
187+
let order = Order {
188+
_id: "6348acd2e1a47ca32e79f46f".to_string(),
189+
item: "lima beans".to_string(),
190+
};
191+
192+
.. tab:: BSON
193+
:tabid: serialized bson
194+
195+
.. code-block:: json
196+
:copyable: false
197+
:emphasize-lines: 2
198+
199+
{
200+
"_id": { "$oid": "6348acd2e1a47ca32e79f46f" },
201+
"item": "lima beans"
202+
}
203+
204+
Serialize a DateTime as a String
205+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
206+
207+
You might want to represent a ``DateTime`` field value in a document as
208+
an ISO-formatted string in BSON. To specify this conversion, use the
209+
``serialize_bson_datetime_as_rfc3339_string`` helper function as the value of
210+
the ``serialize_with`` attribute attached to the field with a
211+
``DateTime`` value. The following example attaches the
212+
``serialize_with`` attribute to the ``delivery_date`` field so that the
213+
driver serializes the ``DateTime`` value to a string:
214+
215+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
216+
:language: rust
217+
:dedent:
218+
:start-after: begin-dt-to-string
219+
:end-before: end-dt-to-string
220+
221+
To see how the driver serializes a sample ``Order`` struct to BSON,
222+
select from the following :guilabel:`Struct` and :guilabel:`BSON` tabs:
223+
224+
.. tabs::
225+
226+
.. tab:: Struct
227+
:tabid: order struct
228+
229+
.. code-block:: rust
230+
:copyable: false
231+
:emphasize-lines: 3
232+
233+
let order = Order {
234+
item: "lima beans".to_string(),
235+
delivery_date: DateTime::now(),
236+
};
237+
238+
.. tab:: BSON
239+
:tabid: serialized bson
240+
241+
.. code-block:: json
242+
:copyable: false
243+
:emphasize-lines: 4
244+
245+
{
246+
"_id": { ... },
247+
"item": "lima beans",
248+
"delivery_date": "2023-09-26T17:30:18.181Z"
249+
}
250+
251+
Serialize a u32 as an f64
252+
~~~~~~~~~~~~~~~~~~~~~~~~~
253+
254+
You might want to represent a ``u32`` field value in a document as
255+
an ``f64``, or ``Double``, type in BSON. To specify this conversion, use the
256+
``serialize_u32_as_f64`` helper function as the value of
257+
the ``serialize_with`` attribute attached to the field with a ``u32``
258+
value. The following example attaches the
259+
``serialize_with`` attribute to the ``quantity`` field so that the
260+
driver serializes the ``u32`` value to a ``Double`` type:
261+
262+
.. literalinclude:: /includes/fundamentals/code-snippets/serialization.rs
263+
:language: rust
264+
:dedent:
265+
:start-after: begin-u32-f64
266+
:end-before: end-u32-f64
267+
268+
.. note::
269+
270+
The BSON ``Double`` representation of a ``u32`` value appears
271+
the same as the original value.
272+
273+
Other Attributes and Modules
274+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
275+
276+
In addition to helper functions, the ``bson`` library provides modules
277+
that handle both serialization and deserialization. To select a module
278+
to use on a specific field or variant, set the value of the ``with``
279+
attribute to the name of the module:
280+
281+
.. code-block:: rust
282+
283+
#[derive(Serialize, Deserialize)]
284+
struct MyStruct {
285+
#[serde(with = "<module>")]
286+
field1: u32,
287+
// ... other fields
288+
}
289+
290+
For a full list of these modules, see the `serde_helpers API
291+
documentation <https://docs.rs/bson/latest/bson/serde_helpers/index.html#modules>`__.
292+
293+
The ``serde`` crate provides many other attributes to customize
294+
serialization. The following list describes some common attributes and
295+
their functionality:
296+
297+
- ``rename``: serialize and deserialize a field with a specified name instead of the
298+
Rust struct or variant name
299+
- ``skip``: do not serialize or deserialize the specified field
300+
- ``default``: if no value is present during deserialization, use the
301+
default value from ``Default::default()``
302+
303+
For a full list of ``serde`` attributes, see the `serde Attributes
304+
API documentation <https://serde.rs/attributes.html>`__.
305+
306+
.. _rust-serialization-addtl-info:
307+
308+
Additional Information
309+
----------------------
310+
311+
To learn more about BSON types, see :manual:`BSON Types
312+
</reference/bson-types/>` in the Server manual.
313+
314+
For more examples that demonstrate ``serde`` functionality, see the
315+
:website:`Structuring Data with Serde in Rust
316+
</developer/languages/rust/serde-improvements/>` Developer Center
317+
article.
318+
319+
To learn more about the Serde framework, see the `Serde documentation
320+
<https://serde.rs/>`__.
321+
322+
API Documentation
323+
~~~~~~~~~~~~~~~~~
324+
325+
To learn more about the methods and types discussed in this
326+
guide, see the following API documentation:
327+
328+
- `collection() <{+api+}/struct.Database.html#method.collection>`__
329+
- `clone_with_type() <{+api+}/struct.Collection.html#method.clone_with_type>`__
330+
- `serialize_with <https://serde.rs/field-attrs.html#serialize_with>`__
331+
Serde attribute
332+
- `deserialize_with <https://serde.rs/field-attrs.html#deserialize_with>`__
333+
Serde attribute
334+
- `with <https://serde.rs/field-attrs.html#with>`__ Serde attribute

0 commit comments

Comments
 (0)