Best way to mapping Vec of fields to struct

Is there a good way to map a Vec containing the fields of a struct to a struct. I have something like F and I want to convert it to S

struct F{
  k: String,
  v_s: Option<String>,
  v_b: Option<bool>,
}
struct S {
  a: String,
  b: bool,
}
let vals = vec![F{k:"a",v_s:Some("1"),v_b:None}, F{k:"b",v_s:None,v_b:Some(true)};

// what I want is
S{a: "1", b: true}

For reference, bellow is the actual code and my WIP.

#[macro_use]
extern crate anyhow;

use anyhow::Result;
use rusoto_rds_data::{ColumnMetadata, Field};
use std::convert::TryFrom;

#[derive(Debug)]
pub struct Job {
  pub id: i32,
}

impl TryFrom<(Vec<ColumnMetadata>, Vec<Field>)> for Job {
  type Error = anyhow::Error;
  fn try_from((columns, fields): (Vec<ColumnMetadata>, Vec<Field>)) -> Result<Self, Self::Error> {
    let mut rslt = Job { id: 1 };

    fn perform_map(acc: &mut Job, column: &ColumnMetadata, field: Field) -> Result<Job> {
      match (acc, column, field) {
        (acc, ColumnMetadata { name: Some(n), .. }, v) if "id" == n => {
          acc.id = v.long_value.ok_or(anyhow!("Invalid Id field"))? as i32;
          Ok(*acc)
        }
        (_, _, _) => Err(anyhow!("invalid field")),
      }
    }
    let jb = columns
      .iter()
      .zip(fields)
      .fold(Ok(rslt), |acc, (column, field)| {
        if let Ok(job) = acc {
          perform_map(job.borrow_mut(), column, field)
        } else {
          acc
        }
      });
    Ok(rslt)
  }
}

fn main() -> Result<()> {
  let from = (
    vec![ColumnMetadata {
      name: Some(String::from("id")),
      ..Default::default()
    }],
    vec![Field {
      long_value: Some(1),
      ..Default::default()
    }],
  );
  Ok(println!("{:?}", Job::try_from(from)))
}

Any advice would be greatly appreciated.

The easiest approach I see is to make an SBuilder struct that has all of the same fields as S but wrapped in an Option. Then, you can write a FromIterator<Item=F> for SBuilder implementation that folds all of your field definitions together, and a TryFrom<SBuilder> for S implementation that fails if any of the fields are still None.

OK is this what you mean?

#[macro_use]
extern crate anyhow;

use anyhow::Result;
use std::{convert::TryFrom, iter::FromIterator};

struct F {
    k: String,
    v_s: Option<String>,
    v_b: Option<bool>,
}

#[derive(Debug)]
struct S {
    a: String,
    b: bool,
}

#[derive(Default)]
struct SBuilder {
    a: Option<String>,
    b: Option<bool>,
}

impl FromIterator<F> for SBuilder {
    fn from_iter<I: IntoIterator<Item = F>>(iter: I) -> Self {
        let mut sb: SBuilder = Default::default();
        for f in iter {
            match f {
                F { k, v_s, .. } if "a" == k => sb.a = v_s,
                F { k, v_b, .. } if "b" == k => sb.b = v_b,
                _ => (),
            }
        }
        sb
    }
}

impl TryFrom<SBuilder> for S {
    type Error = anyhow::Error;
    fn try_from(sb: SBuilder) -> Result<S> {
        Ok(S {
            a: sb.a.ok_or_else(|| anyhow!("a value missing"))?,
            b: sb.b.ok_or_else(|| anyhow!("b value missing"))?,
        })
    }
}

fn main() {
    let vals = vec![
        F {
            k: String::from("a"),
            v_s: Some(String::from("1")),
            v_b: None,
        },
        F {
            k: String::from("b"),
            v_s: None,
            v_b: Some(true),
        },
    ];
    let s: S = S::try_from(SBuilder::from_iter(vals)).unwrap();
    println!("{:?}", s)
}
1 Like

Yes, that’s pretty much what I had in mind. The repetitive structure paves the way for a potential macro implementation, should this become a common pattern.

For actually invoking the code, S::try_from(vals.collect()) might also work if the compiler can figure out the necessary type inference.

It wouldn't compile unless I did.

let s: S = S::try_from(vals.into_iter().collect::<SBuilder>()).unwrap();

But I could just wrap it like.

impl TryFrom<Vec<F>> for S {
    type Error = anyhow::Error;
    fn try_from(vf: Vec<F>) -> Result<S> {
        S::try_from(SBuilder::from_iter(vf))
    }
}

fn main () {
...
    let s = S::try_from(vals).unwrap();
1 Like

Looks like you're trying to deserialize data from a source into Rust structs. Your source is probably a DB table with each parameter type in separate columns (I used many DB tables of this sort to handle configurations etc.), or something like a JSON file with dynamic values.

Shouldn't you be using a serialization/deserialization library like serde?

The data I am dealing with is returned from the rusoto_rds_data crate API call[1]. It has the following structure.

ExecuteStatementRequest {
  column_metadata: Some(vec![
    ColumnMetadata {
      name: Some("id"),
      ..
    },
    ColumnMetadata {
      name: Some("my_bool"),
      ..
    },
    ColumnMetadata {
      name: Some("my_nullable_bool"),
      ..
    },
  ]),
  records: Some(vec![vec![
    Field {
      long_value: Some(54i64),
      ..
    },
    Field {
      boolean_value: Some(true),
      ..
    }
    Field {
      double_value: None,
      is_null: Some(true),
      ..
    }
  ]])
}

I am trying to convert it to the following.

struct Record {
  id: i32,
  my_bool: bool,
  my_nullable_bool: Option<bool>,
}

Record {
  id: 54i32,
  my_bool: true,
  my_nullable_bool: None,
}

How would you approach this with serde?

  1. rusoto_rds_data::ExecuteStatementResponse - Rust

So it really is data returned from a data service in table form with some fields holding dynamic data.

In order to deserialize this into a type (or an object in other languages), you usually use an ORM system. So what you're trying to do is to reinvent an ORM.

On the other hand, are you sure that rusoto does not provide an output format that is more "industry-standard" such as JSON? It would be trivial to use serde to deserialize a piece of JSON.

Check also if rusoto's data structures implement serde::Serialize. If they do, you can trivially serialize it into JSON.

If rusoto does only provide data in this internal form (what a strange API), I'd suggest you write your own implementation of serde::Serialize (which shouldn't be difficult).

Looking at the link you provided, it is the output from a SQL query. So it is well-structured data.

In particular, the type of each "column" is fixed, and so each "field" has a fixed type.

You really will be reinventing the wheel if you try to write something that deserializes SQL tables into an object type. You really need to think of a way to leverage serde which is built exactly for this purpose.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.