Serde/YAML Support for references?

Hello.
how to deserialize my YAML file with references?

My YAML file:

version: 1.0
users:
  - id: &jkovalsky jkovalsky
    name: jan kovalsky
  - id: &tnovak tnovak
    name: ted novak
groups:
  - id: administrators
    users: [ *jkovalsky ]
    privileges: [ create-a-new-user, block-a-user, delete-a-user, read-content]
  - id: employees
    users: [ *tnovak ]
    privileges: [ read-content]

Structs:

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct User {
    id: String,
    name: String
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Group {
    id: String,
    users: Vec<User>,
    privileges: Vec<String>,
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Config {
    version: String,
    users: Vec<User>,
    groups: Vec<Group>,
}

Rust playground: click me

Your anchor was in the wrong spot. You were aliasing the id strings, not the whole map that represented the user, as the error message hinted at.

Message("invalid type: string \"jkovalsky\", expected struct User", Some(Pos { marker: Marker { index: 66, line: 4, col: 27 }, path: "groups[0].users[0]" }))',

You can just move the anchor outside of the id pair and it works
Playground

fn main() {
    println!("Hello, world!");
    
    let yaml = r#"  
        version: 1.0
        users:
          - &jkovalsky
            id: jkovalsky
            name: jan kovalsky
          - &tnovak 
            id: tnovak
            name: ted novak
        groups:
          - id: administrators
            users: [ *jkovalsky ]
            privileges: [ create-a-new-user, block-a-user, delete-a-user, read-content]
          - id: employees
            users: [ *tnovak ]
            privileges: [ read-content]"#;
        
    let values: Config = serde_yaml::from_str(yaml).unwrap();
    println!("{:#?}", values);
}

Output:

Hello, world!
Config {
    version: "1.0",
    users: [
        User {
            id: "jkovalsky",
            name: "jan kovalsky",
        },
        User {
            id: "tnovak",
            name: "ted novak",
        },
    ],
    groups: [
        Group {
            id: "administrators",
            users: [
                User {
                    id: "jkovalsky",
                    name: "jan kovalsky",
                },
            ],
            privileges: [
                "create-a-new-user",
                "block-a-user",
                "delete-a-user",
                "read-content",
            ],
        },
        Group {
            id: "employees",
            users: [
                User {
                    id: "tnovak",
                    name: "ted novak",
                },
            ],
            privileges: [
                "read-content",
            ],
        },
    ],
}
2 Likes

This is not the fault of serde-yaml. Apart from the wrong anchor, you also had a typo, kowalsky vs. kovalsky. This works:

        version: 1.0
        users:
          - &jkowalsky
            id: jkovalsky
            name: jan kovalsky
          - &tnovak
            id: tnovak
            name: ted novak
        groups:
          - id: administrators
            users: [ *jkowalsky ]
            privileges: [ create-a-new-user, block-a-user, delete-a-user, read-content]
          - id: employees
            users: [ *tnovak ]
            privileges: [ read-content]"

Thanks for your replies.
The solution from @semicoleon is satisfactory for me.

I have one more question - how to do shared references? For example Rc<User>?
Rust playground: this solution is correct?

You can't. Just by looking at a YAML document it's impossible to say whether two items are meant to point to the same User object or they just happen to have the same keys, so every time you deserialize a Rc<User> you'll get a new object instead of reusing the existing one.

That's one of the reasons serde puts the Deserialize impl for Rc and Arc behind a feature gate. This is the comment in serde's Cargo.toml:

3 Likes

Thx for your reply.
Ok. I understand. The Mechanism for linking between objects (references) I have to implement myself. I'm Right?
Did you do it anytime?

Correct.

I normally try to avoid a situation where "identity" is important (i.e. you want multiple Rcs to point at the same object) because patching that up after the fact seems difficult and unnecessary.

1 Like