1

I want to deserialize API json response into a struct that uses generic type. Code below does not compile and I can't figure out how to make it work:

use serde::Deserialize;
use serde_json; // 1.0.102

#[derive(Deserialize)]
struct ApiResult<T> {
    pub data: T,
    pub status: String
}

fn get_result<T>(json: serde_json::Value) -> Result<ApiResult<T>, anyhow::Error> {
    let r: ApiResult<T> = serde_json::from_value(json)?;
    Ok(r)
}

fn main() -> Result<(),anyhow::Error> {
    let json = serde_json::json!({
        "data": 1,
        "status": "ok"
    });
    
    let r2: ApiResult<i64> = get_result::<i64>(json).unwrap();
    
    Ok(())
}

It produces an error:

Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `T: Deserialize<'_>` is not satisfied
   --> src/main.rs:11:27
    |
11  |     let r: ApiResult<T> = serde_json::from_value(json)?;
    |                           ^^^^^^^^^^^^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `T`
    |
note: required for `ApiResult<T>` to implement `for<'de> Deserialize<'de>`
   --> src/main.rs:4:10
    |
4   | #[derive(Deserialize)]
    |          ^^^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
5   | struct ApiResult<T> {
    |        ^^^^^^^^^^^^
    = note: required for `ApiResult<T>` to implement `DeserializeOwned`
note: required by a bound in `from_value`
   --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/serde_json-1.0.102/src/value/mod.rs:983:8
    |
981 | pub fn from_value<T>(value: Value) -> Result<T, Error>
    |        ---------- required by a bound in this function
982 | where
983 |     T: DeserializeOwned,
    |        ^^^^^^^^^^^^^^^^ required by this bound in `from_value`
    = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
    |
10  | fn get_result<T: _::_serde::Deserialize<'_>>(json: serde_json::Value) -> Result<ApiResult<T>, anyhow::Error> {
    |                ++++++++++++++++++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (bin "playground") due to previous error

Link to playground.

pielgrzym
  • 1,647
  • 3
  • 19
  • 28
  • It's not exactly clear what the question is here, because the error message literally says what to do: `T` needs to implement `serde::Deserialize` in order for any `ApiResult` to implement `Deserialize`. As the compiler says, the signature of `get_result` needs to be something to the tune of `get_result serde::Deserialize<'a>>(json: serde_json::Value) -> Result, anyhow::Error> ` – user2722968 Sep 01 '23 at 09:09
  • Good point - I did not know how to do this due to confusing example in last line :) – pielgrzym Sep 01 '23 at 14:33

1 Answers1

3

In your get_result function, you need to specify that the T type is deserializable (T: for<'a> Deserialize<'a>):

use serde::Deserialize;
use serde_json; // 1.0.102

#[derive(Deserialize)]
struct ApiResult<T> {
    pub data: T,
    pub status: String
}

fn get_result<T: for<'a> Deserialize<'a>>(json: serde_json::Value) -> Result<ApiResult<T>, anyhow::Error> {
    let r: ApiResult<T> = serde_json::from_value(json)?;
    Ok(r)
}

fn main() -> Result<(),anyhow::Error> {
    let json = serde_json::json!({
        "data": 1,
        "status": "ok"
    });
    
    let r2: ApiResult<i64> = get_result::<i64>(json).unwrap();
    
    Ok(())
}

Playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • Thank you!! Can you give me a nudge why this happens? I haven't seen `for` syntax - how is it called? – pielgrzym Sep 01 '23 at 09:14
  • @pielgrzym This kind of `for` syntax is called a "Higher-Ranked Trait Bound" (or [HRTB](https://doc.rust-lang.org/nomicon/hrtb.html)) – Jmb Sep 01 '23 at 09:18
  • But note that the HRTB is not the critical issue here. The main issue is that `#[derive(Deserialize)]` adds an implicit `T: Deserialize` bound, so the same bound is required in `get_bound`. – Jmb Sep 01 '23 at 09:20
  • And since `Deserialize` has a lifetime parameter, you need either a specific lifetime (e.g. `T: Deserialize<'static>`) or a HRTB (which basically means "for any lifetime"). – Jmb Sep 01 '23 at 09:21
  • Thanks! I actually ran into another problem - I can't handle if nested type is an array - I cannot fit `?Sized` trait relaxation. – pielgrzym Sep 01 '23 at 09:39
  • 4
    I think this is the case for which there is also [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html) which is essentially the HRTB as a trait. – cafce25 Sep 01 '23 at 10:06
  • I wrapped the data field in a box to solve the issue with vector/array subfield :) – pielgrzym Sep 01 '23 at 15:38