Serialization
FieldX uses the serde crate to support serialization. Understanding this chapter may sometimes require some knowledge of the crate.
Serialization in FieldX is enabled with the serde
feature flag and the serde
argument. However, these are not the only prerequisites, and to understand why, some explanations are needed.
The Complications and the Solutions
At this point, it's time to rewind to the Basics chapter and recall that under the hood, FieldX transforms the struct by wrapping its fields into container types if necessary. Direct serialization of some of these containers is not possible or may result in undesired outcomes. Generally speaking, using FieldX could have made serialization impossible unless the serde
crate lends us a helpful hand!
The solution is to use the from
and into
attributes of serde
, implement a copy of the user struct with containers stripped away, and use it for the actual serialization. FieldX calls this a shadow struct.
To support this functionality, the user struct must implement the Clone
trait, which is a prerequisite for applying the serde
argument. Since cloning a struct can be a non-trivial task, it is left to the user. In most cases, deriving it should suffice.
How To in a Nutshell
As is common with FieldX, the first step in serialization is as simple as adding the serde
argument to the struct declaration:
use fieldx::fxstruct;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone)]
#[fxstruct(get, builder, serde)]
pub struct Book {
title: String,
author: String,
#[fieldx(get(copy))]
year: u32,
#[fieldx(optional, get(as_ref))]
signed_by: String,
#[fieldx(set, inner_mut, default("unknown".to_string()))]
location: String,
}
And we're ready to use it:
let book = Book::builder()
.title("The Hitchhiker's Guide to the Galaxy".to_string())
.author("Douglas Adams".to_string())
.year(1979)
.signed_by("Douglas Adams".to_string())
.location("Shelf 42".to_string())
.build()
.expect("Failed to create book");
serde_json::to_string_pretty(&book).expect("Failed to serialize book")
{
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"year": 1979,
"signed_by": "Douglas Adams",
"location": "Shelf 42"
}
And the other way around too:
let serialized = r#"{
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"year": 1979,
"signed_by": "Douglas Adams",
"location": "Shelf 42"
}"#;
let deserialized: super::Book = serde_json::from_str(&serialized).expect("Failed to deserialize book");
assert_eq!(*deserialized.title(), "The Hitchhiker's Guide to the Galaxy");
assert_eq!(*deserialized.author(), "Douglas Adams");
assert_eq!(deserialized.year(), 1979);
assert_eq!(deserialized.signed_by(), Some(&String::from("Douglas Adams")));
assert_eq!(*deserialized.location(), "Shelf 42");
One Way Only
A struct does not always need to be both serialized and deserialized. Which serde
trait will be implemented for the struct is determined by the two sub-arguments of the serde
argument, whose names are (surprise!) serialize
and deserialize
. For example, if we only want to serialize the struct, we simply write:
#[fxstruct(serde(serialize))]
Or
#[fxstruct(serde(deserialize(off)))]
The general rule FieldX follows here is that if the serde
argument is applied and not disabled with the off
flag, then the user intends for one of the two traits to be implemented, whether the desired one is enabled or the undesired one is disabled.
Default Value
It is possible to specify a default for deserialization alone, separate from the field's default value. In other words, it is possible to distinguish the default value a field receives during construction from the one it receives during deserialization if the field is omitted in the serialized data. For example:
#[derive(Clone)]
#[fxstruct(get, serde)]
pub struct Foo {
#[fieldx(
default("constructor".to_string()),
serde(
default("deserialization".to_string())
)
)]
source: String,
}
let foo = Foo::new();
assert_eq!(*foo.source(), "constructor");
let deserialized = serde_json::from_str::<Foo>("{}").expect("Failed to deserialize Foo");
assert_eq!(*deserialized.source(), "deserialization");