I wonder whether there's a way to preserve the original String using serde_json? Consider this example:
#[derive(Debug, Serialize, Deserialize)]
struct User {
#[serde(skip)]
pub raw: String,
pub id: u64,
pub login: String,
}
{
"id": 123,
"login": "johndoe"
}
My structure would end up containing such values:
User {
raw: String::from(r#"{"id": 123,"login": "johndoe"}"#),
id: 1,
login: String::from("johndoe")
}
Currently, I'm doing that by deserializing into Value, then deserializing this value into the User structure and assigning Value to the raw field, but that doesn't seem right, perhaps there's a better way to do so?
This solution uses the RawValue type from serde_json to first get the original input string. Then a new Deserializer is created from that String to deserialize the User type.
This solution can work with readers, by using Box<serde_json::value::RawValue> as an intermediary type and it can also work with struct which borrow from the input, by using &'de serde_json::value::RawValue as the intermediary. You can test it in the solution by (un-)commenting the borrow field.
use std::marker::PhantomData;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(remote = "Self")]
struct User<'a> {
#[serde(skip)]
pub raw: String,
pub id: u64,
pub login: String,
// Test for borrowing input data
// pub borrow: &'a str,
#[serde(skip)]
pub ignored: PhantomData<&'a ()>,
}
impl serde::Serialize for User<'_> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Self::serialize(self, serializer)
}
}
impl<'a, 'de> serde::Deserialize<'de> for User<'a>
where
'de: 'a,
{
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
// Deserializing a `&'a RawValue` would also work here
// but then you loose support for deserializing from readers
let raw: Box<serde_json::value::RawValue> = Box::deserialize(deserializer)?;
// Use this line instead if you have a struct which borrows from the input
// let raw = <&'de serde_json::value::RawValue>::deserialize(deserializer)?;
let mut raw_value_deserializer = serde_json::Deserializer::from_str(raw.get());
let mut user =
User::deserialize(&mut raw_value_deserializer).map_err(|err| D::Error::custom(err))?;
user.raw = raw.get().to_string();
Ok(user)
}
}
fn main() {
// Test serialization
let u = User {
raw: String::new(),
id: 456,
login: "USERNAME".to_string(),
// Test for borrowing input data
// borrow: "foobar",
ignored: PhantomData,
};
let json = serde_json::to_string(&u).unwrap();
println!("{}", json);
// Test deserialization
let u2: User = serde_json::from_str(&json).unwrap();
println!("{:#?}", u2);
}
Test on the Playground.
Related
i have a struct like this
pub struct CmdRequestBuilder {
pub method: String,
pub url: String,
pub query: Option<Vec<String>>,
pub body: Option<Vec<String>>,
pub headers: Option<Vec<String>>,
pub include_headers: bool,
pub include_body: bool,
pub status: i8,
}
as you can see the headers attribute a vector of strings and when i want to loop over it i face an error (bellow)
pub fn build(&self) -> reqwest::RequestBuilder {
let mut header_map = HeaderMap::new();
let mut builder = reqwest::Client::new().request(self.get_method(), self.url.as_str());
// error here
let headers = self.headers.as_ref().unwrap();
headers.into_iter().for_each(|header| {
let mut header_split = header.split(":");
let key = header_split.next().unwrap();
let value = header_split.next().unwrap();
let k = reqwest::header::HeaderName::from_static(key);
header_map.insert(k, value.parse().unwrap());
});
builder = builder.headers(header_map);
builder
}
error : self has an anonymous lifetime '_ but it needs to satisfy a 'static lifetime requirement
My first question is: Will CmdRequestBuilder::build() be called more than once on a single object?
I suspect that the answer is "no", and therefore the first fix would be to make build consume self instead of borrowing it.
There are a couple more things wrong with your code:
// `headers` is a reference.
// I am uncertain why you want this do be a reference, though.
let headers = self.headers.as_ref().unwrap();
// `into_iter()` consumes `headers`.
// Which is impossible the way it is written right now, as
// references cannot be consumed. You need an owned object for that.
headers.into_iter().for_each(|header| {
// .split creates (temporary) references
let mut header_split = header.split(":");
let key = header_split.next().unwrap();
let value = header_split.next().unwrap();
// from_static requires static references, not temporary ones
let k = reqwest::header::HeaderName::from_static(key);
header_map.insert(k, value.parse().unwrap());
});
Solution
change &self to self
fix as_ref() and into_iter() by removing as_ref()
make the header names static. This one is a little trickier, because I'm quite certain that the values aren't static; so it would be easiest to split headers into key and value pairs. Whether you use Vec<(&'static str, String)> or HashMap<&'static str, String> depends on your preference, I'll go with the HashMap.
This is probably what you wanted:
use std::collections::HashMap;
use reqwest::header::HeaderMap;
use reqwest::Method;
pub struct CmdRequestBuilder {
pub method: String,
pub url: String,
pub query: Option<Vec<String>>,
pub body: Option<Vec<String>>,
pub headers: Option<HashMap<&'static str, String>>,
pub include_headers: bool,
pub include_body: bool,
pub status: i8,
}
impl CmdRequestBuilder {
fn get_method(&self) -> Method {
todo!()
}
pub fn build(self) -> reqwest::RequestBuilder {
let mut header_map = HeaderMap::new();
let mut builder = reqwest::Client::new().request(self.get_method(), self.url.as_str());
// error here
let headers = self.headers.unwrap();
headers.into_iter().for_each(|(key, value)| {
header_map.insert(key, value.parse().unwrap());
});
builder = builder.headers(header_map);
builder
}
}
There will be other problems that you will have to solve now that headers is an Option<HashMap<&'static str, String>>, but one step at a time.
If you don't want to consume self in build(), then you could write this instead:
use std::collections::HashMap;
use reqwest::header::HeaderMap;
use reqwest::Method;
pub struct CmdRequestBuilder {
pub method: String,
pub url: String,
pub query: Option<Vec<String>>,
pub body: Option<Vec<String>>,
pub headers: Option<HashMap<&'static str, String>>,
pub include_headers: bool,
pub include_body: bool,
pub status: i8,
}
impl CmdRequestBuilder {
fn get_method(&self) -> Method {
todo!()
}
pub fn build(&self) -> reqwest::RequestBuilder {
let mut header_map = HeaderMap::new();
let mut builder = reqwest::Client::new().request(self.get_method(), self.url.as_str());
// error here
let headers = self.headers.as_ref().unwrap();
headers.iter().for_each(|(key, value)| {
header_map.insert(*key, value.parse().unwrap());
});
builder = builder.headers(header_map);
builder
}
}
If you don't want to include a &'static str in your headers member, then the answer is: what you are trying to do is impossible.
There simply is no way to convert a String into a &'static str. &'static str means the variable is available for the entire program duration, which String by definition isn't.
my "main" file
mod router;
mod student;
use std::sync::Arc;
use crate::router::init_router;
use crate::router::Memory;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
let repo = Arc::new(Memory::repository());
let host = std::env::var("SERVER.HOST").unwrap_or("127.0.0.1".to_string());
let port = std::env::var("SERVER.PORT").unwrap_or("8080".to_string());
let url = format!("{}:{}", host, port);
println!("url: {}", &url);
HttpServer::new(move || {
App::new()
.data(repo.clone()) // shared
.configure(init_router)
})
.bind(&url)?
.run()
.await
}
my file: "router.rs"
use std::sync::Arc;
use crate::student::Student;
use actix_web::{get, web, HttpResponse, Responder};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct Memory {
pub students: Vec<Student>,
}
impl Memory {
fn new() -> Self {
Memory {
students: Vec::new(),
}
}
pub fn repository() -> Self{
Self {
students: vec![
{Student::new("1".to_string(), "Daniel".to_string(), 19)},
{Student::new("2".to_string(), "Lucia".to_string(), 17)},
{Student::new("3".to_string(), "Juan".to_string(), 14)}
]
}
}
}
#[get("/studen/list/all")]
async fn list_all(repo: web::Data<Arc<Memory>>) -> impl Responder {
HttpResponse::Ok().json(repo)
}
pub fn init_router(config: &mut web::ServiceConfig) {
config.service(list_all);
}
and my file: "student.rs"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct Student{
pub id: String,
pub nombre: String,
pub calificacion: u32,
}
impl Student{
pub fn new(id: String, nombre: String, calificacion: u32) -> Self{
Self{id, nombre, calificacion}
}
}
so I want to show all students in a json via the following path: 127.0.0.1:3000/student/list/all
but i have the following error
| HttpResponse::Ok().json(repo)
| ^^^^ the trait Serialize is not implemented for Data<Arc<Memory>>
I still can't pass this data by parameter of a GET method, I need a little help please to display it in json. It is necessary because later I will need this data by parameter to add or remove.
(Note that you typed #[get("/studen/list/all")] instead of #[get("/student/list/all")] in your original code.)
You don't want to serialize the Data<Arc<Memory>> to JSON, you only want to serialize the Memory. To do so, you can dereference the Data<Arc<Memory>> to get an Arc<Arc<Memory>>, then dereference the Arcs twice to get a Memory. Then, you add a reference as to not require cloning the resulting Memory. So, you replace
HttpResponse::Ok().json(repo)
with
HttpResponse::Ok().json(&***repo)
However, Data is already a wrapper around an Arc, so no Arc<Memory> in the is required. If you want to edit the Memory eventually, you'll have to add a Mutex inside. So, you'll have to obtain a lock on repo before serializing it.
I'm learning Rust, but I'm not sure about the most elegant or "rusty" way of doing some things:
I'm retrieving data from an API that, on some endpoints returns a JSON object ({ value: "resource A" }), but in other occasions, it returns a JSON object wrapped by another object ({ error: false, data: { value: "resource A" } }).
I'm using Restson to retrieve that data.
My question is: what is the most elegant way to deal with different responses? I don't know how to use some kind of abstract Response that could accept both kind of JSON responses.
I mean, in this case I'm implementing 2 traits, but both of them have the same content, so, to me, it smells like there is something wrong there.
This is a simplified example, so typos could exist:
use restson::{RestPath, RestClient, Error};
#[derive(Debug, Serialize, Deserialize)]
struct Response<T> {
error: bool,
data: T
}
#[derive(Debug, Serialize, Deserialize)]
struct ResourceA {
value: String,
}
// HERE: How do I remove this duplication?
impl<'a> RestPath<(&'a str, String)> for ResourceA {
fn get_path(params: (i8, String, &str)) -> Result<String, Error> {
let (resource, id) = params;
Ok(format!("{}/{}", resource, id))
}
}
impl<'a, T> RestPath<(&'a str, String)> for Response<T> {
fn get_path(params: (&str, String)) -> Result<String, Error> {
let (resource, id) = params;
Ok(format!("{}/{}", resource, id))
}
}
pub struct Client {
client: RestClient,
}
impl Client {
pub fn new() -> Client {
Client {
client: RestClient::new("http://my.client").unwrap(),
}
}
pub fn get_resource_a(&mut self, id: String) -> ResourceA {
let params = ("a", id);
self.client.get(params).unwrap()
}
pub fn get_resource_a2(&mut self, id: String) -> ResourceA {
let params = ("a2", id);
let response: Response<ResourceA> = self.api_client.get(params).unwrap();
response.data
}
}
You have a response with two variants, so an enum based solution may be considered:
#[derive(Debug, Serialize, Deserialize)]
struct ResourceA {
value: String,
}
#[derive(Debug, Serialize, Deserialize]
#[serde(untagged)]
pub enum Response {
ErrAndValue{error: bool, data: ResourceA},
Simple(ResourceA),
}
I've used the untagged annotation to conform the json format:
{ value: "resource A" }
{ error: false, data: { value: "resource A" } }
Then your RestPath impl reduce to:
impl<'a> RestPath<(&'a str, String)> for Response {
fn get_path(params: (&str, String)) -> Result<String, Error> {
let (resource, id) = params;
Ok(format!("{}/{}", resource, id))
}
}
I want to serialize a HashMap with structs as keys:
use serde::{Deserialize, Serialize}; // 1.0.68
use std::collections::HashMap;
fn main() {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
struct Foo {
x: u64,
}
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
x: HashMap<Foo, f64>,
}
let mut p = Bar { x: HashMap::new() };
p.x.insert(Foo { x: 0 }, 0.0);
let serialized = serde_json::to_string(&p).unwrap();
}
This code compiles, but when I run it I get an error:
Error("key must be a string", line: 0, column: 0)'
I changed the code:
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
x: HashMap<u64, f64>,
}
let mut p = Bar { x: HashMap::new() };
p.x.insert(0, 0.0);
let serialized = serde_json::to_string(&p).unwrap();
The key in the HashMap is now a u64 instead of a string. Why does the first code give an error?
You can use serde_as from the serde_with crate to encode the HashMap as a sequence of key-value pairs:
use serde_with::serde_as; // 1.5.1
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
#[serde_as(as = "Vec<(_, _)>")]
x: HashMap<Foo, f64>,
}
Which will serialize to (and deserialize from) this:
{
"x":[
[{"x": 0}, 0.0],
[{"x": 1}, 0.0],
[{"x": 2}, 0.0]
]
}
There is likely some overhead from converting the HashMap to Vec, but this can be very convenient.
According to JSONs specification, JSON keys must be strings. serde_json uses fmt::Display in here, for some non-string keys, to allow serialization of wider range of HashMaps. That's why HashMap<u64, f64> works as well as HashMap<String, f64> would. However, not all types are covered (Foo's case here).
That's why we need to provide our own Serialize implementation:
impl Display for Foo {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", self.x)
}
}
impl Serialize for Bar {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.x.len()))?;
for (k, v) in &self.x {
map.serialize_entry(&k.to_string(), &v)?;
}
map.end()
}
}
(playground)
I've found the bulletproof solution 😃
Extra dependencies not required
Compatible with HashMap, BTreeMap and other iterable types
Works with flexbuffers
The following code converts a field (map) to the intermediate Vec representation:
pub mod vectorize {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::iter::FromIterator;
pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: IntoIterator<Item = (&'a K, &'a V)>,
K: Serialize + 'a,
V: Serialize + 'a,
{
let container: Vec<_> = target.into_iter().collect();
serde::Serialize::serialize(&container, ser)
}
pub fn deserialize<'de, T, K, V, D>(des: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromIterator<(K, V)>,
K: Deserialize<'de>,
V: Deserialize<'de>,
{
let container: Vec<_> = serde::Deserialize::deserialize(des)?;
Ok(T::from_iter(container.into_iter()))
}
}
To use it just add the module's name as an attribute:
#[derive(Debug, Serialize, Deserialize)]
struct MyComplexType {
#[serde(with = "vectorize")]
map: HashMap<MyKey, String>,
}
The remained part if you want to check it locally:
use anyhow::Error;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyKey {
one: String,
two: u16,
more: Vec<u8>,
}
#[derive(Debug, Serialize, Deserialize)]
struct MyComplexType {
#[serde(with = "vectorize")]
map: HashMap<MyKey, String>,
}
fn main() -> Result<(), Error> {
let key = MyKey {
one: "1".into(),
two: 2,
more: vec![1, 2, 3],
};
let mut map = HashMap::new();
map.insert(key.clone(), "value".into());
let instance = MyComplexType { map };
let serialized = serde_json::to_string(&instance)?;
println!("JSON: {}", serialized);
let deserialized: MyComplexType = serde_json::from_str(&serialized)?;
let expected_value = "value".to_string();
assert_eq!(deserialized.map.get(&key), Some(&expected_value));
Ok(())
}
And on the Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bf1773b6e501a0ea255ccdf8ce37e74d
While all provided answers will fulfill the goal of serializing your HashMap to json they are ad hoc or hard to maintain.
One correct way to allow a specific data structure to be serialized with serde as keys in a map, is the same way serde handles integer keys in HashMaps (which works): They serialize the value to String. This has a few advantages; namely
Intermediate data-structure omitted,
no need to clone the entire HashMap,
easier maintained by applying OOP concepts, and
serialization usable in more complex structures such as MultiMap.
This can be done by manually implementing Serialize and Deserialize for your data-type.
I use composite ids for maps.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Proj {
pub value: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Doc {
pub proj: Proj,
pub value: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Sec {
pub doc: Doc,
pub value: u32,
}
So now manually implementing serde serialization for them is kind of a hassle, so instead we delegate the implementation to the FromStr and From<Self> for String (Into<String> blanket) traits.
impl From<Doc> for String {
fn from(val: Doc) -> Self {
format!("{}{:08X}", val.proj, val.value)
}
}
impl FromStr for Doc {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match parse_doc(s) {
Ok((_, p)) => Ok(p),
Err(e) => Err(e.to_string()),
}
}
}
In order to parse the Doc we make use of nom. The parse functionality below is explained in their examples.
fn is_hex_digit(c: char) -> bool {
c.is_digit(16)
}
fn from_hex8(input: &str) -> Result<u32, std::num::ParseIntError> {
u32::from_str_radix(input, 16)
}
fn parse_hex8(input: &str) -> IResult<&str, u32> {
map_res(take_while_m_n(8, 8, is_hex_digit), from_hex8)(input)
}
fn parse_doc(input: &str) -> IResult<&str, Doc> {
let (input, proj) = parse_proj(input)?;
let (input, value) = parse_hex8(input)?;
Ok((input, Doc { value, proj }))
}
Now we need to hook up self.to_string() and str::parse(&str) to serde we can do this using a simple macro.
macro_rules! serde_str {
($type:ty) => {
impl Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s: String = self.clone().into();
serializer.serialize_str(&s)
}
}
impl<'de> Deserialize<'de> for $type {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
paste! {deserializer.deserialize_string( [<$type Visitor>] {})}
}
}
paste! {struct [<$type Visitor>] {}}
impl<'de> Visitor<'de> for paste! {[<$type Visitor>]} {
type Value = $type;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("\"")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::parse(v) {
Ok(id) => Ok(id),
Err(_) => Err(serde::de::Error::custom("invalid format")),
}
}
}
};
}
Here we are using paste to interpolate the names. Beware that now the struct will always serialize as defined above. Never as a struct, always as a string.
It is important to implement fn visit_str instead of fn visit_string because visit_string defers to visit_str.
Finally, we have to call the macro for our custom structs
serde_str!(Sec);
serde_str!(Doc);
serde_str!(Proj);
Now the specified types can be serialized to and from string with serde.
I'm trying to do the equivalent of Ruby's Enumerable.collect() in Rust.
I have an Option<Vec<Attachment>> and I want to create a Option<Vec<String>> from it, with String::new() elements in case of None guid.
#[derive(Debug)]
pub struct Attachment {
pub guid: Option<String>,
}
fn main() {
let ov: Option<Vec<Attachment>> =
Some(vec![Attachment { guid: Some("rere34r34r34r34".to_string()) },
Attachment { guid: Some("5345345534rtyr5345".to_string()) }]);
let foo: Option<Vec<String>> = match ov {
Some(x) => {
x.iter()
.map(|&attachment| attachment.guid.unwrap_or(String::new()))
.collect()
}
None => None,
};
}
The error in the compiler is clear:
error[E0277]: the trait bound `std::option::Option<std::vec::Vec<std::string::String>>: std::iter::FromIterator<std::string::String>` is not satisfied
--> src/main.rs:15:18
|
15 | .collect()
| ^^^^^^^ the trait `std::iter::FromIterator<std::string::String>` is not implemented for `std::option::Option<std::vec::Vec<std::string::String>>`
|
= note: a collection of type `std::option::Option<std::vec::Vec<std::string::String>>` cannot be built from an iterator over elements of type `std::string::String`
If I remember what I've read from the documentation so far, I cannot implement traits for struct that I don't own.
How can I do this using iter().map(...).collect() or maybe another way?
You should read and memorize all of the methods on Option (and Result). These are used so pervasively in Rust that knowing what is present will help you immensely.
For example, your match statement is Option::map.
Since you never said you couldn't transfer ownership of the Strings, I'd just do that. This will avoid any extra allocation:
let foo: Option<Vec<_>> =
ov.map(|i| i.into_iter().map(|a| a.guid.unwrap_or_else(String::new)).collect());
Note we don't have to specify the type inside the Vec; it can be inferred.
You can of course introduce functions to make it cleaner:
impl Attachment {
fn into_guid(self) -> String {
self.guid.unwrap_or_else(String::new)
}
}
// ...
let foo: Option<Vec<_>> = ov.map(|i| i.into_iter().map(Attachment::into_guid).collect());
If you don't want to give up ownership of the String, you can do the same concept but with a string slice:
impl Attachment {
fn guid(&self) -> &str {
self.guid.as_ref().map_or("", String::as_str)
}
}
// ...
let foo: Option<Vec<_>> = ov.as_ref().map(|i| i.iter().map(|a| a.guid().to_owned()).collect());
Here, we have to use Option::as_ref to avoid moving the guid out of the Attachment, then convert to a &str with String::as_str, providing a default value. We likewise don't take ownership of the Option of ov, and thus need to iterate over references, and ultimately allocate new Strings with ToOwned.
Here is a solution that works:
#[derive(Debug)]
pub struct Attachment {
pub guid: Option<String>,
}
fn main() {
let ov: Option<Vec<Attachment>> =
Some(vec![Attachment { guid: Some("rere34r34r34r34".to_string()) },
Attachment { guid: Some("5345345534rtyr5345".to_string()) }]);
let foo: Option<Vec<_>> = ov.map(|x|
x.iter().map(|a| a.guid.as_ref().unwrap_or(&String::new()).clone()).collect());
println!("{:?}", foo);
}
One of the issues with the above code is stopping the guid being moved out of the Attachment and into the vector. My example calls clone to move cloned instances into the vector.
This works, but I think it looks nicer wrapped in a trait impl for Option<T>. Perhaps this is a better ... option ...:
trait CloneOr<T, U>
where U: Into<T>,
T: Clone
{
fn clone_or(&self, other: U) -> T;
}
impl<T, U> CloneOr<T, U> for Option<T>
where U: Into<T>,
T: Clone
{
fn clone_or(&self, other: U) -> T {
self.as_ref().unwrap_or(&other.into()).clone()
}
}
#[derive(Debug)]
pub struct Attachment {
pub guid: Option<String>,
}
fn main() {
let ov: Option<Vec<Attachment>> =
Some(vec![Attachment { guid: Some("rere34r34r34r34".to_string()) },
Attachment { guid: Some("5345345534rtyr5345".to_string()) },
Attachment { guid: None }]);
let foo: Option<Vec<_>> =
ov.map(|x| x.iter().map(|a| a.guid.clone_or("")).collect());
println!("{:?}", foo);
}
Essentially the unwrapping and cloning is hidden behind a trait implementation that attaches to Option<T>.
Here it is running on the playground.