[serde] deserializing based on generic schema

I have a set of structs that can form a schema. In actuality, there's a lot of these and they have additional behavior attached to them, but the basic idea is that you can statically assemble these using generics, and there is also this Dynamic version which can be changed at runtime.

#[derive(Debug, PartialEq)]
struct Atom {
    param: u32,
    state: u32,
}

#[derive(Debug, PartialEq)]
struct Compound<A, B>(A, B);

#[derive(Debug, PartialEq)]
enum Dynamic {
    Atom(Atom),
    Compound(Box<Compound<Dynamic, Dynamic>>),
}

My goal is to serialize the state, but not the parameters, of these. For Atom, that means state but not param. For Dynamic, it means the state of the variant but not the variant itself. It would be simple if I could do something like this:

trait Stateful {
    type State: serde::Serialize + serde::de::DeserializeOwned;

    fn state(&self) -> Self::State;
    fn set_state(&mut self, state: Self::State);
}

This isn't a good choice, because it isn't possible to make statically-known State type for Dynamic. The obvious route would be to make an enum like this:

enum DynamicState {
    Atom(<Atom as Stateful>::State),
    Compound(Box<<Compound<Dynamic, Dynamic> as Stateful>::State>),
}

However, I this approach means that DynamicState is serialized as a tagged enum, which I don't want.

Current approach

Basically, Dynamic needs to be available when deserializing to know how to deserialize the state. This sounds like a job for DeserializeSeed.

pub trait Stateful: for<'de> DeserializeSeed<'de, Value = Self> {
    type State<'a>: Debug + Serialize
    where
        Self: 'a;

    fn state(&self) -> Self::State<'_>;
}

This works (code in playground), but it sucks to code. I have to manually implement DeserializeSeed for each type, which is extremely verbose.

Is there some way I can accomplish what I'm trying to do here without having to manually write Serialize and Deserialize impls for all of my types?

I suggest, you split the DTOs and the actual models and implement conversion traits between them. Then you can use serde's derive marco and flatten and untagged attributes on the DTOs without the need to implement de-/serialization yourself.

Hey, thanks for taking the time to read and reply. Can you explain a bit more about what you mean? My understanding is that what I said in "it would be simple if" is exactly transforming the model into a DTO and vice versa, but that solution doesn't work. Perhaps you have something different in mind?

I finally discovered a solution I am pretty happy with. I started by using serde_json::Value as the state type for Dynamic. This is nice because I can then use deserialize_into to further convert that into the target state type, but has two drawbacks:

  1. It is tied to JSON, which is not ideal.
  2. We have to deserialize the entire state object before passing it to Dynamic::set_state, when theoretically we could have rejected invalid states immediately.

An AI told me how to address the first point: serde_value. It offers, serde_value::Value, which is a format-independent in-memory data structure that I can use in place of serde_json::Value.

I don't have a good answer for the second point. A solution would require using DeserializeSeed, I think, which is not derivable. The amount of state data I'm going to have is quite small so the drawback here is minimal.

code in playground

Overall, I'm happy with this solution and will move forward with it. I would appreciate if anyone has any easy way to eliminate the other drawback, though.

I meant something like this: