Open bodymindarts opened 4 years ago
👍 will add this to the roadmap. we already have a temporary implementation here: https://github.com/varunsrin/rusty_money/pull/27
I recently had to implement Serialize
/ Deserialize
for a struct which contained Money
, a struct that doesn't have these traits implemented. What I did was create a struct with mock data that did implement those traits, but had enough information for me to regain my initial struct.
Here's the source.
Edit: I ended up writing my own crate for this purpose
I came up with a partial solution. Thought I'd share for discussion or perhaps help someone else.
// My use case is ecom, so this toy struct contains normal fields and rusty-money Currency field
// We use an Rc<> to wrap the Currency and abstract away ownership with the smart pointer
#[derive(Debug, Serialize, Deserialize)]
struct Order {
money: f64, // todo
#[serde(serialize_with="ser_currency", deserialize_with="de_currency")]
currency: Rc<Currency>,
}
// Serialize Fn
fn ser_currency<S>(currency_field: &Rc<Currency>, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
//
let currency_string = currency_field.as_ref().to_string();
serializer.serialize_str(¤cy_string[..])
}
// Deserialize Fn
fn de_currency<'de, D>(deserializer: D)-> Result<Rc<Currency>, D::Error>
where D: Deserializer<'de> {
struct V;
impl <'de> Visitor<'de> for V {
type Value = Rc<Currency>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a string or array of bytes")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let currency: Rc<Currency> = Rc::new(*iso::find(s)
.expect(format!("{s} is not a supported currency.").as_str()));
Ok(currency)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut res = vec![];
while let Some(b) = seq.next_element::<u8>()? {
res.push(b);
}
let mut os_string = OsString::from_vec(res);
os_string.make_ascii_uppercase();
let s = os_string.to_string_lossy();
let currency: Rc<Currency> = Rc::new(*iso::find(s.as_ref())
.expect(format!("{s} is not a supported currency.").as_str()));
Ok(currency)
}
}
deserializer.deserialize_any(V)
}
This produces behavior close to what we expect.
fn main() {
let json = json!({
"money": 42.42,
"currency": "USD"
});
let bad_json = json!({
"money": 42.42,
"currency": "AAA"
});
let order = Order { money: 42.42, currency: Rc::new(*iso::USD)};
eprintln!("{:#}", serde_json::to_string_pretty(&order).unwrap());
let order_rehyd: Order = serde_json::from_value(json).unwrap();
eprintln!("{:#}", serde_json::to_string_pretty(&order_rehyd).unwrap());
let bad_order: Order = serde_json::from_value(bad_json).unwrap(); // Properly Panics Here
// eprintln!("{:#}", serde_json::to_string_pretty(&bad_order).unwrap());
}
I also had success using an enum to represent currencies and impl Into<rusty_money::iso::Currency>
As you can see the money value itself is not yet solved. I might keep it fixed decimal, and only converting to Money when I need to do a calculation. Additional point I mentioned on a different issue, iso is missing DEM and it is proving difficult to deserialize around that.
As I said, just my findings. Hopefully, it helps others. Thanks for the crate!
I am using this library to represent Money in the backend of a web-app. Sometimes I need to serialise and need to translate into another struct for that. Could we add a feature to (de-)serialise via Serde?
Alternatively perhaps we could make Iso Copy/Clone to make it easier to pass around and embed in other structs.