Note that we want to bring in the derive feature from serde. This will allow us to derive the Serialize implementation for our structs. We'll also bring in serde_json so that we can convert our struct to a JSON formatted String.
Example
Let's take an example using a struct from the Rust-Bitcoin library:
pubstructTxOut{/// The value of the output, in satoshis.pubvalue:Amount,/// The script which must be satisfied for the output to be spent.pubscript_pubkey:ScriptBuf,}
So there are two different field types here, the Amount type and the ScriptBuf type. These are essentially just wrappers for other types in the form of tuple structs. Let's include those.
Now we can add the derive attribute to TxOut for Serialize and see if this works by calling serde_json::to_string_pretty on our TxOut struct instance. Remember to bring serde_json and serde::Serialize into scope. You can follow along on Rust Playground.
This, of course, won't work. We need to implement the Serialize trait for both the ScriptBuf and Amount. This trait is already implemented by default for most primitive and standard types. However, since these are custom types we'll need to implement the Serialize trait for each of them.
Problem Statement
Implement the Serialize trait for the Amount and ScriptBuf structs.
Implementation
When implementing the Serialize trait, we must implement the required method, serialize. This is the method signature: fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error>. It accepts a type which implements the Serializer trait. This type will be passed in by serde_json when we call the to_string_pretty method. The Serializer trait has several methods available that we can use to serialize various types, which can be found in the documentation.
Let's start with a simple version, leveraging the methods available for the Serializer trait.
Notice that we use the serialize_u64 and serialize_bytes methods. The serialize_bytes method accepts a slice as an argument so we'll call the .as_slice method on the Vec.
This will now work and print the json output we expect:
Separating display from storage
However, there's another problem. We want to print the Amount in bitcoin and not satoshis. We want to store it in satoshis for internal purposes but serialize it as a bitcoin amount for display purposes.
With serde we can provide custom serialization at the field level. See documentation for various field attributes that we can add. This is useful when we want to serialize Amount differently based on its context.
We can do something like the following:
The as_btc method accepts a generic type T so we'll set up a new trait called SerdeAmount and implement the ser_btc method.
pub struct Amount(u64)
pub struct ScriptBuf(Vec<u8>)
pub struct TxOut {
/// The value of the output, in satoshis.
pub value: Amount,
/// The script which must be satisfied for the output to be spent.
pub script_pubkey: ScriptBuf,
}
use serde_json;
use serde::Serialize;
pub struct Amount(pub u64);
pub struct ScriptBuf(pub Vec<u8>);
#[derive(Serialize)]
pub struct TxOut {
/// The value of the output, in satoshis.
pub value: Amount,
/// The script which must be satisfied for the output to be spent.
pub script_pubkey: ScriptBuf,
}
fn main() -> () {
let amount = Amount(1_000_000);
let script_pubkey = ScriptBuf(vec![0, 0, 0, 0]);
let txout = TxOut {
amount,
script_pubkey,
};
let json_txout = serde_json::to_string_pretty(&txout).unwrap();
println!("{}", json_txout);
}
#[derive(Serialize)]
...
pub struct TxOut {
/// The value of the output, in satoshis.
#[serde(serialize_with = "as_btc")]
pub value: Amount,
/// The script which must be satisfied for the output to be spent.
pub script_pubkey: ScriptBuf,
}
...
...
trait SerdeAmount {
fn ser_btc(&self) -> f64;
}
impl SerdeAmount for Amount {
fn ser_btc(&self) -> f64 {
self.0 as f64 / 100_000_000.0
}
}
#[derive(Serialize)]
pub struct TxOut {
/// The value of the output, in satoshis.
#[serde(serialize_with = "as_btc")]
pub value: Amount,
/// The script which must be satisfied for the output to be spent.
pub script_pubkey: ScriptBuf,
}
fn as_btc<T: SerdeAmount, S: Serializer>(t: &T, s: S) -> Result<S::Ok, S::Error> {
s.serialize_f64(t.ser_btc())
}
...