How to convert Vec to JsonValue in Rust - json

I am querying my database and getting an Vec<Bookable> struct, using diesel library.
#[derive(QueryableByName)]
pub struct Bookable {
#[sql_type = "BigInt"]
pub id: i64,
#[sql_type = "Text"]
pub title: String
}
When I query the elements, I can access the result, but it's not possible to convert the Vec<Bookable> to json! macro:
pub fn get_terms(conn: &MysqlConnection) -> Vec<Bookable> {
diesel::sql_query(r#"SELECT title, LAST_INSERT_ID() 'id' from bookable_term;"#)
.load::<Bookable>(conn).expect("Query failed")
}
And later I call it this way:
let conn = connect();
let terms = bookable::get_terms(&conn);
json!({ "data": {
"items": terms }
})
The question is how to put the terms into this object and send the whole array to the API? I can stringify a json like this:
"items:" &vec!["to", "be", "or", "not", "to", "be"]
But when it comes to an existing Vec I get compiler error. I am using Rocket so it provides a rocket_contrib::json::JsonValue which holds json! macro

First, you derive your struct like -
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use rocket_contrib::{json::{Json}};
#[derive(QueryableByName, Deserialize, Serialize)]
pub struct Bookable {
#[sql_type = "BigInt"]
pub id: i64,
#[sql_type = "Text"]
pub title: String
}
Then you can do something like -
let conn = connect();
let terms = bookable::get_terms(&conn);
let mut data: Vec<Bookable> = HashMap::new();
data.insert("data", terms);
return Json(Vec<Bookable>);
Json(Vec<Bookable>) will also set the content-type as json for the response.

Related

Panic while deserializing JSON data

I'm currently trying to use some API keys to access the Kucoin exchange from my program.I want to be able to have a global initialization of the keys so I can reuse them as credentials in different functions and modules.
I store the keys in a credentials.json file that looks like this:
[{
"name": "",
"api_key": "",
"api_secret": "",
"api_pass": ""
}, {
"name": "",
"api_key": "",
"api_secret": "",
"api_pass": ""
}]
And then go ahead and try to deserialize the json into a format that I can use with my functions, like so:
use kucoin_rs::{serde_json, serde_derive::{Serialize, Deserialize}};
static CREDENTIALS_JSON: &'static str = include_str!("./credentials.json");
#[derive(Serialize, Deserialize, Clone)]
pub struct ApiCredentials {
pub(crate) name: String,
pub(crate) api_key: String,
pub(crate) api_secret: String,
pub(crate) api_pass: String,
}
pub fn load_api_credentials() -> ApiCredentials {
let deserialize_creds = serde_json::from_str::<ApiCredentials(&CREDENTIALS_JSON).expect("Error deserializing JSON data");
deserialize_creds
}
The code compiles when I run cargo check, but panics when I cargo run.The error message reads
thread 'main' panicked at 'Error deserializing JSON data: Error("invalid type: map, expected a string", line: 1, column: 1)'
The message says that it expects a string, so I try to parse CREDENTIALS_JSON as string like so:
let deserialize_creds = serde_json::from_str::<ApiCredentials>(&CREDENTIALS_JSON.to_string()).expect("Unable to deserialize credentials.json!");
But I still get the same error, what I'm I doing wrong?
Your json is a list of credentials.
Deserialize it into a Vec like this:
pub fn load_api_credentials() -> Vec<ApiCredentials> {
let deserialize_creds = serde_json::from_str::<Vec<ApiCredentials>>(&CREDENTIALS_JSON).expect("Error deserializing JSON data");
deserialize_creds
}
Or return the first one:
pub fn load_api_credentials() -> ApiCredentials {
let mut deserialize_creds = serde_json::from_str::<Vec<ApiCredentials>>(&CREDENTIALS_JSON).expect("Error deserializing JSON data");
deserialize_creds.swap_remove(0)
}

deserializing serde_json from API in rust

I am attempting to scrape the JSON from this endpoint https://prices.runescape.wiki/api/v1/osrs/latest .
#[derive(Serialize, Deserialize, Debug)]
struct Latest {
high: u32,
highTime: String,
low: String,
lowTime: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
#[serde(with = "serde_with::json::nested")]
data: Vec<Latest>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Datav2 {
#[serde(with = "serde_with::json::nested")]
data: HashMap<u32, Vec<Latest>>,
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let res = reqwest::get(url).await?;
let response = &res.json::<Datav2>().await?;
}
I've tried two versions of the datastructure. Data is using a vector of latest, but i noticed each object has a unique ID, so in DataV2 i tried using a hashmap but i get the same error. I've also tried unnested versions without using Serde_with.
I get the error Error: reqwest::Error { kind: Decode, source: Error("invalid type: map, expected valid json object", line: 1, column: 8)
It seems my datastructure is messed up, but have been trying for hours to figure out the correct data structure to use.
There are multiple issues with your current code.
Datav2 is closer, but still not correct. It is not a HashMap<u32, Vec<Latest>>but a HashMap<u32, Latest>. There is no need to have another Vec, since each number is assigned a value in the JSON.
highTime, low, lowTime are not of type String (since they have no quotes in the JSON), but are unsigned numbers (to be on the safe side I just assumed them to be u64).
Apparently the fields of Latest can be null, so they need to be Options.
I would still use snake_case for the field names in the structs and then rename them to camelCase using the serde macro
I modified your code like I would do this, in order to give you an example of how it could be done:
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Latest {
high: Option<u64>,
high_time: Option<u64>,
low: Option<u64>,
low_time: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
data: HashMap<u64, Latest>,
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let url = "https://prices.runescape.wiki/api/v1/osrs/latest";
let res = reqwest::get(url).await?;
let response = &res.json::<Data>().await?;
println!("{:#?}", response);
Ok(())
}

Post Rocket route is forwarded (Rust) [duplicate]

This question already has an answer here:
How can I respond to a POST request containing JSON data with Rocket?
(1 answer)
Closed last year.
Sorry to ask such a basic question, there's little info about this in Stack Overflow and GitHub. This must be something silly, but I'm not getting it.
I'm trying to send via JavaScript's fetch to a Rust post endpoint with Rocket 0.5 RC1. But I'm getting hit by:
No matching routes for POST /api/favorites application/json.
Here's the complete log:
POST /api/favorites application/json:
Matched: (post_favorites) POST /api/favorites
`Form < FavoriteInput >` data guard is forwarding.
Outcome: Forward
No matching routes for POST /api/favorites application/json.
No 404 catcher registered. Using Rocket default.
Response succeeded.
Here's my main.rs file (Irrelevant parts omitted):
#[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> {
let cors = CorsOptions::default()
.allowed_origins(AllowedOrigins::all())
.allowed_methods(
vec![Method::Get, Method::Post, Method::Patch]
.into_iter()
.map(From::from)
.collect(),
)
.allow_credentials(true)
.to_cors()?;
rocket::build()
.mount("/api", routes![index, upload::upload, post_favorites])
.attach(cors)
.launch()
.await?;
Ok(())
}
#[post("/favorites", data = "<input>")]
async fn post_favorites(input: Form<FavoriteInput>) -> Json<Favorite> {
let doc = input.into_inner();
let fav = Favorite::new(doc.token_id, doc.name);
let collection = db().await.unwrap().collection::<Favorite>("favorites");
collection.insert_one(&fav, None).await.unwrap();
Json(fav)
}
Here's my cargo.toml:
[dependencies]
rocket = {version ="0.5.0-rc.1", features=["json"]}
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }
reqwest = {version = "0.11.6", features = ["json"] }
serde= {version = "1.0.117", features= ["derive"]}
mongodb = "2.1.0"
rand = "0.8.4"
uuid = {version = "0.8", features = ["serde", "v4"] }
Here are my structs:
#[path = "./paste.rs"]
mod paste;
#[derive(Serialize, Deserialize, Debug)]
pub struct Favorite {
pub id: String,
pub token_id: String,
pub name: String,
}
impl Favorite {
pub fn new(token_id: String, name: String) -> Favorite {
let id = Uuid::new_v4().to_string();
let fav = Favorite { token_id, name, id };
fav
}
}
#[derive(FromForm, Serialize, Deserialize, Debug)]
pub struct FavoriteInput {
pub token_id: String,
pub name: String,
}
Here's the token payload:
I've tried with parameter-less get requests and they were successful.
I thought the Form struct was reading and correctly parsing the FavoriteInput as it allows for missing/extra fields.
There must be something silly that I'm doing wrong. Any ideas?
You seem to be sending JSON from JavaScript but are expecting Form parameters on the server.
You need to change input: Form<FavoriteInput> with input: Json<FavoriteInput>.

Parsing JSON response from request GET call asynchronously

I am making a GET call to some API which in turn would return me list of JSON objects. However, I couldn't parse this into list of custom data structure.
Closest I could come to
struct Pokemon {
id: i32,
name: String,
height: i32,
weight: i32,
}
let mut response = client.get("http://pokeapi.co/api/v2/pokemon/111")
.send()
.expect("Failed to send request");
if let Ok(pokemon) = response.json::<Pokemon>() {
println!("{:#?}", pokemon);
}
Could anyone please provide me suitable example for same. Also, is this the standard way of doing it. I mean what difference would it make to use something like
let url = url.parse().expect("API URL parsing bug");
let request = Request::new(reqwest::Method::GET, url);
self.inner
.execute(request)
.map_err(Error::Request)
.and_then(move |response: Response| {
...
})
In order to use Response::json, you must implement serde::Deserialize for Pokemon. You can do this by adding the following to your Cargo.toml, under [dependencies].
serde = { version = "1.0", features = ["derive"] }
Then, add use serde::Deserialize; at the top of your file, and change the declaration of Pokemon to:
#[derive(Deserialize)]
struct Pokemon {
id: i32,
name: String,
height: i32,
weight: i32,
}

How to return a JSON file using a Nickel template?

I'm trying to return a JSON file using a Nickel template. I found some API sample code that returns a JSON response and modified it:
extern crate rustc_serialize;
#[macro_use]
extern crate nickel;
use nickel::{Nickel, HttpRouter, JsonBody};
use nickel::mimes::MediaType;
use nickel::status::*;
use rustc_serialize::json;
use std::collections::HashMap;
#[derive(RustcDecodable, RustcEncodable)]
struct Person {
firstname: String,
lastname: String,
}
fn main() {
let mut server = Nickel::new();
server.get("/post", middleware! { |request, mut response|
let person: Person = Person { firstname: "firstName ".to_string(), lastname: "lastName".to_string()};
let mut p: Vec<Person> = vec![];
p.push(person);
let json_data = json::encode(&p).unwrap();
let mut data_result = "{\"status\": 200, \"data\":".to_owned();
data_result.push_str(&json_data.to_string());
data_result.push_str("}");
response.set(StatusCode::Ok);
response.set(MediaType::Json);
format!("{}", data_result)
});
server.get("/json", middleware! { |_, response|
let mut data = HashMap::new();
data.insert("name", "user");
return response.render("app/views/temp.tpl", &data);
// template source
//
//{name: {{name}}}
//
});
server.listen("127.0.0.1:6767");
}
And /post returns this JSON:
{ "status": 200, "data": [{ "firstname": "firstName ", "lastname": "lastName" ]}
/json returns this text:
"name: user"
How to return a JSON file using templates?
It actually returns
{name: user}
All you need to do is add response.set(MediaType::Json); like you already have in the other handler:
#[macro_use]
extern crate nickel;
use nickel::{Nickel, HttpRouter};
use nickel::mimes::MediaType;
use std::collections::HashMap;
fn main() {
let mut server = Nickel::new();
server.get("/json", middleware! { |_, mut response| {
let mut data = HashMap::new();
data.insert("name", "user");
response.set(MediaType::Json);
return response.render("app/views/temp.tpl", &data);
}});
server.listen("127.0.0.1:6767");
}
Now, this may not be a good idea. Creating structured formats (CSV, JSON, XML, etc.) via string concatenation often has problems with malformed documents or improperly escaped data.