Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Serialization

Warning

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")

Output

{
  "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)))]

Tip

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");