Deserialize json

Hi

I'm trying to login to a service and get a token. The code as such seems to work and I can print the response which is in json. So I want to deserialize the json response, so I can work on getting to the actual data :slight_smile: But for some reason I cannot figure out how to make it work. Can someone help me make it work?

I get the following error in the current attempt

Rocket has launched from http://127.0.0.1:8000
GET /test?code=fcd960f6-4dcb-4223-bcd5-14304cc917ff&state=ramdom text/html:
   >> Matched: (callback) GET /test?<code>&<state>
Err(
    Error("invalid type: map, expected a string", line: 1, column: 0),
)
   >> Outcome: Success
   >> Response succeeded.

I'm still learning rust, but I have hit the wall and don't know how to make it work.

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;
use rocket::response::Redirect;

extern crate reqwest;
use std::collections::HashMap;

extern crate serde;
use serde::{Deserialize, Serialize};

extern crate serde_json;
use serde_json::{Result, Value, Error};

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct JsonResponse {
    access_token: String,
    token_type: String,
    expires_in: u16,
    refresh_token: String,
    refresh_token_expires_in: u16,
    base_uri: Option,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
enum Option {
    Some(String),
    None,
    }

/*{
    "access_token": "eyJhbGciOiJFUzI1NiIsIng1dCI6IjhGQzE5Qjc0MzFCNjNFNTVCNjc0M0QwQTc5MjMzNjZCREZGOEI4NTAifQ.eyJvYWEiOiIzMzMzMCIsImlzcyI6Im9hIiwiYWlkIjoiMTg2NyIsInVpZCI6IkMxdkhaWThNZjg3cFNQVHNuOHJZSnc9PSIsImNpZCI6IkMxdkhaWThNZjg3cFNQVHNuOHJZSnc9PSIsImlzYSI6IkZhbHNlIiwidGlkIjoiNjg5MiIsInNpZCI6IjhkYjFjYWY1NDhhODQyNTVhNGNhMTQ5NDFjMzBlODg5IiwiZGdpIjoiODQiLCJleHAiOiIxNjI1NTEzODMyIiwib2FsIjoiMUYifQ.SkdlScwDoS5AagtUk3A9eyIu-wKLkzZ9g1i1iWPUGJSAyiMDMtRcp02gK-6UMmK3EBZY04MW3M-GFi-P_n1wsw",
    "token_type": "Bearer",
    "expires_in": 1200,
    "refresh_token": "5b6e6987-82bd-46cb-9e08-5675fa7614ff",
    "refresh_token_expires_in": 3600,
    "base_uri": null
}*/

use std::future::Future;
use std::option;

// https://sim.logonvalidation.net/authorize?response_type=code&client_id=ZYX&state=y90dsygas98dygoidsahf8sa&redirect_uri=http://localhost/test
#[get("/login")]
fn login() -> Redirect {
    let client_id = "ZYX";
    let redirect_uri = "http://localhost:8000/test";
    let response_type = "code";
    let state = "ramdom";
    let uri = "url";

    Redirect::to(format!(
        "{}authorize?response_type={}&Client_id={}&state={}&redirect_uri={}",
        uri, response_type, client_id, state, redirect_uri, 
    ))
}

//http://localhost:8000/test?code=0286b802-1aab-431c-a17d-e44f4a309876&state=ramdom
#[get("/test?<code>&<state>")]
async fn callback(code: String, state: String) -> () {
    let uri = "url";

    let mut map = HashMap::new();
    map.insert("grant_type", "authorization_code");
    map.insert("code", &code);
    //map.insert("Content-Type", "application/x-www-form-urlencoded");
    //map.insert("redirect_uri", "http://localhost:8000/final");

    let client = reqwest::Client::new();
    let res = client.post(uri)
        .basic_auth("XYZ", Some("XYZ"))
        .json(&map)
        .send()
        .await;

    //println!("{:#?}", res.unwrap().text().await.unwrap());
    //println!("{:#?}", res.unwrap().text().await.unwrap());

    //let s: String = res.unwrap().text().await.unwrap().to_owned();
    //let s_slice: &str = &s[..];  // take a full slice of the string

    let profiles = serde_json::Deserializer::from_str(&res.unwrap().text().await.unwrap());
    println!( "{:?}", profiles);
    //let mut my_json: String = String::from(&res.unwrap().text().await.unwrap());
    //println!( "{:#?}", serde_json::from_str::<String>(&res.unwrap().text().await.unwrap()));
    //let data: JsonResponse = serde_json::from_str(s_slice).unwrap();
    //println!( "{:#?}", data.unwrap());
    //format!( "{:?}", data);
    //result.headers().get("set-cookie").unwrap()
    //println!( "{:#?}" , serde_json::from_str::<String>( &my_json.replace('"', "") ) );
}

fn takes_str(s: &str) { }

#[rocket::main]
async fn main() -> Result<()> {
    rocket::build()
    .mount("/", routes![callback])
    .mount("/", routes![login])
    .ignite()
    .await
    .unwrap()
    .launch()
    .await;
    Ok(())
}

Best regards

Michael Ryom

The example code you posted doesn't seem to quite match with the code you're running when getting the error, but it looks like you tried to deserialize the response using serde_json::from_str::<String>(text), which only works if the contents of some_text is a JSON string, such as let text = "\"hello\"";. Instead you're receiving some JSON object, like { "key": "value" }. It looks like you already tried to deserialize into JsonResponse on one of the commented lines:

    //let data: JsonResponse = serde_json::from_str(s_slice).unwrap();

I assume you met some other error while trying it. Assuming the commented JSON documents the format of the response, you need to remove the #[serde(rename_all = "camelCase")] attribute from JsonResponse. Currently, the JSON has fields named like expires_in but because of this attribute serde expects them in camelCase, like expiresIn.

As an aside, the custom Option enum is unnecessary and you can just use Option<String> instead.

1 Like

Sorry for the wrong error code, yes seems that was an earlier one.

Did the change suggested for Option and used s_slice and that did the trick.
To figure out my issue I change Option back and now it fails with the below code. So can you enlighten me to what wrong with the Enum I used?

thread 'rocket-worker-thread' panicked at 'called `Result::unwrap()` on an `Err` value: Error("expected value", line: 1, column: 618)', src\main.rs:89:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
   >> Handler callback panicked.
   >> This is an application bug.
   >> A panic in Rust must be treated as an exceptional event.
   >> Panicking is not a suitable error handling mechanism.
   >> Unwinding, the result of a panic, is an expensive operation.
   >> Panics will degrade application performance.
   >> Instead of panicking, return `Option` and/or `Result`.
   >> Values of either type can be returned directly from handlers.
   >> A panic is treated as an internal server error.
   >> Outcome: Failure
   >> No 500 catcher registered. Using Rocket default.

Thanks for your help ( I have used days on this problem and then the solution always seems so simple :expressionless: )

It looks like base_uri is either a string or null, which corresponds to Option<String> in serde. Its de/serialize implementation is such that if the value is null or missing, it uses the None case, otherwise it uses the Some case with the value.

The enum you've made uses the derived Deserialize implementation, which corresponds to JSON like

{
  "some": "some text"
}

for Option::Some("some text"), or

"none"

for Option::None. You can see other ways to de/serialize enums with serde at Enum representations ยท Serde, but this is the default functionality (with a camelCase attribute).

2 Likes