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 forjobandnameshow the same info despite one being Option-wrapped, and theNoneage field doesn't indicate it's au32None as opposed to aStringor whateverNone.
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:
namewithStringTypeandNotNullconstraintagewithU32TypejobwithStringType
(name gets the NotNull constraint because the field isn't of Option type.)