-
Notifications
You must be signed in to change notification settings - Fork 243
Support jsonc format #457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Support jsonc format #457
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some remarks. Also, the two commits could be squashed into one commit,... as well as further fixups of course!
Either way thanks for your contribution!
tests/file_jsonc.rs
Outdated
@@ -52,7 +52,7 @@ fn test_file() { | |||
assert_eq!(s.place.telephone, None); | |||
assert_eq!(s.elements.len(), 10); | |||
assert_eq!(s.elements[3], "4".to_string()); | |||
if cfg!(feature = "preserve_order") { | |||
if cfg!(feature = "TODO: preserve_order") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, that's because jsonc-parser doesn't support preserve_order. need time to contribute that
src/file/format/mod.rs
Outdated
#[cfg(all(feature = "jsonc", feature = "json"))] | ||
formats.insert(FileFormat::Jsonc, vec!["jsonc"]); | ||
|
||
#[cfg(all(feature = "jsonc", not(feature = "json")))] | ||
formats.insert(FileFormat::Jsonc, vec!["jsonc", "json"]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that the jsonc
parser also parses json
even if that feature is disabled?
I think that's counter-intuitive and we should only parse jsonc
with the jsonc
parser.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, there are 2 reasons i added that.
- the file extension for jsonc is actually
.json
, not.jsonc
, so the real question is that when i enable the featurejsonc
it's about the format or the file extension? - for the common usage, people would choose only one of them, not both. and I believe the jsonc format is more common for config files than the json format.
Pull Request Test Coverage Report for Build 16902150946Details
💛 - Coveralls |
Well, I merged the upstream and fixed the test cases. I’m certain that when I submitted the pull request back then, all test cases passed. This behavior should follow one clear approach, rather than just forcing lowercase:
|
The feedback was back in 2023. You later pushed some commits in Feb 2024 and the PR was silent until recently. It's not a surprise that tests could fail given a new major zero ver release was made which permits breaking changes.
Regarding feedback with the I have seen crates like #[cfg(all(feature = "json", feature = "jsonc"))]
panic!("Both `json` and `jsonc` features are active, only one feature for parsing `.json` files is permitted"); Regarding use std::error::Error;
use crate::format;
use crate::map::Map;
use crate::value::Value;
pub fn parse(
uri: Option<&String>,
text: &str,
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
// Parse a JSON/JSONC input from the provided text
let value = format::from_parsed_value(uri, jsonc_parser::parse_to_serde_value(text)?);
format::extract_root_table(uri, value)
}
Lines 27 to 46 in 07f13ff
So that should already simplify your The following should work for now at least: - let value = format::from_parsed_value(uri, jsonc_parser::parse_to_serde_value(text)?);
+ let value = from_jsonc_value(uri, jsonc_parser::parse_to_value(text, &Default::default())?);
format::extract_root_table(uri, value) Should #472 get merged, then EDIT: Oh just noticed that the return type of the result is the value wrapped in an option, which unlike - let value = format::from_parsed_value(uri, jsonc_parser::parse_to_serde_value(text)?);
+ let parsed = jsonc_parser::parse_to_value(text, &Default::default())?;
+ let value = from_jsonc_value(uri, parsed.unwrap_or(ValueKind::Nil));
format::extract_root_table(uri, value) I'll apply that as a review suggestion 😅 |
Please rework your commits as meaningful, atomic commits to be merged. |
51286fd
to
f4b71af
Compare
@epage, @polarathene all green now :D. The .json vs .jsonc issue comes down to the project’s philosophy: should it aim to be flexible or strict? If goes for flexible, the current behavior makes sense, because in practice, almost no one actually uses the .jsonc extension. People generally just want .json files that can contain comments. For example, from the earlier point about case insensitive handling and forcing lowercase mapping, these are closely tied to the overall design philosophy. Under a strict approach, case insensitive handling should be an optional feature rather than the default, and forcing lowercase mapping should be avoided - upper should map to upper, and lower should be lower. |
From https://jsonc.org/
imo comments inside of json is no longer json. We should be properly validating the json we parse. |
tests/testsuite/file_ini.rs
Outdated
|
||
#[test] | ||
fn test_nothing() { | ||
let res = Config::builder() | ||
.add_source(File::from_str("", FileFormat::Ini)) | ||
.build(); | ||
assert!(res.is_ok()); | ||
let c = res.unwrap(); | ||
assert_data_eq!( | ||
format!("{:?}", c), | ||
str!("Config { defaults: {}, overrides: {}, sources: [], cache: Value { origin: None, kind: Table({}) } }") | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these test_nothing
s added? if they are indirectly related (you wanted a test_nothing
for jsonc), then please add these in their own commit before this one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They might have added due to an observation I also noticed with how jsonc-parser
and serde-json
differ in parsing an empty string (Ok(None)
vs Error(E)
).
So this might be a sanity check that on the config-rs
side we're aware of that parsing difference? Especially for the .json
extension since this PR adopts that extension in addition to the serde-json
format parser.
I had previously suggested considering a build.rs
feature conflict check, so that json
vs jsonc
are mutually exclusive? (EDIT: actually may not be necessary since a format hint can be provided to control selection, or similar methods that take an explicit format arg)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@polarathene is correct. since parsing results in None and config-rs does not have a corresponding type for that, these tests were added because .jsonc needs this case. The goal was to decide whether None should be treated as an error or ignored, and to compare behavior with other parsers.
That said, your point makes sense. I’ll remove the tests for the other parsers and keep only the .jsonc one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For existing formats can you add the tests in a separate commit before jsonc support as mentioned earlier in this thread?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d like to, but preferably after this current pull request is resolved. Since it’s been almost two years of inactivity on this thread, focusing on closing this one first makes the process clearer and prevents scope creep. Afterward, I can gradually add more test cases, including for the existing formats.
assert!(res.is_err()); | ||
let err = res.unwrap_err().to_string(); | ||
let expected_prefix = | ||
"Expected colon after the string or word in object property on line 4 column 1 in "; | ||
assert!( | ||
err.starts_with(expected_prefix), | ||
"Error message does not start with expected prefix. Got: {}", | ||
err | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we switch this to snapshot testing (assert_data_eq!
) like our other formats?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was originally written using snapshot testing (assert_data_eq!), but it failed on Windows MSRV due to \ vs / path differences. Adding OS-specific handling would fix it, but it would make the code noticeably messier.
This ultimately comes down to a design philosophy question. While the spec says otherwise, in practice, especially for configuration files, almost nobody uses the .jsonc as extension; all with .json. That said, this discussion could go on forever. For now, I’ll change it so that jsonc only parses .jsonc. If there’s a decision later to move toward a more user-friendly approach, we can revisit it. |
I accidentally clicked “Review changes” earlier but forgot to press the “Submit review” button, so my comment was left in pending status. It should be visible now. |
I did a new commit, see #452