Search code examples
rustdeserializationserdeserde-json

Deserialize into a generic type with serde::from_value


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.


Solution

  • 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