Make Response::into_json deserialize into a serde DeserializeOwned

This removes the necessity to take the result of Response::into_json and
having to convert it into a struct by using serde_json::from_value

This adds no new dependencies since serde_json already depends on serde.
Users of ureq will have to include `serde_derive` either by importing it
directly or by using serde with the `derive` feature, unless they want to
manually implement `Deserialize` on their structs.
This commit is contained in:
Paolo Barbolini
2020-05-02 17:32:33 +02:00
committed by Martin Algesten
parent 8f2d094cef
commit 0b69c595b6
5 changed files with 44 additions and 17 deletions

View File

@@ -15,7 +15,7 @@ all-features = true
[features] [features]
default = ["tls", "cookies"] default = ["tls", "cookies"]
json = ["serde_json"] json = ["serde", "serde_json"]
charset = ["encoding"] charset = ["encoding"]
tls = ["rustls", "webpki", "webpki-roots"] tls = ["rustls", "webpki", "webpki-roots"]
native-certs = ["rustls-native-certs"] native-certs = ["rustls-native-certs"]
@@ -34,5 +34,9 @@ rustls = { version = "0.17", optional = true, features = [] }
webpki = { version = "0.21", optional = true } webpki = { version = "0.21", optional = true }
webpki-roots = { version = "0.19", optional = true } webpki-roots = { version = "0.19", optional = true }
rustls-native-certs = { version = "0.3", optional = true } rustls-native-certs = { version = "0.3", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
encoding = { version = "0.2", optional = true } encoding = { version = "0.2", optional = true }
[dev-dependencies]
serde = { version = "1", features = ["derive"] }

View File

@@ -10,8 +10,6 @@ use encoding::EncoderTrap;
#[cfg(feature = "json")] #[cfg(feature = "json")]
use super::SerdeValue; use super::SerdeValue;
#[cfg(feature = "json")]
use serde_json;
/// The different kinds of bodies to send. /// The different kinds of bodies to send.
/// ///

View File

@@ -10,7 +10,7 @@ use crate::stream::Stream;
use crate::unit::Unit; use crate::unit::Unit;
#[cfg(feature = "json")] #[cfg(feature = "json")]
use serde_json; use serde::de::DeserializeOwned;
#[cfg(feature = "charset")] #[cfg(feature = "charset")]
use encoding::label::encoding_from_whatwg_label; use encoding::label::encoding_from_whatwg_label;
@@ -382,16 +382,23 @@ impl Response {
/// Example: /// Example:
/// ///
/// ``` /// ```
/// # use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Hello {
/// hello: String,
/// }
///
/// let resp = /// let resp =
/// ureq::get("https://ureq.s3.eu-central-1.amazonaws.com/hello_world.json") /// ureq::get("https://ureq.s3.eu-central-1.amazonaws.com/hello_world.json")
/// .call(); /// .call();
/// ///
/// let json = resp.into_json().unwrap(); /// let json = resp.into_json::<Hello>().unwrap();
/// ///
/// assert_eq!(json["hello"], "world"); /// assert_eq!(json.hello, "world");
/// ``` /// ```
#[cfg(feature = "json")] #[cfg(feature = "json")]
pub fn into_json(self) -> IoResult<serde_json::Value> { pub fn into_json<T: DeserializeOwned>(self) -> IoResult<T> {
let reader = self.into_reader(); let reader = self.into_reader();
serde_json::from_reader(reader).map_err(|e| { serde_json::from_reader(reader).map_err(|e| {
IoError::new( IoError::new(
@@ -723,15 +730,19 @@ mod tests {
#[test] #[test]
#[cfg(feature = "json")] #[cfg(feature = "json")]
fn parse_simple_json() { fn parse_simple_json() {
use serde::Deserialize;
#[derive(Deserialize)]
struct Hello {
hello: String,
}
let s = "HTTP/1.1 200 OK\r\n\ let s = "HTTP/1.1 200 OK\r\n\
\r\n\ \r\n\
{\"hello\":\"world\"}"; {\"hello\":\"world\"}";
let resp = s.parse::<Response>().unwrap(); let resp = s.parse::<Response>().unwrap();
let v = resp.into_json().unwrap(); let v = resp.into_json::<Hello>().unwrap();
let compare = "{\"hello\":\"world\"}" assert_eq!(v.hello, "world");
.parse::<serde_json::Value>()
.unwrap();
assert_eq!(v, compare);
} }
#[test] #[test]

View File

@@ -61,6 +61,13 @@ fn body_as_text() {
#[test] #[test]
#[cfg(feature = "json")] #[cfg(feature = "json")]
fn body_as_json() { fn body_as_json() {
use serde::Deserialize;
#[derive(Deserialize)]
struct Hello {
hello: String,
}
test::set_handler("/body_as_json", |_unit| { test::set_handler("/body_as_json", |_unit| {
test::make_response( test::make_response(
200, 200,
@@ -70,8 +77,8 @@ fn body_as_json() {
) )
}); });
let resp = get("test://host/body_as_json").call(); let resp = get("test://host/body_as_json").call();
let json = resp.into_json().unwrap(); let json = resp.into_json::<Hello>().unwrap();
assert_eq!(json["hello"], "world"); assert_eq!(json.hello, "world");
} }
#[test] #[test]

View File

@@ -17,6 +17,14 @@ fn tls_connection_close() {
#[cfg(feature = "json")] #[cfg(feature = "json")]
#[test] #[test]
fn agent_set_cookie() { fn agent_set_cookie() {
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Deserialize)]
struct HttpBin {
headers: HashMap<String, String>,
}
let agent = ureq::Agent::default().build(); let agent = ureq::Agent::default().build();
let cookie = ureq::Cookie::build("name", "value") let cookie = ureq::Cookie::build("name", "value")
.domain("httpbin.org") .domain("httpbin.org")
@@ -30,10 +38,9 @@ fn agent_set_cookie() {
assert_eq!(resp.status(), 200); assert_eq!(resp.status(), 200);
assert_eq!( assert_eq!(
"name=value", "name=value",
resp.into_json() resp.into_json::<HttpBin>()
.unwrap()
.get("headers")
.unwrap() .unwrap()
.headers
.get("Cookie") .get("Cookie")
.unwrap() .unwrap()
); );