Skip to content

Commit 5667cc4

Browse files
committed
feat: Support jsonc format
1 parent 5f30873 commit 5667cc4

File tree

10 files changed

+317
-2
lines changed

10 files changed

+317
-2
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ json = ["serde_json"]
126126
yaml = ["yaml-rust2"]
127127
ini = ["rust-ini"]
128128
json5 = ["json5_rs", "dep:serde-untagged"]
129+
jsonc = ["dep:jsonc-parser"]
129130
convert-case = ["convert_case"]
130-
preserve_order = ["indexmap", "toml?/preserve_order", "serde_json?/preserve_order", "ron?/indexmap"]
131+
preserve_order = ["indexmap", "toml?/preserve_order", "serde_json?/preserve_order", "jsonc-parser?/preserve_order", "ron?/indexmap"]
131132
async = ["async-trait"]
132133
toml = ["dep:toml"]
133134

@@ -141,6 +142,7 @@ yaml-rust2 = { version = "0.10", optional = true }
141142
rust-ini = { version = "0.21", optional = true }
142143
ron = { version = "0.8", optional = true }
143144
json5_rs = { version = "0.4", optional = true, package = "json5" }
145+
jsonc-parser = { version = "0.26.3", optional = true }
144146
indexmap = { version = "2.10.0", features = ["serde"], optional = true }
145147
convert_case = { version = "0.6", optional = true }
146148
pathdiff = "0.2"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
- Set defaults
1313
- Set explicit values (to programmatically override)
14-
- Read from [JSON], [TOML], [YAML], [INI], [RON], [JSON5] files
14+
- Read from [JSON], [TOML], [YAML], [INI], [RON], [JSON5], [JSONC] files
1515
- Read from environment
1616
- Loosely typed — Configuration values may be read in any supported type, as long as there exists a reasonable conversion
1717
- Access nested fields using a formatted path — Uses a subset of JSONPath; currently supports the child ( `redis.port` ) and subscript operators ( `databases[0].name` )
@@ -22,6 +22,7 @@
2222
[INI]: https://github.com/zonyitoo/rust-ini
2323
[RON]: https://github.com/ron-rs/ron
2424
[JSON5]: https://github.com/callum-oakley/json5-rs
25+
[JSONC]: https://github.com/dprint/jsonc-parser
2526

2627
Please note that this library can not be used to write changed configuration
2728
values back to the configuration file(s)!
@@ -36,6 +37,7 @@ values back to the configuration file(s)!
3637
- `toml` - Adds support for reading TOML files
3738
- `ron` - Adds support for reading RON files
3839
- `json5` - Adds support for reading JSON5 files
40+
- `jsonc` - Adds support for reading JSONC files
3941

4042
### Support for custom formats
4143

src/file/format/jsonc.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use std::error::Error;
2+
3+
use crate::format;
4+
use crate::map::Map;
5+
use crate::value::{Value, ValueKind};
6+
use jsonc_parser::JsonValue;
7+
8+
pub(crate) fn parse(
9+
uri: Option<&String>,
10+
text: &str,
11+
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
12+
let value = match jsonc_parser::parse_to_value(text, &Default::default())? {
13+
Some(json_value) => from_jsonc_value(uri, json_value),
14+
None => Value::new(uri, ValueKind::Nil),
15+
};
16+
format::extract_root_table(uri, value)
17+
}
18+
19+
fn from_jsonc_value(uri: Option<&String>, value: JsonValue<'_>) -> Value {
20+
let vk = match value {
21+
JsonValue::Null => ValueKind::Nil,
22+
JsonValue::String(v) => ValueKind::String(v.to_string()),
23+
JsonValue::Number(number) => {
24+
if let Ok(v) = number.parse::<i64>() {
25+
ValueKind::I64(v)
26+
} else if let Ok(v) = number.parse::<f64>() {
27+
ValueKind::Float(v)
28+
} else {
29+
unreachable!();
30+
}
31+
}
32+
JsonValue::Boolean(v) => ValueKind::Boolean(v),
33+
JsonValue::Object(table) => {
34+
let m = table
35+
.into_iter()
36+
.map(|(k, v)| (k, from_jsonc_value(uri, v)))
37+
.collect();
38+
ValueKind::Table(m)
39+
}
40+
JsonValue::Array(array) => {
41+
let l = array
42+
.into_iter()
43+
.map(|v| from_jsonc_value(uri, v))
44+
.collect();
45+
ValueKind::Array(l)
46+
}
47+
};
48+
Value::new(uri, vk)
49+
}

src/file/format/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ mod ron;
2323
#[cfg(feature = "json5")]
2424
mod json5;
2525

26+
#[cfg(feature = "jsonc")]
27+
mod jsonc;
28+
2629
/// File formats provided by the library.
2730
///
2831
/// Although it is possible to define custom formats using [`Format`] trait it is recommended to use `FileFormat` if possible.
@@ -52,6 +55,10 @@ pub enum FileFormat {
5255
/// JSON5 (parsed with json5)
5356
#[cfg(feature = "json5")]
5457
Json5,
58+
59+
/// JSONC (parsed with jsonc)
60+
#[cfg(feature = "jsonc")]
61+
Jsonc,
5562
}
5663

5764
pub(crate) fn all_extensions() -> &'static HashMap<FileFormat, Vec<&'static str>> {
@@ -79,6 +86,9 @@ pub(crate) fn all_extensions() -> &'static HashMap<FileFormat, Vec<&'static str>
7986
#[cfg(feature = "json5")]
8087
formats.insert(FileFormat::Json5, vec!["json5"]);
8188

89+
#[cfg(feature = "jsonc")]
90+
formats.insert(FileFormat::Jsonc, vec!["jsonc"]);
91+
8292
formats
8393
})
8494
}
@@ -115,13 +125,17 @@ impl FileFormat {
115125
#[cfg(feature = "json5")]
116126
FileFormat::Json5 => json5::parse(uri, text),
117127

128+
#[cfg(feature = "jsonc")]
129+
FileFormat::Jsonc => jsonc::parse(uri, text),
130+
118131
#[cfg(all(
119132
not(feature = "toml"),
120133
not(feature = "json"),
121134
not(feature = "yaml"),
122135
not(feature = "ini"),
123136
not(feature = "ron"),
124137
not(feature = "json5"),
138+
not(feature = "jsonc"),
125139
))]
126140
_ => unreachable!("No features are enabled, this library won't work without features"),
127141
}

tests/testsuite/file_jsonc.enum.jsonc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
// foo
3+
"bar": "bar is a lowercase param",
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"ok": true,
3+
"error"
4+
}

tests/testsuite/file_jsonc.jsonc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
// c
3+
/* c */
4+
"debug": true,
5+
"debug_json": true,
6+
"production": false,
7+
"arr": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
8+
"place": {
9+
"name": "Torre di Pisa",
10+
"longitude": 43.7224985,
11+
"latitude": 10.3970522,
12+
"favorite": false,
13+
"reviews": 3866,
14+
"rating": 4.5,
15+
"creator": {
16+
"name": "John Smith",
17+
"username": "jsmith",
18+
"email": "jsmith@localhost"
19+
},
20+
},
21+
"FOO": "FOO should be overridden",
22+
"bar": "I am bar",
23+
}

0 commit comments

Comments
 (0)