0

My question is pretty simple - what are the ways I can model a JSON response of, e.g., stock prices. Let's say I want to model a JSON response of a price query request that gives me stock names and prices, like:

{"AAPL": {"usd": 10}, "GOOG": {"usd": 20} ...}

If I model this with an enum together with serde crate, it will require me to list a huge number of stock variants, and even if I'll somehow manage to do that, it will still be very inefficient because new stocks are added constantly and I won't be able to properly maintain the variants list. So the following is not feasible:

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PriceResponse {
    AAPL(HashMap<String, HashMap<String, f32>>),
    GOOG(HashMap<String, HashMap<String, f32>>),
    ...
    ...
}

I do want to make use of rust's type system to make the response more "typed", but I don't know how to do it. Ideally, I want to get an enum or a struct back.

Corel
  • 581
  • 3
  • 21
  • It feels like you're trying to enumerate things which are not enumerable in the first place? Stocks are not a well-known fixed list, stocks are added and removed all the time from exchanges, to say nothing of systems interacting with exchanges. That's not what `enum` is for. Best case scenario you could have a few special-cased stocks but even then... what purpose would that have really? Same with currency, though *that* might actually make some amount of sense as you could have a reference currency which all others are relative to, giving more sense to the special-casing. – Masklinn May 11 '21 at 09:45

1 Answers1

1

If I understand you, your data is of the format HashMap<String, HashMap<String, f32>> but you want to parse it into something with types that are more representative of the data. Could you define structs that represent your data but are not enums? Enums are for when the data can take different forms or have different meanings, but in this case every stock seems semantically the same. According to the serde documentation, you won't need to do extra work to get serde to deserialize the inner field.

struct Stock(pub String);
struct CurrencyName(pub String);
struct Price(pub i32); // currencies are typically stored as integer
struct StockResponse(pub Hashmap<Stock, HashMap<CurrencyName, Price>>);
piojo
  • 6,351
  • 1
  • 26
  • 36
  • Thanks! I think that turns out to be the best solution, although it introduces tuple notation like `response.0.get()` which I'm not a fan of. I still don't like the high number of `.get()`s that you have to do in order to extract a price of e.g. `AAPL` stock, at certain currency like `usd`, but it's another issue. – Corel May 11 '21 at 09:59
  • @Corel True. You could always implement a `get` method for the structs. A lot of the `.0` goes away when you write methods that accept your structs. The `.0` gets used when you interact with code you didn't write. (Though you will need to derive Debug, Hash, Eq, and a bunch of other traits.) I think some `.0` goes away if you implement `Deref` as well. Implementing `AsRef` may help in some cases, but I'd just implement what you need to. – piojo May 11 '21 at 10:15