#Serde deserialize from either bool or enum in no_std environment.

52 messages · Page 1 of 1 (latest)

formal rapids
#

I have been using serde with no_std support and the serde-json-core backend.
One of my structs had a field track_mode: bool which I now want to convert from bool into a dedicated enum:

#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub enum TrackMode {
    #[default]
    Off,
    Mid,
    High,
}

For backward-compatibility, I now want to use the #[serde(deserialize_with="..")] attribute to use a custom deserialization function and either deserialize from a bool into OFF/HIGH, or use the TrackMode derived implementation otherwise.

Normally I would take advantage of a helper #[serde(untagged)] enum BoolOrTrackMode but unfortunately untagged is not supported without alloc support. So I am currently trying to implement a custom Visitor. Unfortunately it seems I can't reuse the existing TrackMode derived visitors, can I avoid having to re-write all of them by hand ?

urban salmon
#

Is it not just as simple as rs match v { "Off" => TrackMode::Off, // etc } inside visit_str?

formal rapids
#

I was assuming I would need to implement visit_enum

urban salmon
#

JSON doesn’t have a concept of enums, so no

formal rapids
#

okay so I implement both visit_str and visit_bool

#

which deserialize variant should I call in my custom bool_or_track_mode deserializer?

#

I currently have

fn bool_or_track_mode<'de, D>(des: D) -> Result<TrackMode, D::Error>
where
    D: serde::Deserializer<'de>,
{
    struct Visitor;
    impl<'de> serde::de::Visitor<'de> for Visitor {
        type Value = TrackMode;

        fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
            write!(formatter, "a bool or a TrackMode enum")
        }

        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(if v { TrackMode::High } else { TrackMode::Off })
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            match v {
                "Off" => Ok(TrackMode::Off),
                "Mid" => Ok(TrackMode::Mid),
                "High" => Ok(TrackMode::High),
                variant => Err(E::unknown_variant(variant, &["Off", "Mid", "High"])),
            }
        }
    }

    des.deserialize_enum("TrackMode", &["Low", "Mid", "High"], Visitor)
}
#

but the deserialization seems to fail with an ExpectedSomeValue

#

before that I was experimenting with:

        fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
        where
            A: serde::de::EnumAccess<'de>,
        {
            #[derive(Deserialize)]
            enum Discriminant {
                Off,
                Mid,
                High,
            }

            let (discriminant, variant) = data.variant::<Discriminant>()?;

            VariantAccess::unit_variant(variant)?;

            Ok(match discriminant {
                Discriminant::Off => TrackMode::Off,
                Discriminant::Mid => TrackMode::Mid,
                Discriminant::High => TrackMode::High,
            })
        }
urban salmon
formal rapids
#

That one seems to panic somewhere within serde

urban salmon
#

that would be a bug

#

are you sure?

formal rapids
#

yea, I am currently writing some tests

urban salmon
#

Oh hmmmm

#

serde_json supports deserialize_any but serde_json_core doesn’t

#

in that case, I don’t know if this is possible

#

yeah, I don’t think it is possible

formal rapids
#

I was previously using deserialize_enum, is that incorrect?

urban salmon
#

deserialize_enum will not accept bools

#

ever

#

it only does strings

formal rapids
#

will something hacky like deserialize_str and matching "false", "true" work?

urban salmon
#

No

#

well, unless you convert true into "true" in your JSON

formal rapids
#

ah yes

urban salmon
#

I don’t think there’s any real reason why serde_json_core doesn’t implement deserialize_any

#

it totally could

#

you could just PR that

#

it’d be like 10 lines

formal rapids
#

yea I will.

#

uh maybe there is a reason it is unsupported?

urban salmon
formal rapids
#

nope just saw the serde-json impl

#

seems straightforward

formal rapids
#

@urban salmon it seems the problem is serde-json-core does not actually have Value so there is no type info to match, it can only peek.

urban salmon
#

How does that affect anything

#

you can just peek and call the appropriate method, no?

formal rapids
#

not sure because there is also stuff like peeking a raw str

urban salmon
#

I mean, serde_json’s deserialize_any doesn’t use Value

formal rapids
#

oh yes you are right

#

nvm just tired

formal rapids
#

yea this seems to work

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
    where
        V: Visitor<'de>,
    {
        let peek = self.parse_whitespace().ok_or(Error::EofWhileParsingValue)?;
        match peek {
            b'n' => self.deserialize_unit(visitor),
            b't' | b'f' => self.deserialize_bool(visitor),
            b'-' => self.deserialize_i64(visitor),
            b'0'..=b'9' => self.deserialize_u64(visitor),
            b'"' => self.deserialize_str(visitor),
            b'[' => self.deserialize_seq(visitor),
            b'{' => self.deserialize_map(visitor),
            _ => Err(Error::ExpectedSomeValue),
        }
    }
#

the deserialize numbers bit requires a bit more thought actually and these also parse_whitespace inside them so it is not the most optimal.

urban salmon
#

parsing whitespace twice is probably not a big deal

#

but yeah, for the number routines it’d ideally call into the f64 methods when appropriate

formal rapids
#

This is currently not supported in json core. In general, it seems it doesn't have much support for working with "dynamic" types.

#

uh but that won't work because we get generic value types. oh well. I will make a PR, note the issue and then iterate, it shouldn't be too hard to do it the right way.

formal rapids
#

@urban salmon too much of a hack? 💀

urban salmon
#

lol