Deserializing to OsString with serde

Serde's documentation says that it supports deserializing to OsString, albeit in some sort of indirect way that I'm not clear how to use. Trying to deserialize to OsString naïvely with:

use serde::{Serialize, Deserialize};
use std::error::Error;
use std::ffi::OsString;
use std::path::PathBuf;

#[derive(Serialize, Deserialize, Debug)]
struct Config {
    #[serde(default)]
    dirpaths: Vec<PathBuf>,

    #[serde(default)]
    filenames: Vec<OsString>,
}

fn main() -> Result<(), Box<dyn Error>> {
    let input = r#"{
    "dirpaths": ["/usr/src", "/home/me"],
    "filenames": ["foo", "bar"],
}"#;
    let cfg: Config = serde_json::from_str(input)?;
    println!("{cfg:#?}");
    Ok(())
}

fails with:

Error: Error("unknown variant `foo`, expected `Unix` or `Windows`", line: 3, column: 23)

What exactly do I need to do to be able to deserialize to an OsString?

First a clarification: the docs you linked to are just an import of std docs/types.

OsString is an enum in the model, and this works:

    let input = r#"{
        "dirpaths": ["/usr/src", "/home/me"],
        "filenames": [{ "Unix": [102, 111, 111] }, { "Unix": [98, 97, 114] }]
    }"#;

PathBuf will only actually work with paths that could be Strings, which is quite dishonest IMO. (Supporting non-UTF8 paths is a main motivator for supporting OsStrings.)

n.b. I'm not a serde expert and there may be a better way.

I'm not about to change the input format for my program to look like {"Unix": [102, 111, 111]}. So far, the only way I can see to get strings in JSON or TOML to deserialize to OsStrings is to use an intermediate struct with String fields and an impl From<IntermediateConfig> for Config implementation plus serde's #[serde(from = "IntermediateConfig")]. Does anyone know of a better way?

1 Like

That's what I would do. Almost all JSON strings are encoded as UTF-8 anyway, so it's not really possible for your end users to put a non-UTF-8 string in the file unless they want to write the bytes out as an array of numbers. If you want to support both, then I would probably create my own serialize() and deserialize() implementations using the #[serde(with = ...)] attribute that fits what you want.

1 Like