Hi,
Is it possible by using serde/serde_json to deserialize struct that has raw ptr as a member?
The deserialization should assign null to that member?
Thank you
of course it is possible, but doing so is usually a code smell, and I'd suggest to solve your problem using other approach.
if the struct is simple, you can just manually implement Deserialize instead of deriving it. with derived Deserialize, you can use the #[serde(deserialize_with = "path")] attribute, something like:
#[derive(Deserialize)]
struct Foo {
#[serde(deserialize_with = "deserialize_null")]
ptr: *const (),
}
fn deserialize_null<'de, D>(_d: D) -> Result<*const (), D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(std::ptr::null())
}
as I said, I don't recommend doing this. for example, one common approach I use in similar situation is to use separate types for in-memory data and serialized data, and use the #[serde(from = "Type")] attribute:
#[derive(Deserialize)]
#[serde(from = "FooSaved")]
struct Foo {
x: i32,
ptr: *const (),
}
/// this type contains only serializable data
#[derive(Deserialize)]
struct FooSaved {
x: i32,
}
impl From<FooSaved> for Foo {
fn from(value: FooSaved) -> Foo {
let ptr = allocate_memory_and_initialized_data_from_saved_data(&value);
Foo {
x: value.x,
ptr,
}
}
}
Edit: I should read posts in their entirety...
How about this?
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Data {
id: u32,
#[serde(skip)]
ptr: *const (),
}
fn main() {
let json = r#"{"id": 42}"#;
let data: Data = serde_json::from_str(json).expect("Invaid data.");
dbg!(data);
}
serde(skip) will skip de-/serialization for the field and assign the Default::default() value to the field upon deserialization, which is a NULL pointer for raw pointers.
Thanks for the example. I don't actually agree with that statement, that it is a code smell. Even in your second example, you manually instead of automatically are assigning null value. What's the difference? I prefer automatic way of doing things whenever it is available.
Also, if the struct is not as simple as in the example, your second example would have to do cloning, and that to me is more of a code smell.
But really appreciate you help. Thank you
That actuall will not work for types that you cannot derive Default for. Thank you for your time and help.
You're wrong. It will always work for raw pointers, as you asked for, regardless of the underlying type:
that's just a placeholder for the example. in real code, after you load serialized data e.g. from files, the From trait is where you re-calculate the proper in-memory data structure.
the difference is, the function for #[serde(deserialize_with)] has no context other than the Deserializer, but From::from have all the serialzable fields so you can repopulate the unserializable fields.
however, in some cases, you still need more context than the serializable data in order to fully re-construct the in-memory data structure, in which case #[serde(From)] is not enough, and I would usually ditch Deserialize for the in-memory type completely, and use a dedicated load() function instead.
btw, stateful deserialization is supported by DeserializationSeed, but the API is cumbersome to implement so I generally avoid it.
And do cloning. Nope... I actually am in the middle of it, so definitely not a better option and code smell.
moving, not cloning. and even you implement your From with cloning, how do does memcpy compare to, say, json parsing?
anyway, if you think serializing and deserializing pointers are good ideas, you still didn't get my point.
I don't think is good idea. Sometimes it is simply necessity. You didn't get my point by assuming straightforward, book ish example, where everything is simple...
that's what I meant by "code smell".
most of the time it is not, you just need to accept there's different way to solve the problem.
I'm not assuming anything.
my whole point is, if your type is not (fully) deserializable, then don't pretend it is. use the API as they are intended: Deserialize should be be implemented for deserializable data.
it's the same idea with builder pattern: use separate types to represent the fully constructed data and partially initialized data.
it's also the same reason to avoid two-phase construction: to make it impossible to create a "partially" valid data structure.
this is not just for contrived examples, I used the same technique for rather complicated data in real code.
for example, I tend to create a giant Config struct that holds the "master" configuration for the entire application. I don't serialize/deserialize Config to files directly, but instead using a separate Settings type to load and save user settings.
at application startup, I construct a Config by gathering information from multiple sources: command line options, environment variables, serialized Settings file, fallback defaults, etc.
many fields of Config is runtime only and doesn't need to, or simply cannot, be serialized to files. on the other hand, the Settings data is typically only used as transient data when loading or saving files.
in many of my use cases, Settings is actually an enum, not a struct. this is mainly for compatibility. if I don't need a stable file format, I would use a struct instead.
here's an simplified example how the code looks:
/// not implementing `Serialize` or `Deserialize` directly
/// importantly, no `Option` for any field types,
/// so no need for `if-let` or `unwrap()` at runtime.
/// if some settings are optional, default values
/// are set at construction/load time
struct Config {
config_dir: PathBuf,
data_dir: PathPath,
auth_info: OauthToken,
//...
}
/// convert from/to `Settings`, and other calculations
impl Config {
fn from_config(config_dir: PathBuf, other_parameters: ()) -> Result<Self> {
// load from file e.g. `~/.config/MY_APP/settings.json`
let settings = load_user_settings(&config_dir)?;
// get a valid access token
let auth_info = if let Some(token) = settings.refresh_token.as_deref() {
refresh_access_token(token)?
} else {
login(&settings.username)?
};
let data_dir = settings.data_dir.unwrap_or_else(|| todo!("default data dir"));
// gather other information, e.g. load some states from `data_dir`
//...
// finally construct the "master" configurations
Ok(Config {
//...
})
}
fn save_settings(&self) -> Settings {
todo!()
}
}
/// use flattened internal tagged enum representation,
/// the tag is also the format version
#[derive(Serialize, Deserialize)]
#[serde(tag = "format_version", rename_all = "lowercase")]
enum Settings {
V1(v1::Settings),
//...
}
mod v1 {
#[derive(Serialize, Deserialization)]
struct Settings {
#[serde(skip_if = "Option::is_none")]
data_path: Option<PathBuf>,
username: String,
#[serde(skip_if = "Option::is_none")]
refresh_token: Option<String>,
//...
}
}
/**
example content of `settings.json`:
{
"format_version": "v1",
"username": "nick",
"refresh_token": "xxxxxxxx"
}
*/
fn initialize(config_dir: PathBuf) -> Result<Config> {
}
fn main() {
// from command line option, env var, or fallback to some platform specific default value
let config_dir = todo!();
// load user settings and calculate other configurations
let config = Config::from_config(config_dir)?;
// use `config` for subcommands
match cli.subcommand {
// a hypothetical `my_app set NAME VALUE` subcommand to modify `settings.json`
Command::Set(args) => {
let mut settings = config.save_settings();
// modify `settings` according to command line arguments
//...
// save the updated settings to file
save_user_settings(&config.cofig_dir, settings)?;
},
// many other commands
Command::StartServer(args) => {
start_server(&config, args)?;
}
//...
}
}
And same goes for cloning when unnecessary.
Rest of the comments are in similar vein so I'll avoid replying to them because you begin to irritate me with your simplistic approach.
OK, so this is the struct:
pub struct Item<'a> {
pub name_col: String,
pub obj_col: Option<Obj<'a>>,
}
pub struct Obj<'a> {
pub name_obj: Option<&'a str>,
pub ctx_obj: *mut OCIDirPathFuncCtx, // Function context for this obj column
}
I need to create "dynamically", that is during the program run Vec of < Item >, field name in Item will be different for each item. There will be different number of items as well as the name_col field for different runs of the program.
How do you suggest I do that?
Show me the code.
I'm really sorry, I didn't mean to be irritating.
That's OK, forget that. Show me how would you code that task.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.