Round-trip comments for JSONC/HJSON, with dict-like APIs that stay simple & easy for app config editing.
- Keep comments on read and write (
loads→ edit →restore). - Work with nested structures via
sdict≈ deepmerge + deepdiff + benedict - Use weak-reference ordered containers via
weakList/OrderedWeakSet.
Comments are data too, just like codes are data too by John von Neumann
Warning
This project is currently alpha (0.1.x) and still being refactored.
pip install jsonc-sdictFor local development:
pip install -e ".[dev]"import json
import hjson
from jsonc_sdict import jsonc, AS_DATA
raw = """
// header
{
"a": 1, // inline
"b": 2
}
// footer
"""
def loads(self, obj):
return hjson.loads(obj, ...) # pre-fill your custom args here
jc = jsoncDict(raw, loads, dumps=hjson.dumps)
jc.insert_comment(
{
"/*\\nnew-block": "multi\\nline\\n",
"//\\nnew-line-above": "line above b\\n",
"//this-is-data" + AS_DATA: ["not a comment key"],
},
key="b",
)
print(jc.full)jsonc stores comments as synthetic keys in the underlying mapping:
<prefix><position-marker><id><SEED>
SEEDis auto-appended to mark an internal comment key.- Add
AS_DATAsuffix to force a key starting with comment prefix to be treated as normal data.
Common forms:
| Internal key prefix | Means | Restored shape |
|---|---|---|
// |
single-line comment, inline mode | after current value/comma |
//\n |
single-line comment, line-above mode | independent line before next key/value |
/* |
block comment (default) | inline block comment |
/*\n |
block comment with trailing newline mode | rendered with line break behavior |
/*, |
block comment before comma | placed before comma of current item |
/*k |
block comment before key slot | before JSON key token |
/*: |
block comment before colon slot | between key and value |
/*v |
block comment before value slot | after colon, before value |
/- |
slash_dash comment | comments out a whole subtree (KDL-like style) |
Example mapping shape:
{
"//0<SEED>": ' "": null,',
"0": 0,
"//1<SEED>": " 0",
"//\n2<SEED>": ' "1": 1,/* 1 */',
"/*,3<SEED>": " 2 ",
"2": 2,
"/*\n4<SEED>": " 👻 ",
"/*v6<SEED>": " 6 ",
"6//": 6,
"/*k7<SEED>": " 7 ",
"7": 7,
"/-node<SEED>": {"ignored": "slash_dash comment"},
"node": {"kept": "real data"},
}Invalid JSONC examples:
/* // this is block comment */ trailing-text-is-illegalLOG=DEBUG enables debug-level logging in project loggers.
Common setup:
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
LOG=DEBUG pytest -q- Inline comment (
//...): stored as//<id><SEED>, restored near the current item. - Line-above comment (
//\n...): stored as line-above mode, restored before next item. - Block comment (
/*...*/): stored with mode markers (/*,/*,,/*k,/*:,/*v,/*\n) to preserve placement. - slash_dash comment (
/-name): special key style that comments out a full subtree, similar to KDL config style.
sdictwraps both mapping and iterable nodes; nested access may returnsdictviews, not raw dict/list.- Cache fields (for example
body,body_restored) rely on mutation hooks; bypassing APIs can leave stale cache. dfs()warns against mutating yielded data during iteration.insert(update, key=...|index=...)is ordering-oriented: it inserts by reordering keys after update.
- Items must support both
__hash__and weak references (__weakref__); built-inint/str/list/dictdo not qualify. - Weak references can disappear when no strong references exist; list length can shrink unexpectedly.
WeakList(noRepeat=True)is not identical toOrderedWeakSet: repeated append/insert can move item position.
| pypi | commits | issues | about | lack |
|---|---|---|---|---|
| spyoungtech/json-five |
Python JSON5 parser with round-trip preservation of comments | can keep comment, but in AST-tree style with lots of re-defined concepts (e.g: BlockComment/wsc_before) |
||
| tusharsadhwani/json5kit |
A Roundtrip parser and CST for JSON, JSONC and JSON5. | |||
| dpranke/pyjson5 |
A Python implementation of the JSON5 data format | |||
| austinyu/ujson5 |
A fast JSON5 encoder/decoder for Python | |||
| qvecs/qjson5 |
📎 A quick JSON5 implementation written in C, with Python bindings. |
// /* this is still single-line comment so this line is illegal */