How to parse a response body as JSON format using reqwest?

Summary

I would like to cast the reqwest::Response to a structure using json(), but I get the following decode error.

thread 'main' panicked at 'failed to convert struct from json: reqwest::Error
{
    kind: Decode,
    source: Error("missing field `user_id`", line: 6, column: 1)
}',
src/request.rs:30:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I checked previous topics, but most use reqwest::blocking and I couldn't find anything for async, so I asked here.

Can you please tell me how to fix it?

Any help would be greatly appreciated.

Output

$ cargo run

# println!("Status: {}", res.status());
Status: 200 OK

# println!("Responce All: {:?}", res);
Response All:
Response {
    url: Url {
        scheme: "https",
        cannot_be_a_base: false, username: "",
        password: None,
        host: Some(Domain("jsonplaceholder.typicode.com")),
        port: None,
        path: "/posts/1",
        query: None,
        fragment: None
    },
    status: 200,
    headers: {
        "date": "Fri, 25 Nov 2022 04:54:45 GMT",
        "content-type": "application/json; charset=utf-8",
        "content-length": "292",
        "connection": "keep-alive",
    ...        
    }
}

# Error
thread 'main' panicked at 'failed to convert struct from json: reqwest::Error { kind: Decode, source: Error("missing field `user_id`", line: 6, column: 1) }', src/request.rs:31:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Source Code

source code is below.

Directory

app
├ src
│ ├ types
│ │ └ post.rs
│ ├ main.rs
│ ├ types.rs
│ └ request.rs
└ Cargo.toml

Cargo.toml

[package]
name = "get-post-example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = {version = "0.11.13", features = ["json"] }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.89"
tokio = { version = "1.22.0", features = ["full"] }

types.rs

pub mod post;

types/post.rs

use serde::{Deserialize, Serialize};

/// @see https://jsonplaceholder.typicode.com/guide/

/// Post
#[derive(Debug, Serialize, Deserialize)]
pub struct Post {
    user_id: i32,
    id: i32,
    title: String,
}

request.rs

use reqwest::{Client, Method, Response};

use crate::types::post::Post;

/// Send Request
async fn send_request(endpoint: &str, method: Method) -> Response {
    let client = Client::new();

    let base_url = "https://jsonplaceholder.typicode.com";
    let url = format!("{}{}", base_url, endpoint);

    let res = client
        .request(method, url)
        .send()
        .await
        .expect("failed to get response");

    println!("Status: {}", res.status());
    println!("Responce All: {:?}", res);

    res
}

/// FetchPost
pub async fn fetch_post() -> Post {
    let endpoint = "todos/1";

    let post = send_request(endpoint, Method::GET)
        .await
        .json::<Post>()
        .await
        .expect("failed to convert struct from json");

    return post;
}

main.rs

use types::post::Post;

mod request;
mod types;

#[tokio::main]
async fn main() {
    // Fetch Post
    let post: Post = request::fetch_post().await;
    println!("{:?}", post)
}

I believe the json you're reading is missing the user_id field, because it's userId in the "raw" json.

You can use serde attributes to handle that rename_all = "camelCase"

Expanding the example to show deserialization too.

2 Likes

@erelde

You can use serde attributes to handle that rename_all = "camelCase"

Thanks to your help, it went well.