Serialization
With the serde feature, a decimal implements serde’s Serialize and
Deserialize. Where a person reads the format, as in JSON, TOML or YAML, a
decimal is its text; where none does, as in bincode or postcard, it is its
steps. Text is read exactly, as Text reads it, and a float is
refused, since it has already lost the digits a decimal keeps:
fn a_decimal_is_its_text_where_a_person_reads_the_format() {
use ddust::{D64, dec};
use serde_json::{from_str, to_string};
let price: D64<2> = dec!(64250.75);
assert_eq!(to_string(&price).expect("a decimal writes as a JSON string"), r#""64250.75""#);
let read = |json: &str| from_str::<D64<2>>(json).map_err(|error| error.to_string());
assert_eq!(read(r#""64250.75""#), Ok(price));
assert_eq!(read("64250"), Ok(dec!(64250)), "a whole number, exactly");
let float = r#"invalid type: floating point `64250.75`, expected a decimal as a string, as "60000.5", or a whole number at line 1 column 8"#;
assert_eq!(
read("64250.75"),
Err(float.into()),
"a float, which has lost the digits, is refused"
);
let past = "parse error: the number has more fraction digits than the decimal's scale at line 1 column 11";
assert_eq!(
read(r#""64250.755""#),
Err(past.into()),
"a digit past the scale, as parsing refuses it"
);
}
Choosing a Field’s Form
A field takes another form through #[serde(with = …)] and one of the modules
in ddust::serde, each of which has an option twin for an Option field:
| Form | Where a person reads the format | Where none does |
|---|---|---|
| the decimal’s own | its text | its steps |
text | its text | its text |
steps | its steps, as a number | its steps |
float | its nearest double | the same |
Each reads what it writes; text and the decimal’s own form read a whole number
too, and steps a string of digits, where a person reads the format:
fn a_field_chooses_its_form() {
use ddust::{D64, dec};
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Fill {
price: D64<2>,
#[serde(with = "ddust::serde::steps")]
quantity: D64<4>,
#[serde(with = "ddust::serde::float")]
fee: D64<4>,
#[serde(with = "ddust::serde::text::option", default)]
rebate: Option<D64<4>>,
}
let fill =
Fill { price: dec!(64250.75), quantity: dec!(0.0153), fee: dec!(0.25), rebate: None };
let json = to_string(&fill).expect("each field writes as a JSON value");
assert_eq!(json, r#"{"price":"64250.75","quantity":153,"fee":0.25,"rebate":null}"#);
assert_eq!(from_str::<Fill>(&json).expect("each form reads what it writes"), fill);
}
steps suits a format whose numbers are integers and whose scale the schema
already states. float suits a consumer that takes only doubles: a double is
rarely exact at a decimal scale, and is read back at the nearest step, as
Floats says.
Run-Time Scales
A decimal of a run-time scale is written as every decimal of its scale, so
1.50 stays at two decimals, and is read at the scale its text spells. Where no
person reads the format, it is its steps and its decimals, and more than 38
decimals are refused:
fn a_run_time_scale_is_read_from_the_text() {
use ddust::{Decimal, Dynamic};
use serde_json::{from_str, to_string};
let price: Decimal<i64, Dynamic> = from_str(r#""1.50""#).expect("1.50 is a decimal");
assert_eq!((price.steps(), price.decimals()), (150, 2));
let json = to_string(&price).expect("a decimal writes as a JSON string");
assert_eq!(json, r#""1.50""#, "its scale kept");
}
The modules take a decimal of a static scale, whose scale the type already gives.