char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somfjson: remove members without leaving spurious whitespace75a6060

main
4.0 KiB87 linesraw

fJSON

Friendly JSON: JSON values with comments, single-quoted strings, ASCII identifier keys, trailing commas, and commas that can be replaced by newlines. Root objects may omit their braces. Empty or comment-only documents represent an empty object.

name: "demo"
options: {
  enabled: true
  targets: ["linux", "macos"]
}

Values remain JSON data, not JavaScript expressions. Bare string values, duplicate decoded keys, nondecimal numbers, and unescaped control characters are rejected. Only newlines outside block comments separate entries. Container nesting is limited to 128 levels below the root by default, for both parsing and printing. Pass a custom Options to parse_with, stringify_with, syntax::parse_with, syntax::parse_object_with, or Document::parse_with / Document::parse_object_with to allow more or less nesting. Document remembers its options for later edits.

Values

let value = serde_fjson::parse("name: 'demo'\nactive: true")?;
let text = serde_fjson::stringify(&value)?;
# Ok::<(), serde_fjson::Error>(())

parse returns a serde_json::Value and accepts objects, arrays, and scalar roots. Numbers use the enabled serde_json::Number representation; conversion reports out-of-range numbers at their original source spans. Object insertion order is preserved.

stringify always pretty-prints: two-space indentation, unquoted identifier keys, implicit nonempty root objects, and newline-separated entries. Short scalar arrays stay inline; longer arrays and nested containers expand. It targets an 80-column width, but does not split strings or keys. It does not add a final newline. It returns Result<String, Error> and rejects values beyond the nesting limit rather than producing text that cannot be parsed back.

syntax::parse exposes the span-aware syntax tree without numeric conversion. syntax::parse_object requires a root object, for config consumers. Errors expose UTF-8 byte spans and can be rendered with file, line, and character-column locations, including the first occurrence of duplicate keys. ErrorKind distinguishes syntax, Unicode, numeric-range, and nesting errors.

Source-preserving edits

use serde_fjson::Document;
use serde_json::json;

let mut document = Document::parse("// keep\noptions: {enabled: false}")?;
document.set(&["options".into(), "enabled".into()], &json!(true))?;
document.set(&["options".into(), "targets".into()], &json!(["linux"]))?;
document.remove(&["options".into(), "enabled".into()])?;
let text = document.into_source();
# Ok::<(), serde_fjson::Error>(())

Document::parse_object requires an object root in its input and preserves config-specific parse diagnostics. The returned document still supports root replacement with any JSON value. Implicit root spans exclude trailing trivia, which survives root replacement, including comments and final line endings.

Paths contain PathSegment::Key(&str) or PathSegment::Index(usize). Parents must exist. set replaces an existing value, inserts a missing object key, or appends at an array index equal to its length. An empty path replaces the root. remove returns whether the field/item existed, and does not accept an empty path.

Untouched source is retained byte-for-byte. Setting an equal value is a no-op. Scalar string replacements retain their existing quote style. New values are pretty-printed using the document's line endings and surrounding indentation.

External comments survive removal, including comments between a key, colon, and value. Comments inside a replaced or removed value disappear with it. No attempt is made to infer which neighboring comments belong to an entry. A member alone on its line is removed with its indentation and line break, and blank gaps around its colon and comma go with it; deletion otherwise does not reformat the document.

Edits reparse the result to refresh spans and validate it. A failed edit leaves the document unchanged. source() exposes the current text, root() its syntax tree, and to_value() converts it to JSON data.

1# fJSON
2
3Friendly JSON: JSON values with comments, single-quoted strings, ASCII identifier
4keys, trailing commas, and commas that can be replaced by newlines. Root objects
5may omit their braces. Empty or comment-only documents represent an empty object.
6
7```text
8name: "demo"
9options: {
10  enabled: true
11  targets: ["linux", "macos"]
12}
13```
14
15Values remain JSON data, not JavaScript expressions. Bare string values, duplicate
16decoded keys, nondecimal numbers, and unescaped control characters are rejected.
17Only newlines outside block comments separate entries. Container nesting is limited
18to 128 levels below the root by default, for both parsing and printing.
19Pass a custom `Options` to `parse_with`, `stringify_with`,
20`syntax::parse_with`, `syntax::parse_object_with`, or `Document::parse_with` /
21`Document::parse_object_with` to allow more or less nesting. `Document`
22remembers its options for later edits.
23
24## Values
25
26```rust
27let value = serde_fjson::parse("name: 'demo'\nactive: true")?;
28let text = serde_fjson::stringify(&value)?;
29# Ok::<(), serde_fjson::Error>(())
30```
31
32`parse` returns a `serde_json::Value` and accepts objects, arrays, and scalar roots.
33Numbers use the enabled `serde_json::Number` representation; conversion reports
34out-of-range numbers at their original source spans. Object insertion order is
35preserved.
36
37`stringify` always pretty-prints: two-space indentation, unquoted identifier keys,
38implicit nonempty root objects, and newline-separated entries. Short scalar
39arrays stay inline; longer arrays and nested containers expand. It targets an
4080-column width, but does not split strings or keys. It does not add a final
41newline. It returns `Result<String, Error>` and rejects values beyond the nesting
42limit rather than producing text that cannot be parsed back.
43
44`syntax::parse` exposes the span-aware syntax tree without numeric conversion.
45`syntax::parse_object` requires a root object, for config consumers. Errors expose
46UTF-8 byte spans and can be rendered with file, line, and character-column
47locations, including the first occurrence of duplicate keys. `ErrorKind`
48distinguishes syntax, Unicode, numeric-range, and nesting errors.
49
50## Source-preserving edits
51
52```rust
53use serde_fjson::Document;
54use serde_json::json;
55
56let mut document = Document::parse("// keep\noptions: {enabled: false}")?;
57document.set(&["options".into(), "enabled".into()], &json!(true))?;
58document.set(&["options".into(), "targets".into()], &json!(["linux"]))?;
59document.remove(&["options".into(), "enabled".into()])?;
60let text = document.into_source();
61# Ok::<(), serde_fjson::Error>(())
62```
63
64`Document::parse_object` requires an object root in its input and preserves
65config-specific parse diagnostics. The returned document still supports root
66replacement with any JSON value. Implicit root spans exclude trailing trivia,
67which survives root replacement, including comments and final line endings.
68
69Paths contain `PathSegment::Key(&str)` or `PathSegment::Index(usize)`.
70Parents must exist. `set` replaces an existing value, inserts a missing object key,
71or appends at an array index equal to its length. An empty path replaces the root.
72`remove` returns whether the field/item existed, and does not accept an empty path.
73
74Untouched source is retained byte-for-byte. Setting an equal value is a no-op.
75Scalar string replacements retain their existing quote style. New values are
76pretty-printed using the document's line endings and surrounding indentation.
77
78External comments survive removal, including comments between a key, colon, and
79value. Comments inside a replaced or removed value disappear with it. No attempt
80is made to infer which neighboring comments belong to an entry. A member alone
81on its line is removed with its indentation and line break, and blank gaps
82around its colon and comma go with it; deletion otherwise does not reformat
83the document.
84
85Edits reparse the result to refresh spans and validate it. A failed edit leaves
86the document unchanged. `source()` exposes the current text, `root()` its syntax
87tree, and `to_value()` converts it to JSON data.