ron-rs / ron

Rusty Object Notation
https://docs.rs/ron
Apache License 2.0
3.35k stars 123 forks source link

Roudtrip RON <-> JSON #543

Open mrchantey opened 3 months ago

mrchantey commented 3 months ago

It seems we can convert from RON to JSON but the inverse has unexpected behavior, calling ron::to_string_pretty() on a serde_json::Value outputs json, not ron.

use anyhow::Result;

pub fn ron_to_json(ron_str: &str) -> Result<String> {
  let ron_val = ron::de::from_str::<ron::Value>(ron_str)?;
  let json_str = serde_json::to_string_pretty(&ron_val)?;
  Ok(json_str)
}

pub fn json_to_ron(json_str: &str) -> Result<String> {
  let json_val = serde_json::from_str::<serde_json::Value>(json_str)?;
  let ron_str = ron::ser::to_string_pretty(&json_val, Default::default())?;
  Ok(ron_str)
}

#[cfg(test)]
mod test {
  use anyhow::Result;
  use serde::Deserialize;
  use serde::Serialize;
  use sweet::*;

  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
  struct MyStruct {
    name: String,
    age: u32,
  }

  #[test]
  fn ron_to_json() -> Result<()> {
    let val = MyStruct {
      name: "John".to_string(),
      age: 32,
    };
    let ron_str = ron::ser::to_string_pretty(&val, Default::default())?;
    let json = crate::utils::ron_to_json(&ron_str)?;
    let ron_str2 = crate::utils::json_to_ron(&json)?;
    // this line fails because ron_str2 is outputted in json format
    let val2 = ron::de::from_str::<MyStruct>(&ron_str2)?;
    expect(val).to_be(val2)?;

    println!("{}", ron_str);
    println!("{}", json);
    println!("{}", ron_str2);

    Ok(())
  }
}
juntyr commented 3 months ago

Yes, that is expected behaviour. RON is a superset of JSON, so all JSON documents are valid RON documents. But RON also supports structs and enums with a Rusty syntax, which is not supported by JSON. In your ron_to_json function, you serialize a ron::Value to JSON, which maps all RON-specific concepts down into JSON in a destructive process, e.g. MyStruct(name: "John", age: 32) becomes {"name": "John", "age": 32}. While RON can still parse this JSON document, it cannot convert it back to the original since a map is not a struct.

TLDR: RON can roundtrip JSON documents (through JSON and RON), but not RON documents through JSON.

Still, a reformulation of this issue is on my long-term TODO list. serde::Value will get some love in a future release to allow all RON documents to roundtrip through it. Perhaps it could then also be given the ability to switch to a more verbose encoding when targeting a non-RON format such that RON -> ron::Value -> JSON -> ron::Value -> RON could roundtrip ... but I'm still not 100% sure that this would work.

mrchantey commented 3 months ago

Ok that makes sense thanks for the explanation