GithubHelp home page GithubHelp logo

Comments (7)

jonasbb avatar jonasbb commented on June 10, 2024 1

Instead of extending skip_serializing_none, I would suggest using serde_with::apply unless there is a reason you cannot do so. It applies a set of other attributes to all fields with a matching type. In your case, you want the serde attribute that you write above each field, as part of the apply macro. You can cover both structs OptOneMany and FileConfigEnum in a single invocation.

#[serde_with::apply(
    OptOneMany => #[serde(default, skip_serializing_if = "OptOneMany::is_none")],
    FileConfigEnum => ...,
)]
#[derive(Serialize)]
struct Foo {
    field: OptOneMany,
}

from serde_with.

nyurik avatar nyurik commented on June 10, 2024

@jonasbb thanks, I didn't realize apply could do that, thanks. The only issue I see is that I would still have to do it everywhere - which would become fairly verbose because of all duplications (I have 5-10 structs used for serialization). Ideally, I think it would be far more useful if skip_serialize_none could be extended to tell it which enums behave "like Option" - i.e. to add an attribute on my own custom types:

#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[serde_with::treat_as_option(NoVals)]  // <-- this tells serde_with that NoVals is just like Option::None
pub enum OptOneMany<T> {
    #[default]
    NoVals,
    One(T),
    Many(Vec<T>),
}

from serde_with.

jonasbb avatar jonasbb commented on June 10, 2024

Rust macros do not work like that. I don't know of any way that would make this work.
Rust macros work on token only. They cannot know what type something is, since they run before typechecking. This makes it impossible for the skip_serialize_none to lookup how to handle OptOneMany. skip_serializing_none only recognizes some patterns as Option. They are listed under Limitations. Even a rename, like use std::option::Option as FooBar will break the macro.

Another problem is that macro execution order is undefined. You cannot be sure that #[serde_with::treat_as_option(NoVals)] will be processed before skip_serialize_none.

The only option would be to make the OptOneMany look basically indistinguishable from the real Option type. I do not recommend this approach. It would require renaming/aliasing the type, having a is_none function, etc.

from serde_with.

nyurik avatar nyurik commented on June 10, 2024

@jonasbb thanks for in depth explanation, agree, sadly it seems like a no go. One option of course would be to use the trait system rather than (or in addition to) the macros. Something like pub trait IsOptionable { fn is_none(&self) -> bool; }. You would implement that trait on the Option object (and possibly a few others?), but then each other non-option value would still have to be tagged with some attribute like #[optionable] - at least once per type in each struct (e.g. if there are multiple instances of the same type, derive macro can notice that the same type has already been identified as optionable.

from serde_with.

jonasbb avatar jonasbb commented on June 10, 2024

The skip_serialize_none macro needs to decide at compile time, before type checking, if a field is of type Option or not. I am not sure how you think the macro is supposed to use the IsOptionable trait. Could you expand how you imagine that to work?

from serde_with.

nyurik avatar nyurik commented on June 10, 2024
#[derive(Serialize)]
#[skip_serializing_none]
struct Foo {
  #[SkipIfNone]       // <- this tells serde_with that field1, as well as other fields
  field1: OptOneMany, // of type OptOneMany should be treated as having a   .is_none()
                      // Technically we do not even need a dedicated trait - enough to just 

  field2: OptOneMany,
  field3: OptOneMany,
  field4: SomeOtherType, // <-- serialized as usual
}

To be honest, I am not too thrilled about this solution either -- magic is usually bad. The cleaner (but still a bit hacky?) is this:

#[derive(Serialize)]
#[skip_serializing_none(OptOneMany)]  // <-- all fields of the listed types are assumed to have .is_none()
struct Foo {
  field1: OptOneMany,
  field2: OptOneMany,
  field3: OptOneMany,
}

from serde_with.

jonasbb avatar jonasbb commented on June 10, 2024

I rather not complicate skip_serializing_none with such type matching logic. It feels wrong to just work based on "magic" function names like is_none here. The apply macro is made for it. Yes, it can be a bit verbose, but I rather fix that.

from serde_with.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.