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

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:

FormWhere a person reads the formatWhere none does
the decimal’s ownits textits steps
textits textits text
stepsits steps, as a numberits steps
floatits nearest doublethe 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.