Closed RDambrosio016 closed 2 years ago
You could do something like this:
// [dependencies]
// heck = "0.3"
// indoc = "1.0"
// serde = { version = "1.0", features = ["derive"] }
// toml = "0.5"
// typetag = "0.1"
use heck::CamelCase;
use indoc::indoc;
use serde::de::value::MapAccessDeserializer;
use serde::de::{DeserializeSeed, Deserializer, IntoDeserializer, MapAccess, Visitor};
use serde::Deserialize;
use std::fmt::{self, Debug};
use std::marker::PhantomData;
#[typetag::deserialize]
trait CstRule: Debug {}
#[derive(Deserialize, Debug)]
struct SomethingHere {
some_prop: String,
}
#[typetag::deserialize]
impl CstRule for SomethingHere {}
#[derive(Deserialize, Debug)]
struct SomethingElse {}
#[typetag::deserialize]
impl CstRule for SomethingElse {}
#[derive(Deserialize, Debug)]
struct Config {
rules: Option<RuleConf>,
}
#[derive(Deserialize, Debug)]
struct RuleConf {
#[serde(deserialize_with = "from_typetag_objects")]
errors: Vec<Box<dyn CstRule>>,
#[serde(deserialize_with = "from_typetag_objects")]
warnings: Vec<Box<dyn CstRule>>,
}
fn from_typetag_objects<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
struct TypetagObjects<T> {
_type: PhantomData<T>,
}
impl<'de, T> Visitor<'de> for TypetagObjects<T>
where
T: Deserialize<'de>,
{
type Value = Vec<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("zero or more typename-to-value pairs")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut vec = Vec::new();
while let Some(key) = map.next_key::<String>()? {
let de = MapAccessDeserializer::new(Entry {
key: Some(key.to_camel_case().into_deserializer()),
value: &mut map,
});
vec.push(T::deserialize(de)?);
}
Ok(vec)
}
}
struct Entry<K, V> {
key: Option<K>,
value: V,
}
impl<'de, K, V> MapAccess<'de> for Entry<K, V>
where
K: Deserializer<'de, Error = V::Error>,
V: MapAccess<'de>,
{
type Error = V::Error;
fn next_key_seed<S>(&mut self, seed: S) -> Result<Option<S::Value>, Self::Error>
where
S: DeserializeSeed<'de>,
{
self.key.take().map(|key| seed.deserialize(key)).transpose()
}
fn next_value_seed<S>(&mut self, seed: S) -> Result<S::Value, Self::Error>
where
S: DeserializeSeed<'de>,
{
self.value.next_value_seed(seed)
}
}
deserializer.deserialize_map(TypetagObjects { _type: PhantomData })
}
fn main() {
let input = indoc! {r#"
[rules.errors]
something_here = { some_prop = "foo" }
[rules.warnings]
something_else = {}
something_else = {}
"#};
println!("{:#?}", toml::from_str::<Config>(input).unwrap());
}
That works! thank you 🙂
Any idea on how i could maybe parse special keys as properties? id like to have an error
property which is a boolean based on whether error = {}
is there.
Sorry that no one was able to provide further guidance here. If this is still an issue, you could try taking this question to any of the resources shown in https://www.rust-lang.org/community.
Hello!
I have structs similar to
where CstRule is typetagged as externally typed. what my goal is is to be able to parse the config from this:
where
something_here
andsomething_else
are the "tags" i set for the typetag, like#[typetag::serde(name = "something_else")]
. This structure of vectors does not currently work, because it ends up being a vector of objects, with each object having a single key being the name of the typetag. What i basically want to do is flatten that into an object with those keys. I also tried aHashMap<String, Box<dyn CstRule>>
and flattened it, but that fails to deserialize for more than one item.I would also like to make the deserialization case insensitive, e.g.
something_else
andSomethingElse
both work. I tried using serde-aux's attribute for this but that does not work. And i cant exactly apply it to the enum which would be generated by typetag.Id love to know if there is a way to do this without manually implementing deserialize 🙂