Skip to content
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

Ability to use default value even if set to null #1098

Closed
est31 opened this issue Nov 23, 2017 · 9 comments
Closed

Ability to use default value even if set to null #1098

est31 opened this issue Nov 23, 2017 · 9 comments
Labels

Comments

@est31
Copy link
Contributor

est31 commented Nov 23, 2017

default does not treat a field being set to null equal to the field being emitted. More concretely, the following code will panic in the line where it attempts to deserialize request_c:

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

#[derive(Serialize, Deserialize, Debug)]
struct Request {
    #[serde(default = "Priority::lowest")]
    priority: Priority,
}

#[derive(Serialize, Deserialize, Debug)]
enum Priority { ExtraHigh, High, Normal, Low, ExtraLow }
impl Priority {
    fn lowest() -> Self { Priority::ExtraLow }
}

fn main() {

    let request_a: Request = serde_json::from_str(r"{}").unwrap();
    println!("{:?}", request_a);

    let request_b: Request = serde_json::from_str(r#"{"priority": "High"}"#).unwrap();
    println!("{:?}", request_b);

    let request_c: Request = serde_json::from_str(r#"{"priority": null}"#).unwrap();
    //^~ PANIC: ErrorImpl { code: ExpectedSomeValue, line: 1, column: 14 }
    println!("{:?}", request_c);

}

The data I want to process sometimes has its field set, sometimes it is set to null, and sometimes it is omitted. I want to set a default for when it is null or omitted.

This doesn't need to be the default behaviour but having sth like #[serde(default_on_null)] would be really nice :). Thanks to @oli-obk suggesting that on IRC.

@dtolnay
Copy link
Member

dtolnay commented Nov 23, 2017

I am open to supporting this better. For now the current workaround would be a deserialize_with function that handles null.

use serde::{Deserialize, Deserializer};

#[derive(Serialize, Deserialize, Debug)]
struct Request {
    #[serde(default = "Priority::lowest", deserialize_with = "nullable_priority")]
    priority: Priority,
}

fn nullable_priority<'de, D>(deserializer: D) -> Result<Priority, D::Error>
    where D: Deserializer<'de>
{
    let opt = Option::deserialize(deserializer)?;
    Ok(opt.unwrap_or_else(Priority::lowest))
}

@est31
Copy link
Contributor Author

est31 commented Nov 23, 2017

@dtolnay oh thanks. That's nice and small compared to the monster that I wrote :)...

@dtolnay
Copy link
Member

dtolnay commented Jan 6, 2019

A default_on_null function would be a reasonable addition to a Serde helper library such as serde-aux or serde_with. I am closing this issue because I don't think we need to introduce a new Serde attribute for this use case.

@dtolnay dtolnay closed this as completed Jan 6, 2019
@folex
Copy link

folex commented Jul 16, 2020

Hi! Thank you for an awesome serde :)

Sorry for writing to an old thread, just wanted to know why there's a need for a separate attribute? Couldn't default do the trick?

If I understand correctly, in the case of JSON deserializing #[serde(default)] struct from "{}" yields default value, while deserializing from "null" gives error:

invalid type: null, expected struct

Here's an example code
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9cda0c82d5f144615db93666bee84d24

Is that an expected behaviour, or an inconsistency of serde/serde_json? I would expect {} and null both yield a default value.

Thanks in advance!

@gabhijit
Copy link

Since, search results land to this page - Here's something that can use 'default'

// Omitting other derives, for brevity 
#[derive(Deserialize)]
struct Foo {
   #[serde(deserialize_with = "deserialize_null_default")]
   value: String, 
}

fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
    T: Default + Deserialize<'de>,
    D: Deserializer<'de>,
{
    let opt = Option::deserialize(deserializer)?;
    Ok(opt.unwrap_or_default())
}

[playground link] (https://play.integer32.com/?version=stable&mode=debug&edition=2018&gist=d1b67fae798e958b377c622fd009de14)

@IGJAmpere
Copy link

Is that an expected behaviour, or an inconsistency of serde/serde_json? I would expect {} and null both yield a default value.

It is an expected behaviour. The default value is used when the variable is not specified. null can be a specification of the variable, as sometimes it carries very useful information (can be interpreted as infinite, or unset), which might be relevant to different use-cases.

So, null -> default cases are a specific thing, and should be specifically determined.

@ollyde
Copy link

ollyde commented Mar 28, 2024

Am I missing something here guys?

No one has explained if there was an additional library needed?

Screenshot 2024-03-28 at 15 39 07

@Kochiyama
Copy link

Am I missing something here guys?

No one has explained if there was an additional library needed?

Screenshot 2024-03-28 at 15 39 07

You need to define this function manually in some place in your file.

@mhnap
Copy link

mhnap commented May 31, 2024

Since, search results land to this page - Here's something that can use 'default'

// Omitting other derives, for brevity 
#[derive(Deserialize)]
struct Foo {
   #[serde(deserialize_with = "deserialize_null_default")]
   value: String, 
}

fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
    T: Default + Deserialize<'de>,
    D: Deserializer<'de>,
{
    let opt = Option::deserialize(deserializer)?;
    Ok(opt.unwrap_or_default())
}

[playground link] (play.integer32.com/?version=stable&mode=debug&edition=2018&gist=d1b67fae798e958b377c622fd009de14)

Great solution! But need to remember that #[serde(default)] attribute is still needed if we want to use the Default::default() if the value is not present when deserializing (playground).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

8 participants