Parsing JSON response from request GET call asynchronously - json

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,
}

Related

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>.

Combining multiple JSONs with id

I'm trying to fetch data from a public API. However, all the data I need is accessible only by calling multiple URLs.
However, each JSON provided have a station_id and I'm trying to combine the data based on this value.
I am not sure which strategy I should use to "merge" the results (see code below)
I tried calling both URL at the same time.
Also tried to add the data from the second URL after calling the first URL.
first URL (https://api-core.bixi.com/gbfs/es/station_information.json)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"external_id":"0b100854-08f3-11e7-a1cb-3863bb33a4e4",
"name":"de la Commune / Place Jacques-Cartier",
"short_name":"6026",
"lat":45.50761009451047,
"lon":-73.55183601379395,
"capacity":89,}]
// ...
Second URL (https://api-core.bixi.com/gbfs/en/station_status.json)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"num_bikes_available": 39,
"num_docks_available":50,}]
// ...
Excepted Result (This is the structure I am looking for, not the final code)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"external_id":"0b100854-08f3-11e7-a1cb-3863bb33a4e4",
"name":"de la Commune / Place Jacques-Cartier",
"short_name":"6026",
"lat":45.50761009451047,
"lon":-73.55183601379395,
"capacity":89,
"num_bikes_available": 39,
"num_docks_available":50}]
//...
Structure I tried to pass the data in
struct BixiApiDataModel: Codable {
let last_updated: Int
let ttl: Int
let data: Stations
}
struct Stations: Codable {
let stations: [Station]
}
struct Station: Codable {
let station_id: String
let num_bikes_available: Int
let num_docks_available: Int
let external_id: String
let name: String
let short_name: String
let lat: Float
let lon: Float
let capacity: Int
}
Calling the URL
class Webservice {
func loadBixiApiDataModel(url: URL, completion: #escaping ([Station]?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let response = try? JSONDecoder().decode(BixiApiDataModel.self, from: data)
if let response = response {
DispatchQueue.main.async {
completion(response.data.stations)
}
}
}.resume()
}
}
I'm trying to display the combined information of a station. I assume the data I fetch after calling the first URL isn't stored when I call the second URL.
Should I call both APIs separately, store the data and then combine everything using the station_id value?
Or is it possible to call each APIs and append the data from the second URL based on the station_id?
Thanks in advance for your help.
I would do it like this
Handle each download separately
Keep the resulting data in separate structs
Merge them into a third struct and then use that third struct internally in the app
Handle each download separately
Download the station information first and store it in a dictionary with station_id as key and then download station status and use the same id to match the downloaded elements
Keep the resulting data in separate structs
Since the content of the downloaded data is quite different between the to API calls I would use two different structs for them, StationInformation and StationStatus. Looking at the type of data you might actually want to download status more often than information which seems to be more static so that is another reason to keep them separate.
Merge them into a third struct...
I would create a third struct that contains information from the two other structs, either as just two properties (shown below) or with properties that are extracted from the others
Here is an example of how the third struct could be implemented
struct Station {
let information: StationInformation
var status: StationStatus?
init(information: StationInformation) {
self.information = information
}
var id: String {
return information.stationId
}
mutating func merge(status: StationStatus) {
guard self.id == status.stationId else { return }
self.status = status
}
}
The download function could be modified to be generic to simplify the code. First the structs need to be modified
struct BixiApiDataModel<T: Decodable>: Decodable {
let data: Stations<T>
}
struct Stations<T: Decodable>: Decodable {
let stations: [T]
}
struct StationInformation: Codable {
let stationId: String
let externalId: String
//... rest of properties
}
struct StationStatus: Codable {
let stationId: String
let numBikesAvailable: Int
let numDocksAvailable: Int
}
then the function signature needs to be changed to
func loadBixiApiDataModel<T: Decodable>(url: URL, completion: #escaping ([T]?) -> ()) {
and the decoding needs to be changed (notice the improved error handling, never use try?)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(BixiApiDataModel<T>.self, from: data)
completion(response.data.stations)
} catch {
print(error)
}
And a simplified example of calling the function (but without any merging)
var informationArray: [StationInformation] = []
var statusArray: [StationStatus] = []
if let url = URL(string: "https://api-core.bixi.com/gbfs/es/station_information.json") {
loadBixiApiDataModel(url: url, completion: {(arr: [StationInformation]?) in
if let arr = arr { informationArray = arr }
print(informationArray.count)
})
} else { print("Not a valid url")}
if let url = URL(string: "https://api-core.bixi.com/gbfs/en/station_status.json") {
loadBixiApiDataModel(url: url, completion: {(arr: [StationStatus]?) in
if let arr = arr { statusArray = arr }
print(statusArray.count)
})
} else { print("Not a valid url")}

How to convert Vec to JsonValue in Rust

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.

Cannot invoke 'decode' with an argument list of type '(Codable.Type?

