Serde: deserializing a map into a vector of trait objects

I'm trying to deserialize a map where all possible values are structs implementing a certain CompletionSource trait without going over each item individually.

More specifically (leaving out the irrelevant parts):

trait CompletionSource { /* .. */ }
   
#[derive(Deserialize)]
struct SourceA {
    enable: bool,
}

impl CompletionSource for SourceA { /* .. */ }

#[derive(Deserialize)]
struct SourceB {
    enable: bool,
}

impl CompletionSource for SourceB { /* .. */ }

#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
enum ValidSource {
    SourceA,
    SourceB,
}

The data to deserialize looks like

  source_a = {
    enable = true,
  },

  source_b = {
    enable = false,
  },

and I'd like to get a Vec<Arc<dyn CompletionSource>> out of that. To do that I've implemented serde::de::Visitor for a SourcesVisitor struct:

struct SourcesVisitor;

impl<'de> Visitor<'de> for SourcesVisitor {
    type Value = Vec<Arc<dyn CompletionSource>>;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "a list of completion sources")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        let mut sources = match access.size_hint() {
            Some(len) => Vec::with_capacity(len),
            None => Vec::new(),
        };

        while let Some(source) = access.next_key::<ValidSource>()? {
            match source {
                ValidSource::SourceA => {
                    let a = access.next_value::<SourceA>()?;
                    if a.enable {
                        sources.push(Arc::new(a) as Arc<dyn CompletionSource>);
                    }
                },

                ValidSource::SourceB => {
                    let b = access.next_value::<SourceB>()?;
                    if b.enable {
                        sources.push(Arc::new(b) as Arc<dyn CompletionSource>);
                    }
                },
            }
        }

        Ok(sources)
    }
}

which works, but every time I add a new source I need add a new variant to the ValidSource enum and its corresponding arm in match source { .. }. Since all the arms of the match look exactly the same this feels a bit boilerplatey.

Is there a way to have an implementation of visit_map that doesn't change as the number of possible sources increases? Maybe by turning the variants of the ValidSource enum from units to tuples? I.e. from

enum ValidSource {
    SourceA,
    SourceB,
}

to (apologies for naming the enum variants the same as the struct they contain)

enum ValidSource {
    SourceA(SourceA),
    SourceB(SourceB),
}

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.