[solved] Serde, how to deserialize from string or _internally_ tagged enum

Your infinite loop is because:

  • <Rule as Deserialize<'de>>::deserialize
  • calls string_or_rule
  • which calls Deserializer::deserialize_any::<RuleVisitor>
  • which calls <RuleVisitor as Visitor<'de>>::visit_map
  • which calls <Rule as Deserialize<'de>>::deserialize

You can break the loop by calling anything other than Rule::deserialize on the last step. One way would be to deserialize a helper type in the last step, then convert it to Rule.

fn visit_map<M>(self, map: M) -> Result<Rule, M::Error>
where
    M: MapAccess<'de>,
{
    #[derive(Deserialize)]
    #[serde(tag = "type", rename_all = "lowercase")]
    enum RuleHelper {
        Course(course::Rule),
        Requirement(requirement::Rule),
    }

    let map = de::value::MapAccessDeserializer::new(map);
    let helper = RuleHelper::deserialize(map)?;
    match helper {
        RuleHelper::Course(course) => Ok(Rule::Course(course)),
        RuleHelper::Requirement(req) => Ok(Rule::Requirement(req)),
    }
}

A more concise way would be to treat RuleHelper as a remote derive for Rule so that no conversion is needed.

fn visit_map<M>(self, map: M) -> Result<Rule, M::Error>
where
    M: MapAccess<'de>,
{
    #[derive(Deserialize)]
    #[serde(remote = "Rule")]
    #[serde(tag = "type", rename_all = "lowercase")]
    enum RuleHelper {
        Course(course::Rule),
        Requirement(requirement::Rule),
    }

    RuleHelper::deserialize(de::value::MapAccessDeserializer::new(map))
}

Another more concise way would be to skip RuleHelper altogether and treat Rule as a remote derive for itself. In general remote derives are a way to generate de/serialization logic without generating a De/Serialize impl.

#[derive(Deserialize, Debug)]
#[serde(remote = "Self")]
#[serde(tag = "type", rename_all = "lowercase")]
enum Rule {
    Course(course::Rule),
    Requirement(requirement::Rule),
}

/* ... */

fn visit_map<M>(self, map: M) -> Result<Rule, M::Error>
where
    M: MapAccess<'de>,
{
    Rule::deserialize(de::value::MapAccessDeserializer::new(map))
}
2 Likes