I am trying to filter a vector of structs by a struct attribute and then return another attribute. However I'm not quite sure how to elegantly extract the value.
My function will take in the name of a budget and then I want to return the id of that budget by searching through a list of Budgets.
Is there a cleaner way to do this in one pass without allocating a new struct?
// Budget structs
#[derive(Serialize, Deserialize, Debug)]
pub struct Budgets {
pub budgets: Vec<Budget>
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Budget {
pub id: String,
name: String,
last_modified_on: String,
first_month: String,
last_month: String,
date_format: DateFormat,
currency_format: Option<CurrencyFormat>,
accounts: Option<Accounts>,
}
impl Adapter {
fn get_budget_id(&self, budget_name: &str) -> anyhow::Result<String> {
let budget_data = self.get_budgets()?;
let budget = budget_data.data.budgets.into_iter()
.filter(|b| b.name == budget_name);
//.collect::<Budgets>() <-- FromIterator not implemented
//.remove(0);
let b = Budgets {
budgets: Vec::from_iter(budget)
};
if b.budgets.is_empty() {
Err(anyhow::anyhow!("Error, no budget with {} name found", budget_name))
} else {
Ok(b.budgets[0].id.clone())
}
}
}
After some deeper digging into the docs and suggestions on here, this was my final solution:
fn get_budget_id(&self, budget_name: &str) -> anyhow::Result<String> {
let budget_data = self.get_budgets()?;
if let Some(budget) = budget_data.data.budgets.into_iter()
.find(|b| b.name == budget_name) {
Ok(budget.id)
} else {
Err(anyhow::anyhow!("Error, no budget with name {} found", budget_name))
}
}
let budget:Budget = budget_data.data.budgets.into_iter()
.find(|b| b.name == budget_name)
.ok_or(anyhow::anyhow!("Error, no budget with {} name found", budget_name))?;
Related
An API with this internally tagged field structure, with "topic" being the tag:
{
"topic": "Car"
"name": "BMW"
"HP": 250
}
This can be deserialized with
#[derive(Serialize, Deserialize)]
#[serde(tag = "topic")]
pub enum catalog {
CarEntry(Car),
... (other types)
}
#[derive(Serialize, Deserialize)]
pub struct Car {
pub name: String
pub HP: i32
}
It turns out that instead of reporting the topic as just Car, the API actually sends Car.product1 or Car.product2 etc.
This breaks the deserialization, because the deserializer doesn't know what the type is based on the string. Is there a way to supply a function to chop off the type string so that the correct model is found?
I don't think serde provides a way to mangle the tag before using it (at least I don't see anything relevant). And the generated serializers for tagged enums are relatively complex, with internal caching if the tag isn't the first field, and whatnot, so I wouldn't want to reproduce that in a custom deserializer.
The cheapest (but not necessarily most efficient) shot at this is to deserialize to serde_json::Value first, manually process the tag, and then deserialize the serde_json::Values to whatever struct you want.
Do that in a custom deserializer, and it starts looking reasonable:
impl<'de> Deserialize<'de> for Catalog {
fn deserialize<D>(d: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
use serde_json::{Map, Value};
#[derive(Deserialize)]
struct Pre {
topic: String,
#[serde(flatten)]
data: Map<String, Value>,
}
let v = Pre::deserialize(d)?;
// Now you can mangle Pre any way you want to get your final structs.
match v.topic.as_bytes() {
[b'C', b'a', b'r', b'.', _rest # ..] => Ok(Catalog::CarEntry(
serde_json::from_value(v.data.into()).map_err(de::Error::custom)?,
)),
[b'B', b'a', b'r', b'.', _rest # ..] => Ok(Catalog::BarEntry(
serde_json::from_value(v.data.into()).map_err(de::Error::custom)?,
)),
_ => return Err(de::Error::unknown_variant(&v.topic, &["Car.…", "Bar.…"])),
}
}
}
Playground
Btw, what do you want to do with the suffix of topic? Throw it away? How do you plan on handling serialization if you do throw it away?
You can directly use enum instead of defining extra struct type.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "topic")]
pub enum Catalog {
Car { name: String, hp: i32 }
}
fn main() {
let car = Catalog::Car { name: String::from("BMW"), hp: 2000 };
// Convert the Car to a JSON string.
let serialized = serde_json::to_string(&car).unwrap();
// Prints serialized = {"topic":"Car","name":"BMW","hp":2000}
println!("serialized = {}", serialized);
// Convert the JSON string back to a Car.
let deserialized: Catalog = serde_json::from_str(&serialized).unwrap();
// Prints deserialized = Car { name: "BMW", hp: 2000 }
println!("deserialized = {:?}", deserialized);
}
Playground
You can use #[serde(rename()] to rename type in output
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "topic")]
pub enum Catalog {
#[serde(rename(serialize = "Car", deserialize = "CarEntry"))]
CarEntry(Car),
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Car {
pub name: String,
pub hp: i32
}
fn main() {
let car = Car { name: String::from("BMW"), hp: 2000 };
let catalog = Catalog::CarEntry(car);
// Convert the Car to a JSON string.
let serialized = serde_json::to_string(&catalog).unwrap();
// Prints serialized = {"topic":"Car","name":"BMW","hp":2000}
println!("serialized = {}", serialized);
// Convert the JSON string back to a Car.
let deserialized: Car = serde_json::from_str(&serialized).unwrap();
// Prints deserialized = Car { name: "BMW", hp: 2000 }
println!("deserialized = {:?}", deserialized);
}
Playground
I have a data model that I would like to be deserialized from "camelCase" to the rust standard "snake_case" when reading from a source, X. But I'd like to leave it in "snake_case" when reading or writing to another source, Y.
For example, the following code,
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Data {
foo_bar: String,
hello_word: String,
}
can only be encoded and decoded in camel case. Even if I manually defined my Serialize and Deserialize implementations, I can't define multiple for the same struct. I could define a second struct that's a copy/paste of the other and then derive but that method would get tedious with multiple large structs. What I would really like to do is specify that rename_all attribute at run-time. But I'm not seeing any way to do that in serde's API.
I think the best way sigh is to just write out one struct Data_ per #[serde(rename_all = ...)], then write one additional struct Data that will be the in-memory representation (which won't be serializable, to remove ambiguity), then implement From in both directions for the Data_s and Data so that they're interconvertible.
Thankfully, we can use a macro so that we only have to specify the fields once. (It is incredibly disgusting nonetheless.)
This playground available here.
use serde::{Deserialize, Serialize}; // 1.0.130
use serde_json; // 1.0.69
macro_rules! interconvertible {
($T:ident <-> $U:ident, $($field_name:ident),*) => {
impl From<$T> for $U {
fn from(t: $T) -> Self {
let $T { $($field_name),* } = t;
Self { $($field_name),* }
}
}
impl From<$U> for $T {
fn from(u: $U) -> Self {
let $U { $($field_name),* } = u;
Self { $($field_name),* }
}
}
};
}
macro_rules! create_data_structs {
($($field_name:ident: $field_type:ty),* $(,)?) => {
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct DataX {
$($field_name: $field_type),*
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
struct DataY {
$($field_name: $field_type),*
}
#[derive(Debug)]
struct Data {
$($field_name: $field_type),*
}
interconvertible!(DataX <-> Data, $($field_name),*);
interconvertible!(DataY <-> Data, $($field_name),*);
}
}
create_data_structs!(foo_bar: String, hello_world: String);
fn main() -> serde_json::Result<()> {
let x1: DataX = serde_json::from_str(r#"{"fooBar": "a", "helloWorld": "b"}"#)?;
let y1: DataY = serde_json::from_str(r#"{"foo_bar": "a", "hello_world": "b"}"#)?;
println!("{:?}, {:?}", x1, y1);
let x2: Data = x1.into();
let y2: Data = y1.into();
println!("{:?}, {:?}", x2, y2);
let x_string = serde_json::to_string(&DataX::from(x2))?;
let y_string = serde_json::to_string(&DataY::from(y2))?;
println!("{:?}, {:?}", x_string, y_string);
Ok(())
}
The output is:
DataX { foo_bar: "a", hello_world: "b" }, DataY { foo_bar: "a", hello_world: "b" }
[Data { foo_bar: "a", hello_world: "b" }, Data { foo_bar: "a", hello_world: "b" }]
"{\"fooBar\":\"a\",\"helloWorld\":\"b\"}", "{\"foo_bar\":\"a\",\"hello_world\":\"b\"}"
Since I'm only every decoding from source X I can utilize the #[serde(alias = ???)] macro. So my above use case would be
#[derive(Serialize, Deserialize)]
struct Data {
#[serde(alias="fooBar")]
foo_bar: String,
#[serde(alias="helloWorld")]
hello_word: String,
}
It's still a little tedious but better than an intermediate struct. It won't work though if I want to decode or encode to different cases.
(I'm not going to mark this as an answer because it's a work-around for my specific use case. If anyone has a more generic solution feel free to answer.)
I have two FnvHashMap which is declared like first: FnvHashMap<(i32, i32), Employee>
second: FnvHashMap<(i32, i32), Employee>
Where Employee is
pub struct Employee {
pub emp_id: i32,
pub lang_id: i32,
pub dept_id: f64,
pub description: String,
}
I need to iterate through 'first' FnvHashMap and see if there is a matching record(emp_id and lang_id) in 'second' FnvHashMap
I may not need to consider dept_id and description
Thanks in Advance.
New code after implementing nested loop
for (_, employee1) in &first {
for (_, employee2) in &second {
if employee1.emp_id == employee2.emp_id && employee1.lang_id == employee2.lang_id {
values.push(OldNew {
old: employee2,
new: employee1,
});
}
}
}
let new = first
.into_iter()
.filter(|(a, _)| !old.contains_key(a))
.map(|(_, a)| a)
.collect();
let deleted = second
.iter()
.filter(|(a, _)| !new.contains_key(a))
.map(|(&a, _)| a)
.collect();
Changes {
deleted,
new,
values,
}
pub struct Changes<T, I> {
pub deleted: Vec<I>,
pub new: Vec<T>,
pub values: Vec<OldNew<T>>,
}
expected struct `organization::models::employee_stat::Employee`, found `&organization::models::employee_stat::Employee`
Simply make two nested for loops to iterate through both maps and then compare the values you need from the two iterations of the loops, for example
for (_, employee1) in &first {
for (_, employee2) in &second {
if employee1.emp_id == employee2.emp_id && employee1.lang_id == employee2.lang_id {
/* Code here to run if a matching value is found */
}
}
}
I am a Rust beginner and was wondering how to access a struct's fields dynamically:
use std::collections::HashMap;
struct User {
email: String,
name: String,
}
impl User {
fn new(attributes: &HashMap<String,String>) -> User {
let mut model = User {
email: "",
name: "",
};
for (attr_name,attr_value) in attributes {
// assign value "attr_value" to attribute "attr_name"
// no glue how to do this
// in php would be: $model->{$attr_name} = $attr_value;
model.*attr_name *= attr_value;
}
model;
}
}
fn main() {
let mut map: HashMap::new();
map.insert("email",String::from("foo#bar.de"));
map.insert("name",String::from("John doe"));
user_model = User::new(&map);
println!("{:?}",user_model);
}
How it is possible to initialize a struct by given HashMap?
Unless you change your User to contain a HashMap then Rust can't do that kind of "magic" (or it will require some proc macro usage, which is not beginner friendly).
Instead you can use a match, and match all the keys and update the User fields:
for (attr_name, attr_value) in attributes {
match attr_name {
"email" => model.email = attr_value.clone(),
"name" => model.name = attr_value.clone(),
_ => {}
}
}
However, instead of having empty Strings, I'd suggest using Option<String>.
struct User {
email: Option<String>,
name: Option<String>,
}
Then you can simplify your whole new method to just:
fn new(attributes: &HashMap<String, String>) -> User {
User {
email: attributes.get("email").cloned(),
name: attributes.get("name").cloned(),
}
}
Since you have some mixed String and &'static str usage, along with Debug not being implemented. Then here is the complete example:
use std::collections::HashMap;
#[derive(Debug)]
struct User {
email: Option<String>,
name: Option<String>,
}
impl User {
fn new(attributes: &HashMap<String, String>) -> User {
User {
email: attributes.get("email").cloned(),
name: attributes.get("name").cloned(),
}
}
}
fn main() {
let mut map = HashMap::new();
map.insert(String::from("email"), String::from("foo#bar.de"));
map.insert(String::from("name"), String::from("John doe"));
let user_model = User::new(&map);
println!("{:?}", user_model);
}
I want to iterate over over the fields of a struct and access its respective value for each iteration:
#[derive(Default, Debug)]
struct A {
foo: String,
bar: String,
baz: String
}
fn main() {
let fields = vec!["foo", "bar", "baz"];
let a: A = Default::default();
for field in fields {
let value = a[field] // this doesn't work
}
}
How can I access a field by variable?
Rust doesn't have any way of iterating directly over its fields. You should instead use a collection type such as Vec, array or one of the collections in std::collections if your data semantically represents a collection of some sort.
If you still feel the need to iterate over the fields, perhaps you need to re-consider your approach to your task and see if there isn't a more idiomatic/proper way to accomplish it
By using pattern matching, you can iterate over its fields.
#[derive(Default, Debug)]
struct A {
foo: String,
bar: String,
baz: String
}
impl A {
fn get(&self, field_string: &str) -> Result<&String, String> {
match field_string {
"foo" => Ok(&self.foo),
"bar" => Ok(&self.bar),
"baz" => Ok(&self.baz),
_ => Err(format!("invalid field name to get '{}'", field_string))
}
}
}
fn main() {
let fields = vec!["foo", "bar", "baz"];
let a = A {
foo: "value_of_foo".to_string(),
bar: "value_of_bar".to_string(),
baz: "value_of_baz".to_string()
};
for field in fields {
let value = a.get(field).unwrap();
println!("{:?}", value);
}
}
returns
"value_of_foo"
"value_of_bar"
"value_of_baz"
I am now writing a macro that implements such codes automatically for any struct, although there may be some bugs.
field_accessor (https://github.com/europeanplaice/field_accessor).
Cargo.toml
[dependencies]
field_accessor = "0"
use field_accessor::FieldAccessor;
#[derive(Default, Debug, FieldAccessor)]
struct A {
foo: String,
bar: String,
baz: String
}
fn main() {
let a = A {
foo: "value_of_foo".to_string(),
bar: "value_of_bar".to_string(),
baz: "value_of_baz".to_string()
};
for field in a.getstructinfo().field_names.iter() {
let value = a.get(field).unwrap();
println!("{:?}", value);
}
}
It also returns
"value_of_foo"
"value_of_bar"
"value_of_baz"
Based on the answer of sshashank124 I came to the conclusion that I should use an Hashmap instead of a struct:
fn main() {
let mut B = HashMap::new();
B.insert("foo", 1);
B.insert("bar", 2);
B.insert("baz", 3);
let fields = vec!["foo", "bar", "baz"];
for &field in &fields {
let value = B.get(field);
}
}