Deserialising from toml adds extra double quotes. Can I get rid of them at the time of deserializing, or should I trim it explicitly later? Thanks

#![allow(unused)]
use std::{sync::Arc, collections::HashMap};

use serde::{Deserialize, Deserializer};
use toml::{self, Value}; // 0.5.9

fn main() {
    let toml_string = r#"[foobar]
arg = "foo"
[[foobar.skip]]
name = "foo"
attr = [["foo", "bar"], ["baz", "abc"]]
[[foobar.skip]]
name = "another_foo"
    "#;
    println!("{}",toml_string);
let foo: Custom = toml::from_str(toml_string).unwrap();
println!("Foo: {:?}", foo);
}

#[derive(Debug, Deserialize)]
pub struct Custom {
    foobar: FooBar 
}
#[derive(Debug, Deserialize)]
pub struct FooBar {
    arg: String,
    #[serde(deserialize_with = "parse_skip")]
    skip: Option<Arc<Vec<Skip>>>,
}
#[derive(Debug, Default)]
pub struct Skip {
    pub name: String,
    pub attr: Option<Arc<HashMap<String, String>>>,
}

fn parse_skip<'de, D>(deserializer: D) -> Result<Option<Arc<Vec<Skip>>>, D::Error>
where
    D: Deserializer<'de>,
{
    let mut skips: Vec<Skip> = Vec::new();
    let vals: Vec<HashMap<&str, Value>> = Deserialize::deserialize(deserializer)?;
    for val in vals.into_iter() {
        let mut skip = Skip {
            ..Default::default()
        };
        for (k, v) in val.into_iter() {
            
            if k.eq("name") {
                skip.name = v.to_string();
            }
            if k.eq("attr") && v.is_array() {
                let labels = v
                    .as_array()
                    .into_iter()
                    .filter(|l| l.len() == 2)
                    .map(|f| {
                        
                        (f[0].to_string(), f[1].to_string())
                        
                    })
                    .collect::<HashMap<String, String>>();
                skip.attr = Some(Arc::new(labels));
            }
        }
        skips.push(skip);

    }
    Ok(Some(Arc::new(skips)))
}

(Playground)

Output:

[foobar]
arg = "foo"
[[foobar.skip]]
name = "foo"
attr = [["foo", "bar"], ["baz", "abc"]]
[[foobar.skip]]
name = "another_foo"
    
Foo: Custom { foobar: FooBar { arg: "foo", skip: Some([Skip { name: "\"foo\"", attr: Some({"[\"foo\", \"bar\"]": "[\"baz\", \"abc\"]"}) }, Skip { name: "\"another_foo\"", attr: None }]) } }

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.89s
     Running `target/debug/playground`

Is it possible to get rid of the extra double quotes

name: "\"foo\""

and serialize it as

name: "foo"

?
Thank you.

It's your own deserializer function that adds the quotes.

fn main() {
    println!("{}", Value::String("test".to_string()));
}
"test"

toml::Value's Display impl (which is used by to_string) outputs the value as it would appear in a toml file, in this case the string foo becomes "foo". You should use match or Value::as_str instead.

6 Likes

Thank you @Heliozoa . However, my type inside the hashmap is a String, not &str and Value::as_str returns an &str, so wouldn't that require me to use to_string(), to_owned() or format!() to clone it as a String?

You should probably use .as_str().to_owned(). .to_string() uses the Display impl of Value to get the value, which prints out toml. .as_str() extracts an &str and then .to_owned() turns it into an owned String.

2 Likes

The 30 000-foot view question is: why are you deserializing into a Value in the first place? Your data clearly has a schema (it's statically typed), so you should be deserializing directly into your own domain objects instead of the toml Value type.

2 Likes

Thank you. The reason for deserializing it to a Value is because both are different types

"attr": Array()

and

"name": String()

or am I'm doing it wrong here?

You can deserialize into structs with different types of fields. In fact you are doing just that implicitly with all of the other #[derive(Deserialize)] impls. What are you trying to achieve that doesn't work out of the box with the derived, strongly-typed impls?

1 Like

Thank you. Let me try that and I will update the progress here.

to_owned from as_str was producing the same output, but using write! macro to create a String from the &str returned by as_str did the trick.
Thank you.

There must be something else you are overlooking here. <&str>::to_owned() definitely does not add any quotes on its own.

2 Likes

I also mentioned match, because Value is a regular enum where the String variant contains the string: Value in toml::value - Rust
So you can use match (or if let) to get the string out of it

if let Value::String(s) = value {
    println!("{s}");
}
match value {
    Value::String(s) => println!("{s}"),
    _ => panic!("something else")
}
2 Likes

You're right. I was exhausted and I didn't know what I was writing. Started fresh, used match to cleanly parse each enums from toml::Value and yes, to_owned() doesn't add quotes. Everyone here were of great help. Thank you.

1 Like