-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Struct flattening not working #115
Comments
Interesting. I haven't worked with struct flattening before. Wondering if we need any special handling for those. |
@kvark It looks like there are two kinds of struct flattening available in Serde. There's flattening into a map, which acts as a "catch-all" for fields that aren't found in your struct, like this: #[derive(Deserialize, Serialize)]
struct MyType {
first: u32
second: u32,
#[serde(flatten)]
everything_else: HashMap<String, Value>,
} Then there's flattening into a struct, which operates in a similar manner and is demonstrated in the example code I showed in the first comment of the issue. Not sure if this needs any special handling on RON side either, but I can't think of what else might be going wrong. It appears that |
Blocked by |
Well, it seems we need some workaround since the serde issue won't be fixed. |
I've hit this problem, and found a workaround by using both a struct and map together. I have an enum variant that looks like this: pub struct ObjectCommon { x: u32, y: u32 }
pub enum Object {
Cycle {
#[serde(flatten)]
common: ObjectCommon,
extra: u32,
},
} I can successfully deserialize it from this:
Note the extra curly brackets. |
This bug makes it impossible to use |
The only way we could fix this is by allowing RON to deserialize the RON struct syntax as map. This doesn't seem to be a good idea, though. |
Any updates on this? :) |
Any update on this now? I just ran into this and got a very unhelpful error:
|
@Boscop sorry, it looks like nobody is looking into this. Any help is appreciated! |
One could get flattening to work with this slightly frightening code: fn deserialize_map<V>(mut self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
/* START */
let mut wc = self.bytes;
// remove any struct name
if !self.newtype_variant {
let _ = wc.identifier();
wc.skip_ws()?;
}
// check if this could be a struct
if self.newtype_variant || wc.consume("(") {
wc.skip_ws()?;
// only serialise a struct as a map if it definitely looks like a struct
if !wc.check_tuple_struct().unwrap_or(true) {
let _ = self.bytes.identifier();
return self.deserialize_struct("", &[], visitor);
}
}
/* END */
self.newtype_variant = false;
... While no existing test cases fail, this does seem like Pandora's box for rather weird bugs in the future. In particular, it has to accept any struct name that could be there, circumventing the usual equality checks we have in |
You can find a more refined implementation with some first test cases in #403 - I'm not sure if this is the right path forward, though. |
Any thoughts on an approach to move forward? |
I have been thinking again about #403 again recently but have not come up yet with a better solution. While we could land #403 at any time, having this special case of deserialising a map from what RON sees as a struct just seems wrong. Unfortunately, this also does not seem to be a case where serde uses some specially named @ericsampson If you are anyone else has some ideas of how to improve #403 (with a better impl or more restricted application) I would be very happy to merge the PR and get flattening to work. Right now it still seems like a too-large can of worms. |
I did a bit more digging and flatten is even more broken than I had realised the last time. #403 would only sort-of-fix deserialising from a RON struct into a flattened struct, but it does not solve serialising at all. At serialisation time, serde uses Serialising has no notion of identifiers but deserialising does. So while serialising a flattened struct outputs a map with stringified keys, at deserialisation time serde then asks for a map which contains identifiers as keys. In RON identifiers are typed to not be strings. So while serialising outputs I think there are two ways out of this and I'm sorry that they both sound bad right now. I would be delighted if someone comes up with something better.
|
Ok, with some extra playing around there is a serious 4th option. #453 would at least allow flattened structs to roundtrip through RON. However, the syntax would still use maps since that's what serde provides us with and getting serialisation and deserialisation to work with RON structs instead would require just too many contortions right now. Thus, #453 is something we could land rather soon and thus at least have some support for flattening in RON. |
@ebkalderon @solarretrace @Frizi @ericsampson @kvark @torkleyy What are your thoughts on #453 as a minimal fix? |
In no way serious,
This would not work, since the output of #[derive(serde::Serialize)]
struct A {
a: String,
} to #[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate serde as _serde;
#[automatically_derived]
impl _serde::Serialize for A {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_struct(__serializer, "A", false as usize + 1)?;
_serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "a", &self.a,)?;
_serde::ser::SerializeStruct::end(__serde_state)
}
} and a flatten struct #[derive(serde::Serialize)]
struct A {
a: String,
#[serde(flatten)]
b: BTreeMap<String, bool>,
} to #[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate serde as _serde;
#[automatically_derived]
impl _serde::Serialize for A {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_map(__serializer, _serde::__private::None)?;
_serde::ser::SerializeMap::serialize_entry(&mut __serde_state, "a", &self.a)?;
_serde::Serialize::serialize(&&self.b, _serde::__private::ser::FlatMapSerializer(&mut __serde_state))?;
_serde::ser::SerializeMap::end(__serde_state)
}
} Note we lost "A" in the first place. So in order to have the derive working, we have to fork serde and replace it as a drop-in solution. [patch.crates-io]
serde = { git = 'https://github.com/example/serde2.git' } |
For comparison, serde_starlark (https://github.com/dtolnay/serde-starlark) supports That data structure gets serialized to Starlark which looks like this: https://github.com/dtolnay/cxx/blob/d8427f9c251cac0d6c35a26c0784319c95b208d8/third-party/bazel/BUILD.winapi-util-0.1.5.bazel#L17 Notice that |
Thanks @dtolnay for sharing that crate! I had a quick look and it seems like the |
@juntyr just said what I'd like to say. |
My very quick (and again in no way seriously considered idea) was to add blanket impls for existing |
Ok, for serialising, the problem is that struct fields need For deserialising, the wrapper would need to have access to the struct/variant name and fields as Therefore, a clean generalised approach seems quite difficult to me. A specific format could probably support such wrapper structs by using some in-band signalling to communicate that we're looking at a flattened struct. However, that would again be format specific. So the only option I see right now would be a general crate that uses in-band signalling in some way such that non-supporting formats still do something ok but supporting formats can actually handle flattened structs nicely. However, I've not yet given the details here any thought. |
I'm trying to use Serde's struct flattening functionality with RON, using the Rust code below:
This is the RON that I am attempting to deserialize:
When I try to deserialize the target struct from RON, I get the following error:
On the other hand, deserializing from the following TOML string works as intended:
Any indication on what might be going wrong?
The text was updated successfully, but these errors were encountered: