serde-rs / json

Strongly typed JSON library for Rust
Apache License 2.0
4.7k stars 536 forks source link

Am I passing metadata through DeserializeSeed to facilitate deserialization of algebraic datatypes as intended #1132

Closed hirenhpatel closed 1 month ago

hirenhpatel commented 2 months ago

I have data types that need to be deserialized from json that look like this:

// inlining inner Node struct to better illustrate structure of data-tree:

struct MyData {
    field1: Dynamic<(Float, String, Node {fieldA: Scalar<Int>,  ...} )>,
    field2: Scalar<String>,
}

The datatypes that appear are quite varied, and as a result, I am essentially writing my own implementations of Deserialize.

I have now reached a situation where I need extra information (let's call it MyMetadata) to properly deserialize the Node struct.

Happily, I found DeserializeSeed and started to rewrite all my Deserialize implementations to DeserializeSeed implementations since I can then utilize the extra seed to argument pipe MyMetadata through deserialization. My thought here is MyMetadata tags along for the ride remaining unused, until a Node is encountered by the Visitor at which point I can use it to facilitate deserialization.

Unfortunately, I've run into an awkward problem: it seems that I'm not supposed to implement DeserializeSeed on Dynamic, Scalar, Float like I am supposed to in Deserialize. Rather, I'm supposed to implement it on a new data structure, and the associated return type is supposed to be one of Dynamic, Scalar, Float. With that interface, it's not clear to me how I should be using DeserializeSeed to move the MyMetadata through the deserialization process?

Should I add an unused generic parameter on MyMetadata and implement DeserializeSeed on the various concrete types, converting them to other concrete types in my implementation of Visitor?

dtolnay commented 2 months ago

Usually you would implement DeserializeSeed on the same type that also has the corresponding Visitor impl. So a canonical impl would look like:

impl<'de> DeserializeSeed<'de> for TheVisitor {
    type Value = <Self as Visitor<'de>>::Value;

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_………(self)
    }
}
hirenhpatel commented 2 months ago

The thought of implementing DeserializeSeed on Visitor never occurred to me! After studying your suggestion some more, I have settled on the pattern of having a surrogate nested visitor type DynamicVisitor<TupleVisitor<..., NodeVisitor<...>> that mirrors the target deserialization datatype: Dynamic<(..., NodeVisitor<...>)>.

I have implemented DeserializeSeed<'de> on all of the *Visitor structs, which have the metadata as needed. Then, most of the my deserialize calls look like this deserializer.deserialize_*(self.0) to pass the inner visitor to the next step in the recursion.

Have I understand your guidance correctly?

Further, do I understand correctly that, in my recursive deserialization implementations, I should not mix Deserialize with DeserializeSeed? That is, in any chain, I will be dealing with Deserialize<'de> exclusively, or with DeserializeSeed<'de> exclusively?

Thanks in advance