I'm refactoring (trying to) some network code made by an ex-colleague and I wanted to use the new JSONDecoder.
His network code is wrapped into a function getData:
func getData(modelToReturn:Proto.Type, configuredRequest:URLRequest) {
As you can see from the signature, in this method you could specify the model to return (that is which model is supposed to deserialize the retrieved JSON: I can't say it better, I hope you understood anyway :-) ).
In this case, Proto is the protocol any model conforms to.
eg.
struct GroceryModel:Proto{
//...
}
The method was then called this way:
net.getData(modelToReturn: GroceryModel.self, configuredRequest: grocery)
Once called, GroceryModel.self was assigned to a member of the net class (memberToReturn:Proto.Type?) and then the JSON tested for conformance against a model struct with a failable initializer:
if let instance = modelToReturn?.init(json:jsonResult){...
I made a little test with the following protocol:
struct Test: Codable {
let name: String
let testers: [String]
}
and I replaced much of the original code with this single line:
let test = try? JSONDecoder().decode(modelToReturn, from: jsonData)
My idea was to pass Codable.self as the first parameter of the getData function but I got an error on this last line:
Cannot invoke 'decode' with an argument list of type '(Codable.Type?, from: Data)'
So, how can I properly pass my model, so that it can be used from the decode function?
I must say, I manually tested that line by simply replacing modelToReturn with Test.self and the error disappears.
You can use generics like below.
protocol Proto {
}
struct Test: Codable, Proto {
let name: String
let testers: [String]
}
func getData<T>(type: T.Type, configuredRequest:URLRequest?) where T : Decodable {
let data = """
{
"name": "a test",
"testers": ["mike", "bob"],
}
""".data(using: .utf8)!
let test = try? JSONDecoder().decode(type, from: data)
debugPrint(test)
}
getData(type: Test.self, configuredRequest: nil)
Edited.
As commented, you want Type to be a member of the class, which has getData.
In that case you can apply generics to class definition like below.
class DataFetcher<T> where T: Decodable {
func getData(configuredRequest:URLRequest?) {
let data = """
{
"name": "a test",
"testers": ["mike", "bob"],
}
""".data(using: .utf8)!
let test = try? JSONDecoder().decode(T.self, from: data)
debugPrint(test)
}
}
DataFetcher<Test>().getData(configuredRequest: nil)

Mapping a JSON object to a Swift class/struct

I need to "replicate" an entiry which is returned from a remote web API service in JSON. It looks like this:
{
"field1": "some_id",
"entity_name" = "Entity1"
"field2": "some name",
"details1": [{
"field1": 11,
"field2": "some value",
"data": {
"key1": "value1",
"key2": "value2",
"key3": "value3",
// any other, unknown at compile time keys
}
}],
"details2": {
"field1": 13,
"field2": "some value2"
}
}
Here's my attempt:
struct Entity1 {
struct Details1 {
let field1: UInt32
let field2: String
let data: [String: String]
}
struct Details2 {
let field1: UInt32
let field2: String
}
let field1: String
static let entityName = "Entity1"
let field2: String
let details1: [Details1]
let details2: Details2
}
Is it a good idea to use structs instead of classes for such a goal
as mine?
Can I anyhow define a nested struct or a class, say
Details1 and create a variable of it at the same time?
Like this:
//doesn't compile
struct Entity1 {
let details1: [Details1 {
let field1: UInt32
let field2: String
let data: [String: String]
}]
You can use any if the following good open-source libraries available to handle the mapping of JSON to Object in Swift, take a look :
Mapper
ObjectMapper
JSONHelper
Argo
Unbox
Each one have nice a good tutorial for beginners.
Regarding the theme of struct or class, you can consider the following text from The Swift Programming Language documentation:
Structure instances are always passed by value, and class
instances are always passed by reference. This means that they are
suited to different kinds of tasks. As you consider the data
constructs and functionality that you need for a project, decide
whether each data construct should be defined as a class or as a
structure.
As a general guideline, consider creating a structure when one or more
of these conditions apply:
The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an
instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.
Examples of good candidates for structures include:
The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.
In all other cases, define a class, and create instances of that class
to be managed and passed by reference. In practice, this means that
most custom data constructs should be classes, not structures.
I hope this help you.
HandyJSON is exactly what you need. See code example:
struct Animal: HandyJSON {
var name: String?
var id: String?
var num: Int?
}
let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"
if let animal = JSONDeserializer.deserializeFrom(json: jsonString) {
print(animal)
}
https://github.com/alibaba/handyjson
Details
Xcode 10.2.1 (10E1001), Swift 5
Links
Pods:
Alamofire - loading data
More info:
Codable
More samples of usage Codable and ObjectMapper in Swift 5
Task
Get itunes search results using iTunes Search API with simple request https://itunes.apple.com/search?term=jack+johnson
Full sample
import UIKit
import Alamofire
// Itunce api doc: https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#searching
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
private func loadData() {
let urlString = "https://itunes.apple.com/search?term=jack+johnson"
Alamofire.request(urlString).response { response in
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(ItunceItems.self, from: data)
print(result)
} catch let error {
print("\(error.localizedDescription)")
}
}
}
}
struct ItunceItems: Codable {
let resultCount: Int
let results: [ItunceItem]
}
struct ItunceItem: Codable {
var wrapperType: String?
var artistId: Int?
var trackName: String?
var trackPrice: Double?
var currency: String?
}
you could use SwiftyJson and let json = JSONValue(dataFromNetworking)
if let userName = json[0]["user"]["name"].string{
//Now you got your value
}
Take a look at this awesome library that perfectly fits your need, Argo on GitHub.
In your case, a struct is ok. You can read more on how to choose between a struct and a class here.
You can go with this extension for Alamofire https://github.com/sua8051/AlamofireMapper
Declare a class or struct:
class UserResponse: Decodable {
var page: Int!
var per_page: Int!
var total: Int!
var total_pages: Int!
var data: [User]?
}
class User: Decodable {
var id: Double!
var first_name: String!
var last_name: String!
var avatar: String!
}
Use:
import Alamofire
import AlamofireMapper
let url1 = "https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json"
Alamofire.request(url1, method: .get
, parameters: nil, encoding: URLEncoding.default, headers: nil).responseObject { (response: DataResponse<UserResponse>) in
switch response.result {
case let .success(data):
dump(data)
case let .failure(error):
dump(error)
}
}