ron-rs/ron

(Feature request) Allow omitting of inner parentheses for structs in enum variants

Closed this issue · 2 comments

When using a normally-tagged enum, the variant is specified as VariantName ( variant_contents... ) and if the variant contains a struct, then the struct itself requires its own ( ) with optional class name. This request is to allow omitting the latter.

Here's an example w/ unit tests to illustrate the point:

use serde::Deserialize;

#[derive(Deserialize, PartialEq, Debug)]
#[serde(transparent)]
struct TopLevel {
    nodes: Vec<MyEnum>,
}

#[derive(Deserialize, PartialEq, Debug)]
enum MyEnum {
    ObjTypeA(ObjTypeADef),
    ObjTypeB(ObjTypeBDef),
}

#[derive(Deserialize, PartialEq, Debug)]
struct ObjTypeADef {
    x: i32,
    y: Option<i32>,
}

#[derive(Deserialize, PartialEq, Debug)]
struct ObjTypeBDef {
    x: i32,
    y: Option<i32>,
    z: Option<i32>,
}

#[test]
fn parse_verbose() {
    let content = r#"
        [ 
            ObjTypeA ( ObjTypeADef ( 
                x: 1
            ) ), 
            ObjTypeB ( ObjTypeBDef (
                x: 1
            ) ),
        ]
    "#;
    let parsed = ron::from_str::<TopLevel>(&content);
    let expected = Ok(TopLevel {
        nodes: vec![
            MyEnum::ObjTypeA(ObjTypeADef { x: 1, y: None }),
            MyEnum::ObjTypeB(ObjTypeBDef {
                x: 1,
                y: None,
                z: None,
            }),
        ],
    });
    assert_eq!(parsed, expected);
}

#[test]
fn parse_less_verbose() {
    let content = r#"
        [ 
            ObjTypeA (( 
                x: 1
            )), 
            ObjTypeB ((
                x: 1
            )),
        ]
    "#;
    let parsed = ron::from_str::<TopLevel>(&content);
    let expected = Ok(TopLevel {
        nodes: vec![
            MyEnum::ObjTypeA(ObjTypeADef { x: 1, y: None }),
            MyEnum::ObjTypeB(ObjTypeBDef {
                x: 1,
                y: None,
                z: None,
            }),
        ],
    });
    assert_eq!(parsed, expected);
}

#[test]
fn parse_intuitive() {
    let content = r#"
        [ 
            ObjTypeA ( 
                x: 1
            ), 
            ObjTypeB (
                x: 1
            ),
        ]
    "#;
    let parsed = ron::from_str::<TopLevel>(&content);
    let expected = Ok(TopLevel {
        nodes: vec![
            MyEnum::ObjTypeA(ObjTypeADef { x: 1, y: None }),
            MyEnum::ObjTypeB(ObjTypeBDef {
                x: 1,
                y: None,
                z: None,
            }),
        ],
    });
    assert_eq!(parsed, expected);
}

Running this sample code currently produces these results:

  • parse_verbose test succeeds.
  • parse_less_verbose test also succeeds.
  • parse_intuitive test fails with a deserialization error.

As hinted in the example code, #[serde(untagged)] for the enum isn't always a reliable workaround since it'll successfully deserialize something intended as a valid ObjTypeB as an ObjTypeA in some cases. For my use, which is where the variants are different kinds of UI elements w/ many fields in common (and many of them optional), but with distinct behaviors, it's much more robust for the variants to be explicitly named.

Just mentioning this idea in an Issue here in case it is of any use, but as hinted in parse_less_verbose, it really is just a matter of an extra pair of parentheses for now so RON is still adequate for what I'm trying to do as-is & regardless I appreciate that this project exists for the many benefits it brings overall. Thanks

Thank you for reaching out and submitting this issue! This is already possible with RON using the unwrap_variant_newtypes extension. You can either enable it inside the source document (note the #![enable(unwrap_variant_newtypes)] at the start of the document):

#[test]
fn parse_intuitive() {
    let content = r#"
        #![enable(unwrap_variant_newtypes)]
        [ 
            ObjTypeA ( 
                x: 1
            ), 
            ObjTypeB (
                x: 1
            ),
        ]
    "#;
    let parsed = ron::from_str::<TopLevel>(&content);
    let expected = Ok(TopLevel {
        nodes: vec![
            MyEnum::ObjTypeA(ObjTypeADef { x: 1, y: None }),
            MyEnum::ObjTypeB(ObjTypeBDef {
                x: 1,
                y: None,
                z: None,
            }),
        ],
    });
    assert_eq!(parsed, expected);
}

or enable it for parsing any document (note the use of ron::Options::default().with_default_extension(ron::extensions::Extensions::UNWRAP_VARIANT_NEWTYPES).from_str(ron) instead of ron::from_str(ron)):

#[test]
fn parse_intuitive() {
    let content = r#"
        [ 
            ObjTypeA ( 
                x: 1
            ), 
            ObjTypeB (
                x: 1
            ),
        ]
    "#;
    let parsed = ron::Options::default().with_default_extension(
        ron::extensions::Extensions::UNWRAP_VARIANT_NEWTYPES
    ).from_str::<TopLevel>(&content);
    let expected = Ok(TopLevel {
        nodes: vec![
            MyEnum::ObjTypeA(ObjTypeADef { x: 1, y: None }),
            MyEnum::ObjTypeB(ObjTypeBDef {
                x: 1,
                y: None,
                z: None,
            }),
        ],
    });
    assert_eq!(parsed, expected);
}

It is worth noting that once you enable this extension, you are forced to use the implicit form and can no longer be explicit.

I hope this helps, and please feel free to reach out again if you need a different solution for your use case :)

Awesome, thanks! This extension perfectly solves the problem, especially convenient to use with_default_extension rather than declaring that extension in the data file. Will close this issue out.