Get type info of struct fields when struct is serde::Serialize

52 Views Asked by At

I'm coding a local relational database system for some reason. I wanted to lean on serde for automatically converting some hashmaps to and from tables. The goal is to take hashmaps over simple types like:

struct SampleObject {
    name: String,
    age : Option<u32>,
    job : Option<String>
}

and create some function like:

pub fn from_serde_map<V>(
        table_name: &str,
        primary_key_name: &str,
        data: HashMap<String, V>,
    ) -> Result<Table>
    where
        V: Serialize,

which would use the functionality of serde::Value to convert the struct to a table over its fields. Notably the table needs type information and constraints on the column, and "NOT_NULL" constraints would be applied to all fields that were not optional in the underlying struct.

The sample struct above shows almost everything I expect to want from this - I don't need to support all rust types, all Serialize types or even any particularly broad range of types. The only things I need are flat structs over:

  • Simple numeric types
  • Strings
  • Booleans
  • Option of the other types on this list
  • (Reach goal) Vec<String>
  • (Reach goal) Simple Enums
  • (Reach goal) Vec<Simple Enum>

My first stab at this just looked at the first item in the passed map and tries to construct a header out of it (basically just exploration):

use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
struct SampleObject {
    name: String,
    age : Option<u32>,
    job : Option<String>
}
pub fn explore_item<V>(
    v : V
)
where
    V: Serialize,
{
    let header_object = serde_json::to_value(v)
        .unwrap()
        .as_object()
        .unwrap()
        .clone();
    for (k, v) in header_object.into_iter() {
        let as_value = serde_json::to_value(v).unwrap();
        println!("{}: {:?}", k, as_value);
    }
}
#[test]
fn call_explore_item() {
    let adam = SampleObject {
        name: "Adam".to_string(),
        age: None,
        job : Some("Gardener".to_string())
    }
    ;
    explore_item(adam);
}

This outputs the following:

age: Null
job: String("Gardener")
name: String("Adam")

There are a couple problems here:

  • This requires an actual instance of V, which may not be present - it would be nice to be able to call this with, for instance, an empty map.
  • Option-typed items lack type information; the fields for job and name show the same info despite one being Option-wrapped, and the None age field doesn't indicate it's a u32 None as opposed to a String or whatever None.

I was hoping that I could do something like (V as Serialize)::typeAt("crime") and get some representation of the type information stored there, but no such luck.

I've looked at a couple json_schema crates, but support seems a little spotty and I imagine those may be trying to do a lot more ambitious things than I am. I'm wondering if there isn't an easier approach? Or is today finally the day I need to learn to write my own derive macros?

EDIT FOR CLARIFICATION

Specifically, I want to take a simple struct type like SampleObject and be able to get the types of each field, so long as the record only has fields of fairly simple types. I want this so that I can generate header information for my table.

So I would have some enum like ColType with variants for StringType, u32Type, etc. For SampleObject, I'd like the map to ultimately have three columns:

  • name with StringType and NotNull constraint
  • age with U32Type
  • job with StringType

(name gets the NotNull constraint because the field isn't of Option type.)

0

There are 0 best solutions below